renanbastos93 / alertcovid19

Alert COVID-19 is a small multiplatform tool written in Golang to help keep you informed about the current situation of COVID-19 in your region, while you stay safe at home.
MIT License
17 stars 3 forks source link

We need to create a parameter to filter by states #3

Open renanbastos93 opened 4 years ago

renanbastos93 commented 4 years ago

We need to improve this application to create some filters I suggested we create a filter by states but we can create also filter by cities and countries.

natea123 commented 4 years ago

New to open-source contribution but would like to take a crack at this.

Since this is pulling from an API just showing cases in Brazil [1] would you want to just implement states/cities filter for Brazil?

[1] https://covid19-brazil-api-docs.now.sh/

natea123 commented 4 years ago

Additionally, implementing the NovelCOVID API [1] may make filtering by country easier

[1] https://github.com/NovelCOVID/API

renanbastos93 commented 4 years ago

New to open-source contribution but would like to take a crack at this.

Since this is pulling from an API just showing cases in Brazil [1] would you want to just implement states/cities filter for Brazil?

[1] https://covid19-brazil-api-docs.now.sh/

Hello, thanks for this interesting contribution here.

Well, I believe that we should create a filter for any country, state, and city. I wish this system be used by anyone.

renanbastos93 commented 4 years ago

Additionally, implementing the NovelCOVID API [1] may make filtering by country easier

[1] https://github.com/NovelCOVID/API

How nice, project it is interesting I will see. we can use this API too.

natea123 commented 4 years ago

Working on adding the new API to the application with minimal change but running into error

json: cannot unmarshal array into Go value of type struct { Data main.LastValues "json:\"data\"" }

Below is my current implementation, running into issues unmarshaling the data, any insight would be appreciated:

package main

import (
    "context"
    "encoding/json"
    "flag"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/gen2brain/beeep"
)

// Exported ...
const (
    IMG string = "https://static.poder360.com.br/2020/03/23312-868x644.png"
    URL        = "https://corona.lmao.ninja/countries?sort=country"
)

// LastValues ...
type LastValues struct {
    Confirmed int `json:"cases"`
    Deaths    int `json:"deaths"`
    Recovered int `json:"recovered"`
}

func (l LastValues) String() string {
    return fmt.Sprintf("Confirmed: %d, Deaths: %d, Recovered: %d", l.Confirmed, l.Deaths, l.Recovered)
}

// fetchCOVID19Data ...
func fetchCOVID19Data(ctx context.Context, req *http.Request) <-chan LastValues {
    ch := make(chan LastValues)
    go func() {
        var r struct {
            Data LastValues `json:"data"`
        }
        body, err := http.DefaultClient.Do(req)
        if err != nil {
            log.Printf("fetchCOVID19Data: %v", err)
            return
        }
        defer body.Body.Close()
        err = json.NewDecoder(body.Body).Decode(&r)
        if err != nil {
            log.Printf("fetchCOVID19Data: %v", err)
            return
        }

        select {
        case ch <- LastValues{r.Data.Confirmed, r.Data.Deaths, r.Data.Recovered}:
        case <-ctx.Done():
        }
    }()
    return ch
}

func routine(sleep time.Duration) {
    cachedVal := LastValues{}
    const timeout = time.Second * 2
    for {
        ctx, cancel := context.WithTimeout(context.Background(), timeout)
        req, err := http.NewRequestWithContext(ctx, "GET", URL, nil)
        if err != nil {
            panic("internal error - misuse of NewRequestWithContext")
        }
        select {
        case newVal := <-fetchCOVID19Data(ctx, req):
            if cachedVal != newVal {
                err := beeep.Alert("COVID-19 Brazil", newVal.String(), IMG)
                if err != nil {
                    log.Printf("rountine: %v", err)
                }
                cachedVal = newVal
            }
        case <-ctx.Done():
            log.Printf("rountine: %v", ctx.Err())
        }
        cancel()
        log.Printf("sleeping for %s", sleep)
        time.Sleep(sleep)
    }
}

func main() {
    log.SetPrefix(os.Args[0] + ": ")
    log.SetFlags(0)
    var timer time.Duration
    flag.DurationVar(&timer, "t", time.Hour, "interval between each api request")
    flag.Parse()
    routine(timer)
}
natea123 commented 4 years ago

