Update code

This commit is contained in:
Ayooluwa Isaiah
2020-11-18 11:02:11 +01:00
parent 1909256285
commit 4ac2d8c782
6 changed files with 214 additions and 153 deletions

View File

@@ -1 +1 @@
web: bin/news-demo -apikey $NEWS_API_KEY
web: bin/news-demo

4
go.mod
View File

@@ -1,3 +1,5 @@
module github.com/freshman-tech/news-demo
go 1.14
go 1.15
require github.com/joho/godotenv v1.3.0

4
go.sum
View File

@@ -1,2 +1,2 @@
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/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=

View File

@@ -1,33 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>News App Demo</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<main>
<header>
<a class="logo" href="/">News Demo</a>
<form action="/search" method="GET">
<input autofocus class="search-input" value="{{ .SearchKey }}" placeholder="Enter a news topic" type="search" name="q">
</form>
<a href="https://github.com/freshman-tech/news-demo" class="button
github-button">View on Github</a>
</header>
<section class="container">
<div class="result-count">
{{ if (gt .Results.TotalResults 0)}}
<p>About <strong>{{ .Results.TotalResults }}</strong> results were found. You are on page <strong>{{ .CurrentPage }}</strong> of <strong> {{ .TotalPages }}</strong>.</p>
{{ else if and (ne .SearchKey "") (eq .Results.TotalResults 0) }}
<p>No results found for your query: <strong>{{ .SearchKey }}</strong>.</p>
{{ end }}
</div>
<ul class="search-results">
{{ range .Results.Articles }}
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>News App Demo</title>
<link rel="stylesheet" href="/assets/style.css" />
</head>
<body>
<main>
<header>
<a class="logo" href="/">News Demo</a>
<form action="/search" method="GET">
<input
autofocus
class="search-input"
value="{{ .Query }}"
placeholder="Enter a news topic"
type="search"
name="q"
/>
</form>
<a
href="https://github.com/freshman-tech/news"
class="button github-button"
>View on Github</a
>
</header>
<section class="container">
<div class="result-count">
{{ if (gt .Results.TotalResults 0)}}
<p>
About <strong>{{ .Results.TotalResults }}</strong> results were
found. You are on page <strong>{{ .CurrentPage }}</strong> of
<strong> {{ .TotalPages }}</strong
>.
</p>
{{ else if (ne .Query "") and (eq .Results.TotalResults 0) }}
<p>
No results found for your query: <strong>{{ .Query }}</strong
>.
</p>
{{ end }}
</div>
<ul class="search-results">
<!-- prettier-ignore -->
{{ range.Results.Articles }}
<li class="news-article">
<div>
<a target="_blank" rel="noreferrer noopener" href="{{.URL}}">
@@ -39,20 +57,28 @@
<time class="published-date">{{ .FormatPublishedDate }}</time>
</div>
</div>
<img class="article-image" src="{{ .URLToImage }}">
<img class="article-image" src="{{ .URLToImage }}" />
</li>
{{ end }}
</ul>
<div class="pagination">
{{ if (gt .NextPage 2) }}
<a href="/search?q={{ .SearchKey }}&page={{ .PreviousPage }}" class="button previous-page">Previous</a>
{{ end }}
{{ if (ne .IsLastPage true) }}
<a href="/search?q={{ .SearchKey }}&page={{ .NextPage }}" class="button next-page">Next</a>
{{ end }}
</div>
</section>
</main>
</body>
<!-- prettier-ignore -->
{{ end }}
</ul>
<div class="pagination">
{{ if (gt .NextPage 2) }}
<a
href="/search?q={{ .Query }}&page={{ .PreviousPage }}"
class="button previous-page"
>Previous</a
>
{{ end }}
{{ if (ne .IsLastPage true) }}
<a
href="/search?q={{ .Query }}&page={{ .NextPage }}"
class="button next-page"
>Next</a
>
{{ end }}
</div>
</section>
</main>
</body>
</html>

174
main.go
View File

