Initial commit
This commit is contained in:
22
LICENCE
Normal file
22
LICENCE
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT Licence
|
||||
|
||||
Copyright (c) 2019 Ayooluwa Isaiah
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
23
README.md
Normal file
23
README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# News Demo Application
|
||||
|
||||
Learn web development with Go. Live demo: https://freshman-news.herokuapp.com/
|
||||
|
||||
Here's what the completed application looks like:
|
||||
|
||||

|
||||
|
||||
The code in this repo is meant to be a reference point for anyone following along with the [tutorial](https://freshman.tech/web-development-with-go/).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You need to have Go installed on your computer. The version used for the tutorial is **1.12.9**.
|
||||
|
||||
- Sign up for a [News API account](https://newsapi.org/register) and get your free API key.
|
||||
|
||||
## Get started
|
||||
|
||||
- Clone this repository to your filesystem.
|
||||
|
||||
- `cd` into it and run the following command: `go run main.go -apikey=<your news api key>` to start the server on port 3000.
|
||||
|
||||
- View http://localhost:3000 in your browser.
|
46
index.html
46
index.html
@@ -8,5 +8,51 @@
|
||||
<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" 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 .SearchKey "") and (eq .Results.TotalResults 0) }}
|
||||
<p>No results found for your query: <strong>{{ .SearchKey }}</strong>.</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<ul class="search-results">
|
||||
{{ range .Results.Articles }}
|
||||
<li class="news-article">
|
||||
<div>
|
||||
<a target="_blank" rel="noreferrer noopener" href="{{.URL}}">
|
||||
<h3 class="title">{{.Title }}</h3>
|
||||
</a>
|
||||
<p class="description">{{ .Description }}</p>
|
||||
<div class="metadata">
|
||||
<p class="source">{{ .Source.Name }}</p>
|
||||
<time class="published-date">{{ .FormatPublishedDate }}</time>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</html>
|
||||
|
152
main.go
152
main.go
@@ -1 +1,153 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
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
|
||||
NextPage int
|
||||
TotalPages int
|
||||
Results 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
|
||||
}
|
||||
|
||||
return s.NextPage - 1
|
||||
}
|
||||
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
tpl.Execute(w, nil)
|
||||
}
|
||||
|
||||
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 {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&search.Results)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
search.TotalPages = int(math.Ceil(float64(search.Results.TotalResults / pageSize)))
|
||||
if ok := !search.IsLastPage(); ok {
|
||||
search.NextPage++
|
||||
}
|
||||
|
||||
err = tpl.Execute(w, search)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
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")
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
fs := http.FileServer(http.Dir("assets"))
|
||||
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
|
||||
|
||||
mux.HandleFunc("/search", searchHandler)
|
||||
mux.HandleFunc("/", indexHandler)
|
||||
http.ListenAndServe(":"+port, mux)
|
||||
}
|
||||
|
Reference in New Issue
Block a user