4 files changed,
44 insertions(+),
64 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-04-22 20:13:38 +0300
Authored at:
2026-04-20 16:04:24 +0300
Change ID:
vklopukvkryuuvnmuovpqtqvlwulzxsk
Parent:
c37eeed
jump to
| M | app/atom.go |
| M | app/atom_test.go |
| M | sources/telegram/telegram.go |
| M | sources/ztoe/ztoe.go |
M
app/atom.go
··· 41 41 Updated time.Time 42 42 } 43 43 44 -type FeedOption func(*AtomFeed) 44 +type FeedBuilder struct{ f AtomFeed } 45 45 46 -func WithFeedSubtitle(subtitle string) FeedOption { 47 - return func(f *AtomFeed) { 48 - f.Subtitle = subtitle 49 - } 50 -} 51 - 52 -func WithFeedUpdated(updated time.Time) FeedOption { 53 - return func(f *AtomFeed) { 54 - if !updated.IsZero() { 55 - f.Updated = updated.Format(time.RFC3339) 56 - } 57 - } 58 -} 59 - 60 -type FeedBuilder struct { 61 - feed AtomFeed 62 -} 63 - 64 -func NewFeed(title, id string, opts ...FeedOption) *FeedBuilder { 65 - builder := &FeedBuilder{feed: AtomFeed{ 46 +func NewFeed(title, id string) *FeedBuilder { 47 + return &FeedBuilder{f: AtomFeed{ 66 48 XMLNS: "http://www.w3.org/2005/Atom", 67 49 Title: title, 68 50 ID: id, 69 51 Updated: time.Now().Format(time.RFC3339), 70 52 }} 71 - for _, opt := range opts { 72 - opt(&builder.feed) 73 - } 74 - return builder 75 53 } 76 54 77 -func (f *FeedBuilder) Add(title, id, content string, date time.Time) *FeedBuilder { 78 - return f.AddEntry(FeedEntry{ 79 - Title: title, 80 - ID: id, 81 - Content: content, 82 - Updated: date, 83 - }) 55 +func (f *FeedBuilder) WithSubtitle(subtitle string) *FeedBuilder { 56 + f.f.Subtitle = subtitle 57 + return f 84 58 } 85 59 86 -func (f *FeedBuilder) AddText(title, content string, updated time.Time) *FeedBuilder { 87 - return f.AddEntry(FeedEntry{ 88 - Title: title, 89 - Content: content, 90 - Updated: updated, 91 - }) 60 +func (f *FeedBuilder) WithUpdated(updated time.Time) *FeedBuilder { 61 + if !updated.IsZero() { 62 + f.f.Updated = updated.Format(time.RFC3339) 63 + } 64 + return f 92 65 } 93 66 94 -func (f *FeedBuilder) AddEntry(entry FeedEntry) *FeedBuilder { 67 +func (f *FeedBuilder) Add(entry FeedEntry) *FeedBuilder { 95 68 if entry.Updated.IsZero() { 96 69 entry.Updated = time.Now() 97 70 } ··· 100 73 entry.ID = fmt.Sprintf("urn:sha1:%x", hash) 101 74 } 102 75 103 - f.feed.Entries = append(f.feed.Entries, AtomEntry{ 76 + contentType := entry.ContentType 77 + if contentType == "" { 78 + contentType = "text" 79 + } 80 + 81 + f.f.Entries = append(f.f.Entries, AtomEntry{ 104 82 Title: entry.Title, 105 83 ID: entry.ID, 106 84 Updated: entry.Updated.Format(time.RFC3339), ··· 110 88 }, 111 89 }) 112 90 113 - feedUpdated, err := time.Parse(time.RFC3339, f.feed.Updated) 91 + feedUpdated, err := time.Parse(time.RFC3339, f.f.Updated) 114 92 if err != nil || entry.Updated.After(feedUpdated) { 115 - f.feed.Updated = entry.Updated.Format(time.RFC3339) 93 + f.f.Updated = entry.Updated.Format(time.RFC3339) 116 94 } 117 95 return f 118 96 } 119 97 120 98 func (f *FeedBuilder) SetUpdated(updated time.Time) *FeedBuilder { 121 99 if !updated.IsZero() { 122 - f.feed.Updated = updated.Format(time.RFC3339) 100 + f.f.Updated = updated.Format(time.RFC3339) 123 101 } 124 102 return f 125 103 } ··· 127 105 func (f *FeedBuilder) WriteTo(w io.Writer) error { 128 106 enc := xml.NewEncoder(w) 129 107 enc.Indent("", " ") 130 - return enc.Encode(f.feed) 108 + return enc.Encode(f.f) 131 109 } 132 110 133 111 func (f *FeedBuilder) Bytes() ([]byte, error) {
M
app/atom_test.go
··· 14 14 15 15 func TestFeedBuilderAddEntryDefaults(t *testing.T) { 16 16 feed := NewFeed("test", "feed-id") 17 - feed.AddEntry(FeedEntry{Title: "entry", Content: "body"}) 17 + feed.Add(FeedEntry{Title: "entry", Content: "body"}) 18 18 19 19 is.Equal(t, 1, len(feed.f.Entries)) 20 20 entry := feed.f.Entries[0] ··· 24 24 25 25 func TestFeedBuilderBytesAndWriteTo(t *testing.T) { 26 26 updated := time.Date(2026, 4, 20, 12, 30, 0, 0, time.UTC) 27 - feed := NewFeed("test", "feed-id", WithFeedSubtitle("subtitle")).AddText("entry", "content", updated) 27 + feed := NewFeed("test", "feed-id"). 28 + WithSubtitle("subtitle"). 29 + Add(FeedEntry{Title: "entry", Content: "content", Updated: updated}) 28 30 29 31 raw, err := feed.Bytes() 30 32 is.Err(t, err, nil) ··· 40 42 func TestFeedBuilderRender(t *testing.T) { 41 43 r := httptest.NewRecorder() 42 44 err := NewFeed("test", "feed-id"). 43 - Add("entry", "entry-id", "content", time.Date(2026, 4, 20, 8, 0, 0, 0, time.UTC)). 45 + Add(FeedEntry{ 46 + Title: "entry", 47 + ID: "entry-id", 48 + Content: "content", 49 + Updated: time.Date(2026, 4, 20, 8, 0, 0, 0, time.UTC), 50 + }). 44 51 Render(r) 45 52 is.Err(t, err, nil) 46 53 ··· 52 59 53 60 func TestFeedEntryTextContent(t *testing.T) { 54 61 feed := NewFeed("test", "feed-id"). 55 - AddEntry(FeedEntry{ 62 + Add(FeedEntry{ 56 63 Title: "text entry", 57 64 Content: "plain text content", 58 65 ContentType: "text", ··· 77 84 func TestFeedEntryHtmlContent(t *testing.T) { 78 85 htmlContent := "<p>Hello <strong>World</strong></p>" 79 86 feed := NewFeed("test", "feed-id"). 80 - AddEntry(FeedEntry{ 87 + Add(FeedEntry{ 81 88 Title: "html entry", 82 89 Content: htmlContent, 83 90 ContentType: "html", ··· 102 109 func TestFeedMultipleEntriesWithMixedContentTypes(t *testing.T) { 103 110 updated := time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC) 104 111 feed := NewFeed("test", "feed-id"). 105 - AddEntry(FeedEntry{ 112 + Add(FeedEntry{ 106 113 Title: "text entry", 107 114 Content: "plain text", 108 115 ContentType: "text", 109 116 Updated: updated, 110 117 }). 111 - AddEntry(FeedEntry{ 118 + Add(FeedEntry{ 112 119 Title: "html entry", 113 120 Content: "<p>html content</p>", 114 121 ContentType: "html", 115 122 Updated: updated, 116 123 }). 117 - AddEntry(FeedEntry{ 124 + Add(FeedEntry{ 118 125 Title: "default entry", 119 126 Content: "default content", 120 127 Updated: updated,
M
sources/ztoe/ztoe.go
··· 56 56 fmt.Sprintf("ztoe-%s-%s", group, subgroup)) 57 57 58 58 for _, interval := range buildOutageIntervals(slots) { 59 - feed.Add( 60 - fmt.Sprintf("Power outage %s-%s", interval.Start, interval.End), 61 - fmt.Sprintf("ztoe-%s-%s-%s-%s-%s", 62 - group, subgroup, schedule.Date, 63 - strings.ReplaceAll(interval.Start, ":", ""), 64 - strings.ReplaceAll(interval.End, ":", ""), 65 - ), 66 - fmt.Sprintf("Date: %s\nGroup: %s.%s\nTime: %s-%s", 67 - schedule.Date, group, subgroup, interval.Start, interval.End), 68 - intervalTime(schedule.Date, interval.Start), 69 - ) 59 + feed.Add(app.FeedEntry{ 60 + Title: fmt.Sprintf("Power outage %s-%s", interval.Start, interval.End), 61 + ID: fmt.Sprintf("ztoe-%s-%s-%s-%s-%s", group, subgroup, schedule.Date, strings.ReplaceAll(interval.Start, ":", ""), strings.ReplaceAll(interval.End, ":", "")), 62 + Content: fmt.Sprintf("Date: %s\nGroup: %s.%s\nTime: %s-%s", schedule.Date, group, subgroup, interval.Start, interval.End), 63 + Updated: intervalTime(schedule.Date, interval.Start), 64 + }) 70 65 } 71 66 if err := feed.Render(w); err != nil { 72 67 http.Error(w, "failed to render feed", http.StatusInternalServerError)