initial
This commit is contained in:
commit
44b915c54b
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
example
|
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@ -0,0 +1,37 @@
|
||||
# First stage: Use the golangci-lint image to run the linter
|
||||
FROM golangci/golangci-lint:latest as lint
|
||||
|
||||
# Set the Current Working Directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the go.mod file and the rest of the application code
|
||||
COPY go.mod ./
|
||||
COPY . .
|
||||
|
||||
# Run golangci-lint
|
||||
RUN golangci-lint run
|
||||
|
||||
# Second stage: Use the official Golang image to run tests
|
||||
FROM golang:1.22 as test
|
||||
|
||||
# Set the Current Working Directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the go.mod file and the rest of the application code
|
||||
COPY go.mod ./
|
||||
COPY . .
|
||||
|
||||
# Run tests
|
||||
RUN go test -v ./...
|
||||
|
||||
# Final stage: Combine the linting and testing stages
|
||||
FROM golang:1.22 as final
|
||||
|
||||
# Ensure that the linting stage succeeded
|
||||
WORKDIR /app
|
||||
COPY --from=lint /app .
|
||||
COPY --from=test /app .
|
||||
|
||||
# Set the final CMD to something minimal since we only needed to verify lint and tests during build
|
||||
CMD ["echo", "Build and tests passed successfully!"]
|
||||
|
13
LICENSE
Normal file
13
LICENSE
Normal file
@ -0,0 +1,13 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
22
Makefile
Normal file
22
Makefile
Normal file
@ -0,0 +1,22 @@
|
||||
# Targets
|
||||
.PHONY: all run test clean
|
||||
|
||||
all: run
|
||||
|
||||
example: *.go ./cmd/example/*.go
|
||||
go build -o $@ ./cmd/example/main.go
|
||||
|
||||
run: example
|
||||
./example
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
clean:
|
||||
rm -f example
|
||||
|
||||
docker:
|
||||
docker build --progress plain .
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
102
hnblogs.go
Normal file
102
hnblogs.go
Normal file
@ -0,0 +1,102 @@
|
||||
package hnblogs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const BlogsURL = "https://raw.githubusercontent.com/surprisetalk/blogs.hn/main/blogs.json"
|
||||
|
||||
// Blog represents a single blog entry.
|
||||
type Blog struct {
|
||||
URL string `json:"url"`
|
||||
Title string `json:"title"`
|
||||
About string `json:"about"`
|
||||
Now string `json:"now"`
|
||||
Feed string `json:"feed"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
var (
|
||||
blogs []Blog
|
||||
fetchError error
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// FetchBlogs fetches the list of blogs and memoizes it in RAM.
|
||||
func FetchBlogs() ([]Blog, error) {
|
||||
once.Do(func() {
|
||||
resp, err := http.Get(BlogsURL)
|
||||
if err != nil {
|
||||
fetchError = fmt.Errorf("failed to fetch blogs: %v", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fetchError = fmt.Errorf("failed to fetch blogs: status code %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
var fetchedBlogs []Blog
|
||||
if err := json.NewDecoder(resp.Body).Decode(&fetchedBlogs); err != nil {
|
||||
fetchError = fmt.Errorf("failed to decode blogs JSON: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
blogs = fetchedBlogs
|
||||
})
|
||||
|
||||
return blogs, fetchError
|
||||
}
|
||||
|
||||
// GetBlogs returns the memoized list of blogs.
|
||||
func GetBlogs() ([]Blog, error) {
|
||||
return FetchBlogs()
|
||||
}
|
||||
|
||||
// RandomBlog returns a random blog from the list of blogs.
|
||||
func RandomBlog() (Blog, error) {
|
||||
blogs, err := GetBlogs()
|
||||
if err != nil {
|
||||
return Blog{}, err
|
||||
}
|
||||
|
||||
return blogs[rand.Intn(len(blogs))], nil
|
||||
}
|
||||
|
||||
// RandomBlogs returns n random blogs from the list of blogs.
|
||||
func RandomBlogs(n int) ([]Blog, error) {
|
||||
blogs, err := GetBlogs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n <= 0 || n > len(blogs) {
|
||||
return nil, fmt.Errorf("invalid number of blogs requested")
|
||||
}
|
||||
|
||||
selected := make([]Blog, n)
|
||||
for i := range selected {
|
||||
selected[i] = blogs[rand.Intn(len(blogs))]
|
||||
}
|
||||
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// NthBlog returns the nth blog from the list of blogs.
|
||||
func NthBlog(n int) (Blog, error) {
|
||||
blogs, err := GetBlogs()
|
||||
if err != nil {
|
||||
return Blog{}, err
|
||||
}
|
||||
|
||||
if n < 0 || n >= len(blogs) {
|
||||
return Blog{}, fmt.Errorf("index out of range")
|
||||
}
|
||||
|
||||
return blogs[n], nil
|
||||
}
|
77
hnblogs_test.go
Normal file
77
hnblogs_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
package hnblogs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFetchBlogs(t *testing.T) {
|
||||
blogs, err := FetchBlogs()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if len(blogs) == 0 {
|
||||
t.Fatalf("Expected to fetch some blogs, got %d", len(blogs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBlogs(t *testing.T) {
|
||||
blogs, err := GetBlogs()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if len(blogs) == 0 {
|
||||
t.Fatalf("Expected to fetch some blogs, got %d", len(blogs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomBlog(t *testing.T) {
|
||||
blog, err := RandomBlog()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if blog.URL == "" {
|
||||
t.Fatalf("Expected a valid blog, got an empty URL")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomBlogs(t *testing.T) {
|
||||
n := 5
|
||||
randomBlogs, err := RandomBlogs(n)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if len(randomBlogs) != n {
|
||||
t.Fatalf("Expected %d random blogs, got %d", n, len(randomBlogs))
|
||||
}
|
||||
|
||||
for _, blog := range randomBlogs {
|
||||
if blog.URL == "" {
|
||||
t.Fatalf("Expected a valid blog, got an empty URL")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNthBlog(t *testing.T) {
|
||||
blogs, err := GetBlogs()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
blog, err := NthBlog(5)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if blog.URL != blogs[5].URL {
|
||||
t.Fatalf("Expected %s, got %s", blogs[5].URL, blog.URL)
|
||||
}
|
||||
|
||||
_, err = NthBlog(len(blogs))
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for out-of-range index, got none")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user