Skip to content

Commit 381c6ff

Browse files
committed
feat: Add Mastodon handler to serverless function, move Luma handler into pkg
Signed-off-by: Felicitas Pojtinger <felicitas@pojtinger.com>
1 parent 3322c75 commit 381c6ff

4 files changed

Lines changed: 182 additions & 162 deletions

File tree

go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
module github.com/vanlug/vanlug.github.io
22

33
go 1.25.0
4+
5+
require (
6+
github.com/mattn/go-mastodon v0.0.11 // indirect
7+
github.com/pojntfx/felicitas.pojtinger.com v0.4.1
8+
)
9+
10+
require (
11+
github.com/gorilla/websocket v1.5.3 // indirect
12+
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e // indirect
13+
)

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
2+
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
3+
github.com/mattn/go-mastodon v0.0.11 h1:Zcvc/8EHpf3os1mwAuUUB5es5VnfVdAeb4ed6ByJnCY=
4+
github.com/mattn/go-mastodon v0.0.11/go.mod h1:0DcwYEkqigrvknMvjmfKXLP0vYyeYm+vBdUOvoHcczg=
5+
github.com/pojntfx/felicitas.pojtinger.com v0.4.1 h1:bTUaI/b8csLNWjsEBqb2d3Xao6njUst5XPOREuOfkiM=
6+
github.com/pojntfx/felicitas.pojtinger.com v0.4.1/go.mod h1:XaKmAAyinrHKMlRAXF7/rhBeENkC6e/Zf2SrhIGMgbo=
7+
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e h1:tD38/4xg4nuQCASJ/JxcvCHNb46w0cdAaJfkzQOO1bA=
8+
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e/go.mod h1:krvJ5AY/MjdPkTeRgMYbIDhbbbVvnPQPzsIsDJO8xrY=

main.go

Lines changed: 56 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,19 @@
11
package main
22

33
import (
4-
"encoding/json"
54
"fmt"
6-
"io"
75
"log"
86
"net/http"
9-
"net/url"
107
"os"
8+
9+
mastodonhandler "github.com/pojntfx/felicitas.pojtinger.com/api/mastodon"
10+
"github.com/vanlug/vanlug.github.io/pkg/handlers"
1111
)
1212

1313
const defaultLumaAPIBase = "https://api.luma.com/calendar/get-items"
1414

