main.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: Apache-2.0
  4
  5package main
  6
  7import (
  8	"encoding/csv"
  9	"errors"
 10	"fmt"
 11	"log"
 12	"net/http"
 13	"os"
 14	"sort"
 15	"strings"
 16	"time"
 17
 18	"github.com/microcosm-cc/bluemonday"
 19)
 20
 21type (
 22	Model struct {
 23		Projects []project
 24	}
 25
 26	project struct {
 27		URL      string
 28		Name     string
 29		Forge    string
 30		Running  string
 31		Releases []release
 32	}
 33
 34	release struct {
 35		Tag     string
 36		Content string
 37		URL     string
 38		Date    time.Time
 39	}
 40)
 41
 42var (
 43	req           = make(chan struct{})
 44	manualRefresh = make(chan struct{})
 45	res           = make(chan []project)
 46	m             = Model{
 47		Projects: []project{},
 48	}
 49	bmUGC    = bluemonday.UGCPolicy()
 50	bmStrict = bluemonday.StrictPolicy()
 51)
 52
 53func main() {
 54	file, err := os.Open("projects.csv")
 55	if err != nil {
 56		log.Fatalln(err)
 57	}
 58	defer file.Close()
 59
 60	reader := csv.NewReader(file)
 61
 62	records, err := reader.ReadAll()
 63	if err != nil {
 64		log.Fatalln(err)
 65	}
 66
 67	m.Projects = []project{}
 68	if len(records) > 0 {
 69		for i, record := range records {
 70			if i == 0 {
 71				continue
 72			}
 73			m.Projects = append(m.Projects, project{
 74				URL:      record[0],
 75				Name:     record[1],
 76				Forge:    record[2],
 77				Running:  record[3],
 78				Releases: []release{},
 79			})
 80		}
 81	}
 82
 83	go refreshLoop(manualRefresh, req, res)
 84
 85	mux := http.NewServeMux()
 86
 87	httpServer := &http.Server{
 88		Addr:    "0.0.0.0:1337",
 89		Handler: mux,
 90	}
 91
 92	mux.HandleFunc("/", rootHandler)
 93	mux.HandleFunc("/static", staticHandler)
 94	mux.HandleFunc("/new", newHandler)
 95
 96	if err := httpServer.ListenAndServe(); errors.Is(err, http.ErrServerClosed) {
 97		log.Println("Web server closed")
 98	} else {
 99		log.Fatalln(err)
100	}
101}
102
103func refreshLoop(manualRefresh, req chan struct{}, res chan []project) {
104	ticker := time.NewTicker(time.Second * 3600)
105
106	fetch := func() []project {
107		projects := make([]project, len(m.Projects))
108		copy(projects, m.Projects)
109		for i, project := range projects {
110			project, err := getReleases(project)
111			if err != nil {
112				fmt.Println(err)
113				continue
114			}
115			projects[i] = project
116		}
117		sort.Slice(projects, func(i, j int) bool { return strings.ToLower(projects[i].Name) < strings.ToLower(projects[j].Name) })
118		return projects
119	}
120
121	projects := fetch()
122
123	for {
124		select {
125		case <-ticker.C:
126			projects = fetch()
127		case <-manualRefresh:
128			ticker.Reset(time.Second * 3600)
129			projects = fetch()
130		case <-req:
131			projectsCopy := make([]project, len(projects))
132			copy(projectsCopy, projects)
133			res <- projectsCopy
134		}
135	}
136}