Below is how im unmarshaling in my current working implementation of the API:

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)

func main() {

    var country string

    flag.StringVar(&country, "country", "", "specify country for COVID case stats")
    flag.Parse()

    type Resp struct {
        Country   string
        Cases     int
        Deaths    int
        Recovered int
    }

    url := "https://corona.lmao.ninja/countries?sort=country"
    method := "GET"

    client := &http.Client{}
    req, err := http.NewRequest(method, url, nil)

    if err != nil {
        fmt.Println(err)
    }
    res, err := client.Do(req)
    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)

    var resp []Resp
    json.Unmarshal([]byte(body), &resp)
}
renanbastos93 commented 4 years ago

Working on adding the new API to the application with minimal change but running into error

json: cannot unmarshal array into Go value of type struct { Data main.LastValues "json:"data"" }

Below is my current implementation, running into issues unmarshaling the data, any insight would be appreciated:

package main

import (
  "context"
  "encoding/json"
  "flag"
  "fmt"
  "log"
  "net/http"
  "os"
  "time"

  "github.com/gen2brain/beeep"
)

// Exported ...
const (
  IMG string = "https://static.poder360.com.br/2020/03/23312-868x644.png"
  URL        = "https://corona.lmao.ninja/countries?sort=country"
)

// LastValues ...
type LastValues struct {
  Confirmed int `json:"cases"`
  Deaths    int `json:"deaths"`
  Recovered int `json:"recovered"`
}

func (l LastValues) String() string {
  return fmt.Sprintf("Confirmed: %d, Deaths: %d, Recovered: %d", l.Confirmed, l.Deaths, l.Recovered)
}

// fetchCOVID19Data ...
func fetchCOVID19Data(ctx context.Context, req *http.Request) <-chan LastValues {
  ch := make(chan LastValues)
  go func() {
      var r struct {
          Data LastValues `json:"data"`
      }
      body, err := http.DefaultClient.Do(req)
      if err != nil {
          log.Printf("fetchCOVID19Data: %v", err)
          return
      }
      defer body.Body.Close()
      err = json.NewDecoder(body.Body).Decode(&r)
      if err != nil {
          log.Printf("fetchCOVID19Data: %v", err)
          return
      }

      select {
      case ch <- LastValues{r.Data.Confirmed, r.Data.Deaths, r.Data.Recovered}:
      case <-ctx.Done():
      }
  }()
  return ch
}

func routine(sleep time.Duration) {
  cachedVal := LastValues{}
  const timeout = time.Second * 2
  for {
      ctx, cancel := context.WithTimeout(context.Background(), timeout)
      req, err := http.NewRequestWithContext(ctx, "GET", URL, nil)
      if err != nil {
          panic("internal error - misuse of NewRequestWithContext")
      }
      select {
      case newVal := <-fetchCOVID19Data(ctx, req):
          if cachedVal != newVal {
              err := beeep.Alert("COVID-19 Brazil", newVal.String(), IMG)
              if err != nil {
                  log.Printf("rountine: %v", err)
              }
              cachedVal = newVal
          }
      case <-ctx.Done():
          log.Printf("rountine: %v", ctx.Err())
      }
      cancel()
      log.Printf("sleeping for %s", sleep)
      time.Sleep(sleep)
  }
}

func main() {
  log.SetPrefix(os.Args[0] + ": ")
  log.SetFlags(0)
  var timer time.Duration
  flag.DurationVar(&timer, "t", time.Hour, "interval between each api request")
  flag.Parse()
  routine(timer)
}

Hello, I am sorry for you to wait,

Well, this error occurrent because the JSON in the body is not matched with struct LastValues then we need to change struct to another approach. According to your another example in comment https://github.com/renanbastos93/alertcovid19/issues/3#issuecomment-606308182

Like this:

type LastValue struct {
    Country   string `json:"country"`
    Cases     int    `json:"cases"`
    Deaths    int    `json:"deaths"`
    Recovered int    `json:"recovered"`
}
renanbastos93 commented 4 years ago

Both examples sound good. So I suggest we create the filter that compares country case is different to Brazil use Novel API else use currently API when to use Novel we can get geolocation from client to get country based this.

@natea123 What do you think?