blob: ff01b2d8e0901a62c6f997f6aa801b100c8518da [file] [log] [blame]
Serge Bazanskia7674672021-05-30 21:09:34 +00001package main
2
3import (
4 "flag"
5 "fmt"
Serge Bazanski717aad42021-07-11 16:03:43 +00006 "math/rand"
Serge Bazanskia7674672021-05-30 21:09:34 +00007 "mime"
8 "net/http"
Serge Bazanski1f062302021-06-01 22:24:34 +00009 "regexp"
Serge Bazanskia7674672021-05-30 21:09:34 +000010 "strings"
Serge Bazanski3c9092a2021-05-30 23:15:20 +000011 "sync"
Serge Bazanski717aad42021-07-11 16:03:43 +000012 "time"
Serge Bazanskia7674672021-05-30 21:09:34 +000013
14 "code.hackerspace.pl/hscloud/go/mirko"
15 "github.com/golang/glog"
16
Serge Bazanski717aad42021-07-11 16:03:43 +000017 "code.hackerspace.pl/hscloud/hswaw/site/calendar"
Serge Bazanskia7674672021-05-30 21:09:34 +000018 "code.hackerspace.pl/hscloud/hswaw/site/static"
19)
20
21var (
22 flagSitePublic string
23)
24
25type service struct {
Serge Bazanski3c9092a2021-05-30 23:15:20 +000026 // feeds is a map from atom feed name to atom feed. This is updated by a
27 // background worker.
28 feeds map[string]*atomFeed
29 // feedsMu locks the feeds field.
30 feedsMu sync.RWMutex
Serge Bazanski717aad42021-07-11 16:03:43 +000031
32 // events is a list of upcoming events, sorted so the first event is the
33 // one that will happen the soonests.
34 events []*calendar.UpcomingEvent
35 // eventsMu locks the events field.
36 eventsMu sync.RWMutex
Serge Bazanskia7674672021-05-30 21:09:34 +000037}
38
39func main() {
40 flag.StringVar(&flagSitePublic, "site_public", "0.0.0.0:8080", "Address at which to serve public HTTP requests")
41 flag.Parse()
42
Serge Bazanski717aad42021-07-11 16:03:43 +000043 rand.Seed(time.Now().UnixNano())
44
Serge Bazanskia7674672021-05-30 21:09:34 +000045 mi := mirko.New()
46 if err := mi.Listen(); err != nil {
47 glog.Exitf("Listen failed: %v", err)
48 }
49
50 s := &service{}
Serge Bazanski3c9092a2021-05-30 23:15:20 +000051 go s.feedWorker(mi.Context())
Serge Bazanski717aad42021-07-11 16:03:43 +000052 go s.eventsWorker(mi.Context())
Serge Bazanskia7674672021-05-30 21:09:34 +000053
54 mux := http.NewServeMux()
55 s.registerHTTP(mux)
56
57 go func() {
58 glog.Infof("Listening for public HTTP at %v", flagSitePublic)
59 if err := http.ListenAndServe(flagSitePublic, mux); err != nil {
60 glog.Exit(err)
61 }
62 }()
63
64 if err := mi.Serve(); err != nil {
65 glog.Exitf("Serve failed: %v", err)
66 }
67
68 <-mi.Done()
69}
70
Serge Bazanski1f062302021-06-01 22:24:34 +000071var (
72 // staticRoutes define the resolution of static file paths into assets
73 // built into //hswaw/site/static, whose names correspond to their origin
74 // within the Bazel workspace.
75 // The regexp will be matched against the normalized within the URL. If it
76 // matches, its first group/submatch will be used to format the string
77 // corresponding to this regexp, and that string will be then used to
78 // retrieve a path embedded within //hswaw/site/static:static
79 // (go_embed_data).
80 //
81 // To see paths available within that go_embed_data, you can do:
82 // $ bazel build //hswaw/site/static:static
83 // $ grep -A100 'Data =' bazel-bin/hswaw/site/static/static.go
84 staticRoutes = map[*regexp.Regexp]string{
Serge Bazanski19c8b602022-02-01 09:36:47 +000085 regexp.MustCompile(`^static/site/(.+)$`): "hswaw/site/static/%s",
86 regexp.MustCompile(`^static/leaflet/(.+)$`): "external/com_npmjs_leaflet/package/dist/%s",
87 regexp.MustCompile(`^static/gfonts/([a-z]+)/([0-9]+)\.ttf$`): "external/com_gstatic_fonts_%s_%s/file/font.ttf",
Serge Bazanski1f062302021-06-01 22:24:34 +000088 }
89)
90
91// handleHTTPStatic uses staticRoutes to serve static files embedded within
92// //hswaw/site/static.
Serge Bazanskia7674672021-05-30 21:09:34 +000093func (s *service) handleHTTPStatic(w http.ResponseWriter, r *http.Request) {
94 path := strings.TrimPrefix(r.URL.Path, "/")
Serge Bazanski1f062302021-06-01 22:24:34 +000095 for from, to := range staticRoutes {
96 match := from.FindStringSubmatch(path)
97 if match == nil {
98 continue
99 }
Serge Bazanski19c8b602022-02-01 09:36:47 +0000100 var args []interface{}
101 for _, m := range match[1:] {
102 args = append(args, m)
103 }
104 if len(args) != strings.Count(to, "%s") {
105 http.NotFound(w, r)
106 return
107 }
108 to := fmt.Sprintf(to, args...)
Serge Bazanski1f062302021-06-01 22:24:34 +0000109 data, ok := static.Data[to]
110 if !ok {
111 continue
112 }
Serge Bazanskia7674672021-05-30 21:09:34 +0000113 parts := strings.Split(path, ".")
114 ext := fmt.Sprintf(".%s", parts[len(parts)-1])
115 t := mime.TypeByExtension(ext)
116 w.Header().Set("Content-Type", t)
117 w.Write(data)
Serge Bazanski1f062302021-06-01 22:24:34 +0000118 return
119
Serge Bazanskia7674672021-05-30 21:09:34 +0000120 }
Serge Bazanski1f062302021-06-01 22:24:34 +0000121 http.NotFound(w, r)
Serge Bazanskia7674672021-05-30 21:09:34 +0000122}
123
124func (s *service) registerHTTP(mux *http.ServeMux) {
125 mux.HandleFunc("/static/", s.handleHTTPStatic)
Serge Bazanski11b276d2021-07-11 23:49:55 +0000126 mux.HandleFunc("/spaceapi", s.handleSpaceAPI)
Serge Bazanski717aad42021-07-11 16:03:43 +0000127 mux.HandleFunc("/events.json", s.handleJSONEvents)
128 mux.HandleFunc("/event/", s.handleEvent)
Serge Bazanski15b5bc12022-11-06 17:04:16 +0000129 mux.HandleFunc("/robots.txt", s.handleRobotsTxt)
Serge Bazanski4d7b2f02021-05-31 22:33:51 +0000130 mux.HandleFunc("/", s.handleIndex)
Serge Bazanskia7674672021-05-30 21:09:34 +0000131}