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