15-
type LumaEvent struct {
16-
Name string `json:"name"`
17-
StartAt string `json:"start_at"`
18-
URL string `json:"url"`
19-
CoverURL string `json:"cover_url"`
20-
GeoAddr *struct {
21-
Address string `json:"address"`
22-
ShortAddress string `json:"short_address"`
23-
} `json:"geo_address_info"`
24-
}
25-
26-
type LumaEntry struct {
27-
Event LumaEvent `json:"event"`
28-
}
29-
30-
type LumaResponse struct {
31-
Entries []LumaEntry `json:"entries"`
32-
}
33-
34-
type EventItem struct {
35-
Name string `json:"name"`
36-
StartAt string `json:"start_at"`
37-
URL string `json:"url"`
38-
Location string `json:"location"`
39-
CoverURL string `json:"cover_url"`
40-
}
41-
42-
type Output struct {
43-
Entries []EventItem `json:"entries"`
44-
}
45-
46-
func NextEventHandler(apiBase string) http.HandlerFunc {
47-
return func(w http.ResponseWriter, r *http.Request) {
48-
cal := r.URL.Query().Get("calendar")
49-
if cal == "" {
50-
w.Write([]byte("missing calendar query parameter"))
51-
52-
panic("missing calendar query parameter")
53-
}
54-
55-
u := apiBase + "?" + url.Values{
56-
"calendar_api_id": {cal},
57-
"period": {"future"},
58-
"pagination_limit": {"1"},
59-
}.Encode()
60-
61-
resp, err := http.Get(u)
62-
if err != nil {
63-
panic(err)
64-
}
65-
defer resp.Body.Close()
66-
67-
body, err := io.ReadAll(resp.Body)
68-
if err != nil {
69-
panic(err)
70-
}
71-
72-
var lr LumaResponse
73-
if err := json.Unmarshal(body, &lr); err != nil {
74-
panic(err)
75-
}
76-
77-
output := Output{}
78-
79-
if len(lr.Entries) > 0 {
80-
evt := lr.Entries[0].Event
81-
82-
loc := ""
83-
if evt.GeoAddr != nil {
84-
loc = evt.GeoAddr.Address
85-
if loc == "" {
86-
loc = evt.GeoAddr.ShortAddress
87-
}
88-
}
89-
90-
output.Entries = []EventItem{{
91-
Name: evt.Name,
92-
StartAt: evt.StartAt,
93-
URL: evt.URL,
94-
Location: loc,
95-
CoverURL: evt.CoverURL,
96-
}}
97-
}
98-
99-
j, err := json.Marshal(output)
100-
if err != nil {
101-
panic(err)
102-
}
103-
104-
fmt.Fprintf(w, "%v", string(j))
105-
}
106-
}
107-
108-
func EventsHandler(apiBase string) http.HandlerFunc {
109-
return func(w http.ResponseWriter, r *http.Request) {
110-
cal := r.URL.Query().Get("calendar")
111-
if cal == "" {
112-
w.Write([]byte("missing calendar query parameter"))
113-
114-
panic("missing calendar query parameter")
115-
}
116-
117-
u := apiBase + "?" + url.Values{
118-
"calendar_api_id": {cal},
119-
"period": {"future"},
120-
"pagination_limit": {"20"},
121-
}.Encode()
122-
123-
resp, err := http.Get(u)
124-
if err != nil {
125-
panic(err)
126-
}
127-
defer resp.Body.Close()
128-
129-
body, err := io.ReadAll(resp.Body)
130-
if err != nil {
131-
panic(err)
132-
}
133-
134-
var lr LumaResponse
135-
if err := json.Unmarshal(body, &lr); err != nil {
136-
panic(err)
137-
}
138-
139-
output := Output{}
140-
141-
for _, entry := range lr.Entries {
142-
evt := entry.Event
143-
144-
loc := ""
145-
if evt.GeoAddr != nil {
146-
loc = evt.GeoAddr.Address
147-
if loc == "" {
148-
loc = evt.GeoAddr.ShortAddress
149-
}
150-
}
151-
152-
output.Entries = append(output.Entries, EventItem{
153-
Name: evt.Name,
154-
StartAt: evt.StartAt,
155-
URL: evt.URL,
156-
Location: loc,
157-
CoverURL: evt.CoverURL,
158-
})
159-
}
160-
161-
j, err := json.Marshal(output)
162-
if err != nil {
163-
panic(err)
164-
}
165-
166-
fmt.Fprintf(w, "%v", string(j))
167-
}
168-
}
169-
17015
func Handler(w http.ResponseWriter, r *http.Request) {
171-
NextEventHandler(defaultLumaAPIBase).ServeHTTP(w, r)
16+
handlers.NextEventHandler(w, r, defaultLumaAPIBase)
17217
}
17318