@@ -1,9 +1,7 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"bytes"
"html/template"
"log"
"math"
@@ -12,53 +10,24 @@ import (
"os"
"strconv"
"time"
"github.com/freshman-tech/news-demo/news"
"github.com/joho/godotenv"
)
var tpl = template.Must(template.ParseFiles("index.html"))
var apiKey *string
type Source struct {
ID interface{} `json:"id"`
Name string `json:"name"`
}
type Article struct {
Source Source `json:"source"`
Author string `json:"author"`
Title string `json:"title"`
Description string `json:"description"`
URL string `json:"url"`
URLToImage string `json:"urlToImage"`
PublishedAt time.Time `json:"publishedAt"`
Content string `json:"content"`
}
func (a *Article) FormatPublishedDate() string {
year, month, day := a.PublishedAt.Date()
return fmt.Sprintf("%v %d, %d", month, day, year)
}
type Results struct {
Status string `json:"status"`
TotalResults int `json:"totalResults"`
Articles []Article `json:"articles"`
}
type Search struct {
SearchKey string
Query string
NextPage int
TotalPages int
Results Results
Results *news.Results
}
func (s *Search) IsLastPage() bool {
return s.NextPage >= s.TotalPages
}
func (s *Search) PreviousPage() int {
return s.CurrentPage() - 1
}
func (s *Search) CurrentPage() int {
if s.NextPage == 1 {
return s.NextPage
@@ -67,101 +36,94 @@ func (s *Search) CurrentPage() int {
return s.NextPage - 1
}
type NewsAPIError struct {
Status string `json:"status"`
Code string `json:"code"`
Message string `json:"message"`
func (s *Search) PreviousPage() int {
return s.CurrentPage() - 1
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, nil)
buf := &bytes.Buffer{}
err := tpl.Execute(buf, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf.WriteTo(w)
}
func searchHandler(w http.ResponseWriter, r *http.Request) {
u, err := url.Parse(r.URL.String())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
params := u.Query()
searchKey := params.Get("q")
page := params.Get("page")
if page == "" {
page = "1"
}
search := &Search{}
search.SearchKey = searchKey
next, err := strconv.Atoi(page)
if err != nil {
http.Error(w, "Unexpected server error", http.StatusInternalServerError)
return
}
search.NextPage = next
pageSize := 20
endpoint := fmt.Sprintf("https://newsapi.org/v2/everything?q=%s&pageSize=%d&page=%d&apiKey=%s&sortBy=publishedAt&language=en", url.QueryEscape(search.SearchKey), pageSize, search.NextPage, *apiKey)
resp, err := http.Get(endpoint)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
newError := &NewsAPIError{}
err := json.NewDecoder(resp.Body).Decode(newError)
func searchHandler(newsapi *news.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
u, err := url.Parse(r.URL.String())
if err != nil {
http.Error(w, "Unexpected server error", http.StatusInternalServerError)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Error(w, newError.Message, http.StatusInternalServerError)
return
}
params := u.Query()
searchQuery := params.Get("q")
page := params.Get("page")
if page == "" {
page = "1"
}
err = json.NewDecoder(resp.Body).Decode(&search.Results)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
results, err := newsapi.FetchEverything(searchQuery, page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
search.TotalPages = int(math.Ceil(float64(search.Results.TotalResults / pageSize)))
if ok := !search.IsLastPage(); ok {
search.NextPage++
}
nextPage, err := strconv.Atoi(page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = tpl.Execute(w, search)
if err != nil {
log.Println(err)
search := &Search{
Query: searchQuery,
NextPage: nextPage,
TotalPages: int(math.Ceil(float64(results.TotalResults / newsapi.PageSize))),
Results: results,
}
if ok := !search.IsLastPage(); ok {
search.NextPage++
}
buf := &bytes.Buffer{}
err = tpl.Execute(buf, search)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf.WriteTo(w)
}
}
func main() {
err := godotenv.Load()
if err != nil {
log.Println("Error loading .env file")
}
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
apiKey = flag.String("apikey", "", "Newsapi.org access key")
flag.Parse()
if *apiKey == "" {
log.Fatal("apiKey must be set")
apiKey := os.Getenv("NEWS_API_KEY")
if apiKey == "" {
log.Fatal("Env: apiKey must be set")
}
mux := http.NewServeMux()
myClient := &http.Client{Timeout: 10 * time.Second}
newsapi := news.NewClient(myClient, apiKey, 20)
fs := http.FileServer(http.Dir("assets"))
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
mux.HandleFunc("/search", searchHandler)
mux := http.NewServeMux()
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
mux.HandleFunc("/search", searchHandler(newsapi))
mux.HandleFunc("/", indexHandler)
http.ListenAndServe(":"+port, mux)
}

71
news/news.go Normal file
View File

@@ -0,0 +1,71 @@
package news
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
)
type Article struct {
Source struct {
ID interface{} `json:"id"`
Name string `json:"name"`
} `json:"source"`
Author string `json:"author"`
Title string `json:"title"`
Description string `json:"description"`
URL string `json:"url"`
URLToImage string `json:"urlToImage"`
PublishedAt time.Time `json:"publishedAt"`
Content string `json:"content"`
}
func (a *Article) FormatPublishedDate() string {
year, month, day := a.PublishedAt.Date()
return fmt.Sprintf("%v %d, %d", month, day, year)
}
type Results struct {
Status string `json:"status"`
TotalResults int `json:"totalResults"`
Articles []Article `json:"articles"`
}
type Client struct {
http *http.Client
key string
PageSize int
}
func (c *Client) FetchEverything(query, page string) (*Results, error) {
endpoint := fmt.Sprintf("https://newsapi.org/v2/everything?q=%s&pageSize=%d&page=%s&apiKey=%s&sortBy=publishedAt&language=en", url.QueryEscape(query), c.PageSize, page, c.key)
resp, err := c.http.Get(endpoint)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(string(body))
}
res := &Results{}
return res, json.Unmarshal(body, res)
}
func NewClient(httpClient *http.Client, key string, pageSize int) *Client {
if pageSize > 100 {
pageSize = 100
}
return &Client{httpClient, key, pageSize}
}