Added: downloading and returning wikipedia articles
This commit is contained in:
78
api/article/handler.go
Normal file
78
api/article/handler.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"scrap/api/httpio"
|
||||
"scrap/internal/article"
|
||||
"scrap/internal/db"
|
||||
)
|
||||
|
||||
func ArticleDownloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
dbInstance := db.GetInstance()
|
||||
txRepo := db.NewTxRepository(dbInstance)
|
||||
articleRepo := article.NewArticleRepository()
|
||||
|
||||
service := article.NewArticleService(txRepo, articleRepo)
|
||||
if err := service.DownloadArticles(); err != nil {
|
||||
switch err {
|
||||
default:
|
||||
httpio.RaiseOnlyStatusCode(w, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func ArticleQueryHandler(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := httpio.ParseURLQuery[ArticleQueryRequest](
|
||||
r,
|
||||
httpio.URLQueryKey[string]("title"),
|
||||
)
|
||||
if err != nil {
|
||||
httpio.RaiseOnlyStatusCode(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if httpErr := body.Validate(); httpErr != nil {
|
||||
httpErr.Raise(w)
|
||||
return
|
||||
}
|
||||
|
||||
dbInstance := db.GetInstance()
|
||||
txRepo := db.NewTxRepository(dbInstance)
|
||||
articleRepo := article.NewArticleRepository()
|
||||
|
||||
service := article.NewArticleService(txRepo, articleRepo)
|
||||
|
||||
articleQueryData := article.ArticleQueryDTO{
|
||||
Title: body.Title,
|
||||
}
|
||||
|
||||
articles, err := service.QueryArticles(articleQueryData)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case article.ErrArticleTitleInvalidLength:
|
||||
ErrHttpArticleTitleInvalidLength.Raise(w)
|
||||
default:
|
||||
httpio.RaiseOnlyStatusCode(w, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
articlesOut := make([]ArticleResponse, 0, len(articles))
|
||||
for _, a := range articles {
|
||||
ar := ArticleResponse{
|
||||
Uuid: a.Uuid,
|
||||
Title: a.Title,
|
||||
Content: a.Content,
|
||||
}
|
||||
|
||||
articlesOut = append(articlesOut, ar)
|
||||
}
|
||||
|
||||
if err = ArticleQueryResponse(articlesOut).Return(w, http.StatusOK); err != nil {
|
||||
httpio.RaiseOnlyStatusCode(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
14
api/article/httperror.go
Normal file
14
api/article/httperror.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"scrap/api/httpio"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrHttpArticleTitleInvalidLength = httpio.HTTPError{
|
||||
StatusCode: http.StatusBadRequest,
|
||||
ErrorCode: "ARTICLE_TITLE_LENGTH",
|
||||
Message: "Invalid title length.",
|
||||
}
|
||||
)
|
||||
16
api/article/request.go
Normal file
16
api/article/request.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package article
|
||||
|
||||
import "scrap/api/httpio"
|
||||
|
||||
type ArticleQueryRequest struct {
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
func (a ArticleQueryRequest) Validate() *httpio.HTTPError {
|
||||
titleLength := len(a.Title)
|
||||
if titleLength < 1 || titleLength > 255 {
|
||||
return &ErrHttpArticleTitleInvalidLength
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
15
api/article/response.go
Normal file
15
api/article/response.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package article
|
||||
|
||||
import "scrap/api/httpio"
|
||||
|
||||
type ArticleResponse struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func ArticleQueryResponse(articles []ArticleResponse) httpio.ResponseIO {
|
||||
return httpio.ResponseIO{
|
||||
"articles": articles,
|
||||
}
|
||||
}
|
||||
24
api/httpio/httperror.go
Normal file
24
api/httpio/httperror.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package httpio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type HTTPError struct {
|
||||
StatusCode int `json:"-"`
|
||||
ErrorCode string `json:"error-code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (h HTTPError) Raise(w http.ResponseWriter) {
|
||||
jsonBytes, _ := json.Marshal(h)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(h.StatusCode)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
|
||||
func RaiseOnlyStatusCode(w http.ResponseWriter, code int) {
|
||||
http.Error(w, "", code)
|
||||
}
|
||||
38
api/httpio/request.go
Normal file
38
api/httpio/request.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package httpio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type IRequestIO interface {
|
||||
// Validates the received request.
|
||||
Validate() *HTTPError
|
||||
}
|
||||
|
||||
// Parses request body into the provided struct.
|
||||
// Throws an error if the body could not be parsed.
|
||||
func ParseRequestBody[T IRequestIO](r *http.Request) (*T, error) {
|
||||
requestBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !json.Valid(requestBytes) {
|
||||
return nil, errors.New("invalid JSON format")
|
||||
}
|
||||
|
||||
var req T
|
||||
err = json.Unmarshal(requestBytes, &req)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &req, nil
|
||||
|
||||
}
|
||||
21
api/httpio/response.go
Normal file
21
api/httpio/response.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package httpio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ResponseIO map[string]any
|
||||
|
||||
func (r ResponseIO) Return(w http.ResponseWriter, statusCode int) error {
|
||||
jsonBytes, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(jsonBytes)
|
||||
|
||||
return nil
|
||||
}
|
||||
86
api/httpio/urlquery.go
Normal file
86
api/httpio/urlquery.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package httpio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type URLQueryValueType interface {
|
||||
string | int | float32 | float64 | bool
|
||||
}
|
||||
|
||||
type iURLQueryKeyType interface {
|
||||
GetKey() string
|
||||
}
|
||||
|
||||
type URLQueryKeyType[T URLQueryValueType] struct {
|
||||
Key string
|
||||
_ T
|
||||
}
|
||||
|
||||
func (u URLQueryKeyType[T]) GetKey() string { return u.Key }
|
||||
|
||||
func URLQueryKey[T URLQueryValueType](key string) iURLQueryKeyType {
|
||||
return URLQueryKeyType[T]{
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
func ParseURLQuery[T IRequestIO](r *http.Request, keys ...iURLQueryKeyType) (*T, error) {
|
||||
query := make(map[string]any, len(keys))
|
||||
|
||||
for _, key := range keys {
|
||||
queryValue := r.URL.Query().Get(key.GetKey())
|
||||
|
||||
if queryValue == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch key.(type) {
|
||||
case URLQueryKeyType[string]:
|
||||
query[key.GetKey()] = queryValue
|
||||
case URLQueryKeyType[int]:
|
||||
x, err := strconv.Atoi(queryValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query[key.GetKey()] = x
|
||||
case URLQueryKeyType[float32]:
|
||||
x, err := strconv.ParseFloat(queryValue, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query[key.GetKey()] = x
|
||||
case URLQueryKeyType[float64]:
|
||||
x, err := strconv.ParseFloat(queryValue, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query[key.GetKey()] = x
|
||||
case URLQueryKeyType[bool]:
|
||||
x, err := strconv.ParseBool(queryValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query[key.GetKey()] = x
|
||||
default:
|
||||
return nil, errors.New("unsupported URL query key type")
|
||||
}
|
||||
}
|
||||
|
||||
queryBytes, _ := json.Marshal(query)
|
||||
|
||||
var req T
|
||||
err := json.Unmarshal(queryBytes, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &req, nil
|
||||
}
|
||||
17
api/setup.go
Normal file
17
api/setup.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"scrap/api/article"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func Setup() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Get("/articles", article.ArticleQueryHandler)
|
||||
r.Get("/articles-download", article.ArticleDownloadHandler)
|
||||
|
||||
http.ListenAndServe(":8080", r)
|
||||
}
|
||||
Reference in New Issue
Block a user