17419
func main() {
@@ -187,6 +32,14 @@ func main() {
18732
apiBase = defaultLumaAPIBase
18833
}
18934

35+
mastodonServer := os.Getenv("MASTODON_SERVER")
36+
if mastodonServer == "" {
37+
mastodonServer = "https://thecanadian.social"
38+
}
39+
mastodonClientID := os.Getenv("MASTODON_CLIENT_ID")
40+
mastodonClientSecret := os.Getenv("MASTODON_CLIENT_SECRET")
41+
mastodonAccessToken := os.Getenv("MASTODON_ACCESS_TOKEN")
42+
19043
cors := func(next http.HandlerFunc) http.HandlerFunc {
19144
return func(w http.ResponseWriter, r *http.Request) {
19245
w.Header().Set("Access-Control-Allow-Origin", origin)
@@ -204,11 +57,52 @@ func main() {
20457
}
20558
}
20659

207-
http.HandleFunc("/next-event", cors(NextEventHandler(apiBase)))
208-
http.HandleFunc("/events", cors(EventsHandler(apiBase)))
60+
mux := http.NewServeMux()
61+
62+
mux.HandleFunc("/next-event", cors(func(w http.ResponseWriter, r *http.Request) {
63+
defer func() {
64+
if err := recover(); err != nil {
65+
log.Println("Error occured in next-event API:", err)
66+
67+
http.Error(w, "Error occured in next-event API", http.StatusInternalServerError)
68+
69+
return
70+
}
71+
}()
72+
73+
handlers.NextEventHandler(w, r, apiBase)
74+
}))
75+
76+
mux.HandleFunc("/events", cors(func(w http.ResponseWriter, r *http.Request) {
77+
defer func() {
78+
if err := recover(); err != nil {
79+
log.Println("Error occured in events API:", err)
80+
81+
http.Error(w, "Error occured in events API", http.StatusInternalServerError)
82+
83+
return
84+
}
85+
}()
86+
87+
handlers.EventsHandler(w, r, apiBase)
88+
}))
89+
90+
mux.HandleFunc("/mastodon", cors(func(w http.ResponseWriter, r *http.Request) {
91+
defer func() {
92+
if err := recover(); err != nil {
93+
log.Println("Error occured in Mastodon API:", err)
94+
95+
http.Error(w, "Error occured in Mastodon API", http.StatusInternalServerError)
96+
97+
return
98+
}
99+
}()
100+
101+
mastodonhandler.MastodonFeedHandler(w, r, mastodonServer, mastodonClientID, mastodonClientSecret, mastodonAccessToken)
102+
}))
209103

210104
log.Printf("listening on :%s", port)
211-
if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil {
105+
if err := http.ListenAndServe(fmt.Sprintf(":%s", port), mux); err != nil {
212106
log.Fatal(err)
213107
}
214108
}

pkg/handlers/luma.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package handlers
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"net/url"
9+
)
10+
11+
type LumaEvent struct {
12+
Name string `json:"name"`
13+
StartAt string `json:"start_at"`
14+
URL string `json:"url"`
15+
CoverURL string `json:"cover_url"`
16+
GeoAddr *struct {
17+
Address string `json:"address"`
18+
ShortAddress string `json:"short_address"`
19+
} `json:"geo_address_info"`
20+
}
21+
22+
type LumaEntry struct {
23+
Event LumaEvent `json:"event"`
24+
}
25+
26+
type LumaResponse struct {
27+
Entries []LumaEntry `json:"entries"`
28+
}
29+
30+
type EventItem struct {
31+
Name string `json:"name"`
32+
StartAt string `json:"start_at"`
33+
URL string `json:"url"`
34+
Location string `json:"location"`
35+
CoverURL string `json:"cover_url"`
36+
}
37+
38+
type EventOutput struct {
39+
Entries []EventItem `json:"entries"`
40+
}
41+
42+
func lumaFetch(w http.ResponseWriter, r *http.Request, apiBase string, limit string) {
43+
cal := r.URL.Query().Get("calendar")
44+
if cal == "" {
45+
w.Write([]byte("missing calendar query parameter"))
46+
47+
panic("missing calendar query parameter")
48+
}
49+
50+
u := apiBase + "?" + url.Values{
51+
"calendar_api_id": {cal},
52+
"period": {"future"},
53+
"pagination_limit": {limit},
54+
}.Encode()
55+
56+
resp, err := http.Get(u)
57+
if err != nil {
58+
panic(err)
59+
}
60+
defer resp.Body.Close()
61+
62+
body, err := io.ReadAll(resp.Body)
63+
if err != nil {
64+
panic(err)
65+
}
66+
67+
var lr LumaResponse
68+
if err := json.Unmarshal(body, &lr); err != nil {
69+
panic(err)
70+
}
71+
72+
output := EventOutput{}
73+
74+
for _, entry := range lr.Entries {
75+
evt := entry.Event
76+
77+
loc := ""
78+
if evt.GeoAddr != nil {
79+
loc = evt.GeoAddr.Address
80+
if loc == "" {
81+
loc = evt.GeoAddr.ShortAddress
82+
}
83+
}
84+
85+
output.Entries = append(output.Entries, EventItem{
86+
Name: evt.Name,
87+
StartAt: evt.StartAt,
88+
URL: evt.URL,
89+
Location: loc,
90+
CoverURL: evt.CoverURL,
91+
})
92+
}
93+
94+
j, err := json.Marshal(output)
95+
if err != nil {
96+
panic(err)
97+
}
98+
99+
fmt.Fprintf(w, "%v", string(j))
100+
}
101+
102+
func NextEventHandler(w http.ResponseWriter, r *http.Request, apiBase string) {
103+
lumaFetch(w, r, apiBase, "1")
104+
}
105+
106+
func EventsHandler(w http.ResponseWriter, r *http.Request, apiBase string) {
107+
lumaFetch(w, r, apiBase, "20")
108+
}

0 commit comments

Comments
 (0)