2021-11-22 02:39:25 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2021-11-22 15:05:03 +00:00
|
|
|
"net/url"
|
2023-03-06 16:46:26 +00:00
|
|
|
"sort"
|
2021-11-22 02:39:25 +00:00
|
|
|
"strconv"
|
2021-11-22 15:05:03 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2021-11-22 02:39:25 +00:00
|
|
|
|
2021-11-22 15:05:03 +00:00
|
|
|
"github.com/gorilla/feeds"
|
2021-11-22 02:39:25 +00:00
|
|
|
"github.com/gorilla/mux"
|
2021-11-22 15:05:03 +00:00
|
|
|
"github.com/pkg/errors"
|
2024-04-15 13:08:40 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2021-11-22 16:31:05 +00:00
|
|
|
|
2024-04-15 13:08:40 +00:00
|
|
|
"github.com/Luzifer/go-latestver/internal/badge"
|
2021-11-22 16:31:05 +00:00
|
|
|
"github.com/Luzifer/go-latestver/internal/config"
|
|
|
|
"github.com/Luzifer/go-latestver/internal/database"
|
|
|
|
"github.com/Luzifer/go-latestver/internal/fetcher"
|
2021-11-22 02:39:25 +00:00
|
|
|
)
|
|
|
|
|
2021-11-22 22:19:58 +00:00
|
|
|
type (
|
2023-03-18 16:38:05 +00:00
|
|
|
apiCatalogEntry struct {
|
2021-11-22 22:19:58 +00:00
|
|
|
database.CatalogEntry
|
|
|
|
database.CatalogMeta
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2021-11-22 23:14:23 +00:00
|
|
|
func buildFullURL(u *url.URL, _ error) string {
|
2021-11-22 15:05:03 +00:00
|
|
|
return strings.Join([]string{
|
|
|
|
strings.TrimRight(cfg.BaseURL, "/"),
|
|
|
|
strings.TrimLeft(u.String(), "/"),
|
|
|
|
}, "/")
|
|
|
|
}
|
|
|
|
|
2023-03-18 16:38:05 +00:00
|
|
|
func catalogEntryToAPICatalogEntry(ce database.CatalogEntry) (apiCatalogEntry, error) {
|
2021-11-29 15:52:51 +00:00
|
|
|
cm, err := storage.Catalog.GetMeta(&ce)
|
|
|
|
if err != nil {
|
2023-03-18 16:38:05 +00:00
|
|
|
return apiCatalogEntry{}, errors.Wrap(err, "fetching catalog meta")
|
2021-11-29 15:52:51 +00:00
|
|
|
}
|
|
|
|
|
2021-12-01 02:38:52 +00:00
|
|
|
for _, l := range fetcher.Get(ce.Fetcher).Links(ce.FetcherConfig) {
|
2021-11-29 15:52:51 +00:00
|
|
|
var found bool
|
|
|
|
for _, el := range ce.Links {
|
|
|
|
if l.Name == el.Name {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
ce.Links = append(ce.Links, l)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-18 16:38:05 +00:00
|
|
|
return apiCatalogEntry{CatalogEntry: ce, CatalogMeta: *cm}, nil
|
2021-11-29 15:52:51 +00:00
|
|
|
}
|
|
|
|
|
2024-04-15 13:08:40 +00:00
|
|
|
func handleBadge(w http.ResponseWriter, r *http.Request) {
|
2021-11-30 23:14:25 +00:00
|
|
|
var (
|
|
|
|
compare = r.FormValue("compare")
|
|
|
|
vars = mux.Vars(r)
|
|
|
|
name, tag = vars["name"], vars["tag"]
|
|
|
|
)
|
|
|
|
|
|
|
|
ce, err := configFile.CatalogEntryByTag(name, tag)
|
|
|
|
if errors.Is(err, config.ErrCatalogEntryNotFound) {
|
|
|
|
http.Error(w, "Not found", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cm, err := storage.Catalog.GetMeta(&ce)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Unable to fetch catalog data", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
color := "green"
|
|
|
|
if compare != "" && compare != cm.CurrentVersion {
|
|
|
|
color = "red"
|
|
|
|
}
|
|
|
|
|
2024-04-15 13:08:40 +00:00
|
|
|
svg := badge.Create(ce.Key(), cm.CurrentVersion, color)
|
|
|
|
w.Header().Add("Content-Type", "image/svg+xml")
|
|
|
|
if _, err = w.Write(svg); err != nil {
|
|
|
|
logrus.WithError(err).Error("writing SVG response")
|
2021-11-30 23:14:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-22 02:39:25 +00:00
|
|
|
func handleCatalogGet(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var (
|
|
|
|
vars = mux.Vars(r)
|
|
|
|
name, tag = vars["name"], vars["tag"]
|
|
|
|
)
|
|
|
|
|
|
|
|
ce, err := configFile.CatalogEntryByTag(name, tag)
|
|
|
|
if errors.Is(err, config.ErrCatalogEntryNotFound) {
|
|
|
|
http.Error(w, "Not found", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-29 15:52:51 +00:00
|
|
|
ae, err := catalogEntryToAPICatalogEntry(ce)
|
2021-11-22 02:39:25 +00:00
|
|
|
if err != nil {
|
2024-04-15 13:08:40 +00:00
|
|
|
logrus.WithError(err).Error("Unable to fetch catalog data")
|
2021-11-29 15:52:51 +00:00
|
|
|
http.Error(w, "Unable to fetch catalog data", http.StatusInternalServerError)
|
2021-11-22 02:39:25 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2021-11-29 15:52:51 +00:00
|
|
|
if err = json.NewEncoder(w).Encode(ae); err != nil {
|
2024-04-15 13:08:40 +00:00
|
|
|
logrus.WithError(err).Error("Unable to encode catalog entry")
|
2021-11-22 02:39:25 +00:00
|
|
|
http.Error(w, "Unable to encode catalog meta", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleCatalogGetVersion(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var (
|
|
|
|
vars = mux.Vars(r)
|
|
|
|
name, tag = vars["name"], vars["tag"]
|
|
|
|
)
|
|
|
|
|
|
|
|
ce, err := configFile.CatalogEntryByTag(name, tag)
|
|
|
|
if errors.Is(err, config.ErrCatalogEntryNotFound) {
|
|
|
|
http.Error(w, "Not found", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cm, err := storage.Catalog.GetMeta(&ce)
|
|
|
|
if err != nil {
|
2024-04-15 13:08:40 +00:00
|
|
|
logrus.WithError(err).Error("Unable to fetch catalog meta")
|
2021-11-22 02:39:25 +00:00
|
|
|
http.Error(w, "Unable to fetch catalog meta", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
fmt.Fprint(w, cm.CurrentVersion)
|
|
|
|
}
|
|
|
|
|
2023-03-18 16:38:05 +00:00
|
|
|
func handleCatalogList(w http.ResponseWriter, _ *http.Request) {
|
|
|
|
out := make([]apiCatalogEntry, len(configFile.Catalog))
|
2021-11-22 02:39:25 +00:00
|
|
|
|
|
|
|
for i := range configFile.Catalog {
|
|
|
|
ce := configFile.Catalog[i]
|
|
|
|
|
2021-11-29 15:52:51 +00:00
|
|
|
ae, err := catalogEntryToAPICatalogEntry(ce)
|
2021-11-22 02:39:25 +00:00
|
|
|
if err != nil {
|
2024-04-15 13:08:40 +00:00
|
|
|
logrus.WithError(err).Error("Unable to fetch catalog data")
|
2021-11-29 15:52:51 +00:00
|
|
|
http.Error(w, "Unable to fetch catalog data", http.StatusInternalServerError)
|
2021-11-22 02:39:25 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-29 15:52:51 +00:00
|
|
|
out[i] = ae
|
2021-11-22 02:39:25 +00:00
|
|
|
}
|
|
|
|
|
2023-03-06 16:46:26 +00:00
|
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Key() < out[j].Key() })
|
|
|
|
|
2021-11-22 02:39:25 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
if err := json.NewEncoder(w).Encode(out); err != nil {
|
2024-04-15 13:08:40 +00:00
|
|
|
logrus.WithError(err).Error("Unable to encode catalog entry list")
|
2021-11-22 02:39:25 +00:00
|
|
|
http.Error(w, "Unable to encode catalog meta", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleLog(w http.ResponseWriter, r *http.Request) {
|
2021-11-22 15:05:03 +00:00
|
|
|
logs, err := prepareLogForRequest(r)
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
// This is fine
|
|
|
|
|
|
|
|
case config.ErrCatalogEntryNotFound:
|
|
|
|
http.Error(w, "Not found", http.StatusNotFound)
|
|
|
|
|
|
|
|
default:
|
|
|
|
http.Error(w, "Unable to fetch logs", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
if err = json.NewEncoder(w).Encode(logs); err != nil {
|
2024-04-15 13:08:40 +00:00
|
|
|
logrus.WithError(err).Error("Unable to encode logs")
|
2021-11-22 15:05:03 +00:00
|
|
|
http.Error(w, "Unable to encode logs", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleLogFeed(w http.ResponseWriter, r *http.Request) {
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
|
|
|
logs, err := prepareLogForRequest(r)
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
// This is fine
|
|
|
|
|
|
|
|
case config.ErrCatalogEntryNotFound:
|
|
|
|
http.Error(w, "Not found", http.StatusNotFound)
|
|
|
|
|
|
|
|
default:
|
|
|
|
http.Error(w, "Unable to fetch logs", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
feed := &feeds.Feed{
|
|
|
|
Description: "Generated by go-latestver: https://github.com/Luzifer/go-latestver",
|
2021-11-22 23:14:23 +00:00
|
|
|
Link: &feeds.Link{Href: buildFullURL(router.Get("catalog").URL())},
|
|
|
|
Title: "Latestver Update Log",
|
|
|
|
}
|
|
|
|
|
|
|
|
if vars["name"] != "" {
|
|
|
|
feed.Title = fmt.Sprintf("Latestver Update Log of %s:%s", vars["name"], vars["tag"])
|
|
|
|
feed.Link.Href = buildFullURL(router.Get("catalog-entry").URL("name", vars["name"], "tag", vars["tag"]))
|
2021-11-22 15:05:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, le := range logs {
|
|
|
|
feed.Add(&feeds.Item{
|
|
|
|
Created: le.Timestamp.UTC(),
|
|
|
|
Description: fmt.Sprintf("%s:%s updated to version %s from %s", le.CatalogName, le.CatalogTag, le.VersionTo, le.VersionFrom),
|
|
|
|
Id: fmt.Sprintf("%s:%s-%s", le.CatalogName, le.CatalogTag, le.Timestamp.UTC().Format(time.RFC3339)),
|
2021-11-22 23:14:23 +00:00
|
|
|
Link: &feeds.Link{Href: buildFullURL(router.Get("catalog-entry").URL("name", le.CatalogName, "tag", le.CatalogTag))},
|
2021-11-22 15:05:03 +00:00
|
|
|
Title: fmt.Sprintf("%s:%s %s", le.CatalogName, le.CatalogTag, le.VersionTo),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
|
|
|
|
if err = feed.WriteRss(w); err != nil {
|
2024-04-15 13:08:40 +00:00
|
|
|
logrus.WithError(err).Error("Unable to render RSS")
|
2021-11-22 15:05:03 +00:00
|
|
|
http.Error(w, "Unable to render RSS", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func prepareLogForRequest(r *http.Request) ([]database.LogEntry, error) {
|
2021-11-22 02:39:25 +00:00
|
|
|
var (
|
|
|
|
vars = mux.Vars(r)
|
|
|
|
name, tag = vars["name"], vars["tag"]
|
|
|
|
|
|
|
|
num, page = 25, 0
|
|
|
|
|
2021-12-01 02:38:52 +00:00
|
|
|
ce database.CatalogEntry
|
2021-11-22 02:39:25 +00:00
|
|
|
err error
|
|
|
|
logs []database.LogEntry
|
|
|
|
)
|
|
|
|
|
|
|
|
if v, err := strconv.Atoi(r.FormValue("num")); err == nil && v > 0 && v < 100 {
|
|
|
|
num = v
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, err := strconv.Atoi(r.FormValue("page")); err == nil && v >= 0 {
|
|
|
|
page = v
|
|
|
|
}
|
|
|
|
|
|
|
|
if name == "" && tag == "" {
|
|
|
|
logs, err = storage.Logs.List(num, page)
|
|
|
|
} else {
|
2021-12-01 02:38:52 +00:00
|
|
|
ce, err = configFile.CatalogEntryByTag(name, tag)
|
2021-11-22 02:39:25 +00:00
|
|
|
if errors.Is(err, config.ErrCatalogEntryNotFound) {
|
2021-11-22 15:05:03 +00:00
|
|
|
return nil, config.ErrCatalogEntryNotFound
|
2021-11-22 02:39:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
logs, err = storage.Logs.ListForCatalogEntry(&ce, num, page)
|
|
|
|
}
|
|
|
|
|
2021-11-22 15:05:03 +00:00
|
|
|
return logs, errors.Wrap(err, "listing log entries")
|
2021-11-22 02:39:25 +00:00
|
|
|
}
|