rss email digests over ssh because you're a cool kid herald.dunkirk.sh
go rss rss-reader ssh charm
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add docs and defer for linter

+117 -94
+4 -4
.golangci.yml
··· 12 12 - unused 13 13 - misspell 14 14 - unconvert 15 + - errcheck 16 + - gosec 15 17 disable: 16 - - errcheck # Too many benign defer Close() errors (87 issues) 17 - - gosec # G104 duplicates errcheck, G304 acceptable for config loading 18 - - revive # Style-focused, not correctness (95 doc comments) 19 - - unparam # Unused params are future-proofing for interface compatibility 18 + - revive # Enabled but too verbose (91 doc comments) - re-enable when ready to document 19 + - unparam # 2 false positives for error returns that may be used in future 20 20 21 21 formatters: 22 22 enable:
+1 -1
config/app.go
··· 58 58 } 59 59 60 60 if path != "" { 61 - data, err := os.ReadFile(path) 61 + data, err := os.ReadFile(path) //nolint:gosec // Config file path from CLI flag 62 62 if err != nil { 63 63 return nil, fmt.Errorf("failed to read config file: %w", err) 64 64 }
+1
config/parse.go
··· 1 + // Package config handles Herald configuration parsing and validation. 1 2 package config 2 3 3 4 import (
+8 -8
email/send.go
··· 50 50 if err != nil { 51 51 return fmt.Errorf("TLS dial: %w", err) 52 52 } 53 - defer conn.Close() 53 + defer func() { _ = conn.Close() }() 54 54 55 55 client, err := smtp.NewClient(conn, m.cfg.Host) 56 56 if err != nil { 57 57 return fmt.Errorf("SMTP client: %w", err) 58 58 } 59 - defer client.Close() 59 + defer func() { _ = client.Close() }() 60 60 61 61 if auth != nil { 62 62 if err = client.Auth(auth); err != nil { ··· 72 72 if err != nil { 73 73 return fmt.Errorf("dial: %w", err) 74 74 } 75 - defer conn.Close() 75 + defer func() { _ = conn.Close() }() 76 76 77 77 client, err := smtp.NewClient(conn, m.cfg.Host) 78 78 if err != nil { 79 79 return fmt.Errorf("SMTP client: %w", err) 80 80 } 81 - defer client.Close() 81 + defer func() { _ = client.Close() }() 82 82 83 83 // Start TLS before auth 84 84 tlsConfig := &tls.Config{ ··· 182 182 func encodeQuotedPrintable(s string) string { 183 183 var buf strings.Builder 184 184 w := quotedprintable.NewWriter(&buf) 185 - w.Write([]byte(s)) 186 - w.Close() 185 + _, _ = w.Write([]byte(s)) 186 + _ = w.Close() 187 187 return buf.String() 188 188 } 189 189 ··· 197 197 if err != nil { 198 198 return fmt.Errorf("TLS dial: %w", err) 199 199 } 200 - defer conn.Close() 200 + defer func() { _ = conn.Close() }() 201 201 202 202 client, err := smtp.NewClient(conn, m.cfg.Host) 203 203 if err != nil { 204 204 return fmt.Errorf("SMTP client: %w", err) 205 205 } 206 - defer client.Close() 206 + defer func() { _ = client.Close() }() 207 207 208 208 if auth != nil { 209 209 if err = client.Auth(auth); err != nil {
+1 -1
main.go
··· 140 140 if err != nil { 141 141 return fmt.Errorf("failed to open database: %w", err) 142 142 } 143 - defer db.Close() 143 + defer func() { _ = db.Close() }() 144 144 145 145 if err := db.Migrate(); err != nil { 146 146 return fmt.Errorf("failed to migrate database: %w", err)
+1
ratelimit/limiter.go
··· 1 + // Package ratelimit provides functionality for Herald. 1 2 package ratelimit 2 3 3 4 import (
+2 -1
scheduler/fetch.go
··· 1 + // Package scheduler provides functionality for Herald. 1 2 package scheduler 2 3 3 4 import ( ··· 70 71 result.Error = err 71 72 return result 72 73 } 73 - defer resp.Body.Close() 74 + defer func() { _ = resp.Body.Close() }() 74 75 75 76 if resp.StatusCode == http.StatusNotModified { 76 77 return result
+1 -1
scheduler/scheduler.go
··· 273 273 if err != nil { 274 274 return fmt.Errorf("begin transaction: %w", err) 275 275 } 276 - defer tx.Rollback() 276 + defer func() { _ = tx.Rollback() }() 277 277 278 278 // Mark items seen BEFORE sending email 279 279 for _, result := range results {
+46 -30
ssh/commands.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 + "io" 6 7 "strings" 7 8 "time" 8 9 ··· 28 29 Foreground(lipgloss.Color("9")) 29 30 ) 30 31 32 + // print writes to the session, ignoring errors (connection drops are expected) 33 + func print(w io.Writer, args ...interface{}) { 34 + _, _ = fmt.Fprint(w, args...) 35 + } 36 + 37 + // printf writes formatted output to the session, ignoring errors 38 + func printf(w io.Writer, format string, args ...interface{}) { 39 + _, _ = fmt.Fprintf(w, format, args...) 40 + } 41 + 42 + // println writes a line to the session, ignoring errors 43 + func println(w io.Writer, args ...interface{}) { 44 + _, _ = fmt.Fprintln(w, args...) 45 + } 46 + 31 47 func HandleCommand(sess ssh.Session, user *store.User, st *store.DB, sched *scheduler.Scheduler, logger *log.Logger) { 32 48 cmd := sess.Command() 33 49 if len(cmd) == 0 { ··· 41 57 handleLs(ctx, sess, user, st) 42 58 case "cat": 43 59 if len(cmd) < 2 { 44 - fmt.Fprintln(sess, errorStyle.Render("Usage: cat <filename>")) 60 + println(sess, errorStyle.Render("Usage: cat <filename>")) 45 61 return 46 62 } 47 63 handleCat(ctx, sess, user, st, cmd[1]) 48 64 case "rm": 49 65 if len(cmd) < 2 { 50 - fmt.Fprintln(sess, errorStyle.Render("Usage: rm <filename>")) 66 + println(sess, errorStyle.Render("Usage: rm <filename>")) 51 67 return 52 68 } 53 69 handleRm(ctx, sess, user, st, cmd[1]) 54 70 case "activate": 55 71 if len(cmd) < 2 { 56 - fmt.Fprintln(sess, errorStyle.Render("Usage: activate <filename>")) 72 + println(sess, errorStyle.Render("Usage: activate <filename>")) 57 73 return 58 74 } 59 75 handleActivate(ctx, sess, user, st, cmd[1]) 60 76 case "deactivate": 61 77 if len(cmd) < 2 { 62 - fmt.Fprintln(sess, errorStyle.Render("Usage: deactivate <filename>")) 78 + println(sess, errorStyle.Render("Usage: deactivate <filename>")) 63 79 return 64 80 } 65 81 handleDeactivate(ctx, sess, user, st, cmd[1]) 66 82 case "run": 67 83 if len(cmd) < 2 { 68 - fmt.Fprintln(sess, errorStyle.Render("Usage: run <filename>")) 84 + println(sess, errorStyle.Render("Usage: run <filename>")) 69 85 return 70 86 } 71 87 handleRun(ctx, sess, user, st, sched, cmd[1]) 72 88 case "logs": 73 89 handleLogs(ctx, sess, user, st) 74 90 default: 75 - fmt.Fprintf(sess, errorStyle.Render("Unknown command: %s\n"), cmd[0]) 76 - fmt.Fprintln(sess, "Available commands: ls, cat, rm, activate, deactivate, run, logs") 91 + printf(sess, errorStyle.Render("Unknown command: %s\n"), cmd[0]) 92 + println(sess, "Available commands: ls, cat, rm, activate, deactivate, run, logs") 77 93 } 78 94 } 79 95 80 96 func handleLs(ctx context.Context, sess ssh.Session, user *store.User, st *store.DB) { 81 97 configs, err := st.ListConfigs(ctx, user.ID) 82 98 if err != nil { 83 - fmt.Fprintln(sess, errorStyle.Render("Error: "+err.Error())) 99 + println(sess, errorStyle.Render("Error: "+err.Error())) 84 100 return 85 101 } 86 102 87 103 if len(configs) == 0 { 88 - fmt.Fprintln(sess, dimStyle.Render("No configs found. Upload one with: scp feeds.txt <host>:")) 104 + println(sess, dimStyle.Render("No configs found. Upload one with: scp feeds.txt <host>:")) 89 105 return 90 106 } 91 107 92 - fmt.Fprintln(sess, titleStyle.Render("Your configs:")) 108 + println(sess, titleStyle.Render("Your configs:")) 93 109 94 110 for _, cfg := range configs { 95 111 feeds, err := st.GetFeedsByConfig(ctx, cfg.ID) ··· 103 119 nextRunStr = formatRelativeTime(cfg.NextRun.Time) 104 120 } 105 121 106 - fmt.Fprintf(sess, " %-20s %s next: %s\n", 122 + printf(sess, " %-20s %s next: %s\n", 107 123 cfg.Filename, 108 124 dimStyle.Render(fmt.Sprintf("%d feed(s)", feedCount)), 109 125 nextRunStr, ··· 114 130 func handleCat(ctx context.Context, sess ssh.Session, user *store.User, st *store.DB, filename string) { 115 131 cfg, err := st.GetConfig(ctx, user.ID, filename) 116 132 if err != nil { 117 - fmt.Fprintln(sess, errorStyle.Render("Config not found: "+filename)) 133 + println(sess, errorStyle.Render("Config not found: "+filename)) 118 134 return 119 135 } 120 136 121 - fmt.Fprintln(sess, titleStyle.Render("# "+filename)) 122 - fmt.Fprintln(sess, cfg.RawText) 137 + println(sess, titleStyle.Render("# "+filename)) 138 + println(sess, cfg.RawText) 123 139 } 124 140 125 141 func handleRm(ctx context.Context, sess ssh.Session, user *store.User, st *store.DB, filename string) { 126 142 err := st.DeleteConfig(ctx, user.ID, filename) 127 143 if err != nil { 128 - fmt.Fprintln(sess, errorStyle.Render("Error: "+err.Error())) 144 + println(sess, errorStyle.Render("Error: "+err.Error())) 129 145 return 130 146 } 131 147 132 - fmt.Fprintln(sess, successStyle.Render("Deleted: "+filename)) 148 + println(sess, successStyle.Render("Deleted: "+filename)) 133 149 } 134 150 135 151 func handleActivate(ctx context.Context, sess ssh.Session, user *store.User, st *store.DB, filename string) { 136 152 err := st.ActivateConfig(ctx, user.ID, filename) 137 153 if err != nil { 138 - fmt.Fprintln(sess, errorStyle.Render("Error: "+err.Error())) 154 + println(sess, errorStyle.Render("Error: "+err.Error())) 139 155 return 140 156 } 141 157 142 - fmt.Fprintln(sess, successStyle.Render("Activated: "+filename)) 158 + println(sess, successStyle.Render("Activated: "+filename)) 143 159 } 144 160 145 161 func handleDeactivate(ctx context.Context, sess ssh.Session, user *store.User, st *store.DB, filename string) { 146 162 err := st.DeactivateConfigByFilename(ctx, user.ID, filename) 147 163 if err != nil { 148 - fmt.Fprintln(sess, errorStyle.Render("Error: "+err.Error())) 164 + println(sess, errorStyle.Render("Error: "+err.Error())) 149 165 return 150 166 } 151 167 152 - fmt.Fprintln(sess, successStyle.Render("Deactivated: "+filename)) 168 + println(sess, successStyle.Render("Deactivated: "+filename)) 153 169 } 154 170 155 171 func handleRun(ctx context.Context, sess ssh.Session, user *store.User, st *store.DB, sched *scheduler.Scheduler, filename string) { 156 172 cfg, err := st.GetConfig(ctx, user.ID, filename) 157 173 if err != nil { 158 - fmt.Fprintln(sess, errorStyle.Render("Config not found: "+filename)) 174 + println(sess, errorStyle.Render("Config not found: "+filename)) 159 175 return 160 176 } 161 177 ··· 175 191 case <-done: 176 192 return 177 193 default: 178 - fmt.Fprintf(sess, "\r%s Fetching feeds...", spinChars[i%len(spinChars)]) 194 + printf(sess, "\r%s Fetching feeds...", spinChars[i%len(spinChars)]) 179 195 i++ 180 196 time.Sleep(80 * time.Millisecond) 181 197 } ··· 194 210 // Wait for result 195 211 res := <-result 196 212 close(done) 197 - fmt.Fprint(sess, "\r\033[K") // Clear the spinner line 213 + print(sess, "\r\033[K") // Clear the spinner line 198 214 199 215 if res.err != nil { 200 - fmt.Fprintln(sess, errorStyle.Render("Error: "+res.err.Error())) 216 + println(sess, errorStyle.Render("Error: "+res.err.Error())) 201 217 return 202 218 } 203 219 204 220 if res.items == 0 { 205 - fmt.Fprintln(sess, dimStyle.Render("No new items found.")) 221 + println(sess, dimStyle.Render("No new items found.")) 206 222 } else { 207 - fmt.Fprintln(sess, successStyle.Render(fmt.Sprintf("Sent %d new item(s) to %s", res.items, cfg.Email))) 223 + println(sess, successStyle.Render(fmt.Sprintf("Sent %d new item(s) to %s", res.items, cfg.Email))) 208 224 } 209 225 } 210 226 211 227 func handleLogs(ctx context.Context, sess ssh.Session, user *store.User, st *store.DB) { 212 228 logs, err := st.GetRecentLogs(ctx, user.ID, 20) 213 229 if err != nil { 214 - fmt.Fprintln(sess, errorStyle.Render("Error: "+err.Error())) 230 + println(sess, errorStyle.Render("Error: "+err.Error())) 215 231 return 216 232 } 217 233 218 234 if len(logs) == 0 { 219 - fmt.Fprintln(sess, dimStyle.Render("No logs yet.")) 235 + println(sess, dimStyle.Render("No logs yet.")) 220 236 return 221 237 } 222 238 223 - fmt.Fprintln(sess, titleStyle.Render("Recent activity:")) 239 + println(sess, titleStyle.Render("Recent activity:")) 224 240 225 241 for _, l := range logs { 226 242 levelStyle := dimStyle ··· 232 248 } 233 249 234 250 timestamp := l.CreatedAt.Format("Jan 02 15:04") 235 - fmt.Fprintf(sess, " %s %s %s\n", 251 + printf(sess, " %s %s %s\n", 236 252 dimStyle.Render(timestamp), 237 253 levelStyle.Render(fmt.Sprintf("[%s]", l.Level)), 238 254 l.Message,
+1 -1
ssh/scp.go
··· 154 154 if err != nil { 155 155 return 0, fmt.Errorf("begin transaction: %w", err) 156 156 } 157 - defer tx.Rollback() 157 + defer func() { _ = tx.Rollback() }() 158 158 159 159 if err := h.store.DeleteConfigTx(ctx, tx, user.ID, name); err != nil { 160 160 h.logger.Debug("no existing config to delete", "filename", name)
+14 -13
ssh/server.go
··· 1 + // Package ssh provides functionality for Herald. 1 2 package ssh 2 3 3 4 import ( ··· 139 140 140 141 user, ok := sess.Context().Value("user").(*store.User) 141 142 if !ok { 142 - fmt.Fprintln(sess, "Authentication error") 143 + println(sess, "Authentication error") 143 144 return 144 145 } 145 146 ··· 163 164 164 165 func (s *Server) handleWelcome(sess ssh.Session, user *store.User) { 165 166 fp := sess.Context().Value("fingerprint").(string) 166 - fmt.Fprintf(sess, "Welcome to Herald!\n\n") 167 - fmt.Fprintf(sess, "Your fingerprint: %s\n\n", fp) 168 - fmt.Fprintf(sess, "Upload a config with:\n") 169 - fmt.Fprintf(sess, " scp feeds.txt %s:\n\n", sess.User()) 170 - fmt.Fprintf(sess, "Commands:\n") 171 - fmt.Fprintf(sess, " ls List your configs\n") 172 - fmt.Fprintf(sess, " cat <file> Show config contents\n") 173 - fmt.Fprintf(sess, " rm <file> Delete a config\n") 174 - fmt.Fprintf(sess, " activate <file> Enable a config\n") 175 - fmt.Fprintf(sess, " deactivate <file> Disable a config\n") 176 - fmt.Fprintf(sess, " run <file> Run a config now\n") 177 - fmt.Fprintf(sess, " logs Show recent activity\n") 167 + printf(sess, "Welcome to Herald!\n\n") 168 + printf(sess, "Your fingerprint: %s\n\n", fp) 169 + printf(sess, "Upload a config with:\n") 170 + printf(sess, " scp feeds.txt %s:\n\n", sess.User()) 171 + printf(sess, "Commands:\n") 172 + printf(sess, " ls List your configs\n") 173 + printf(sess, " cat <file> Show config contents\n") 174 + printf(sess, " rm <file> Delete a config\n") 175 + printf(sess, " activate <file> Enable a config\n") 176 + printf(sess, " deactivate <file> Disable a config\n") 177 + printf(sess, " run <file> Run a config now\n") 178 + printf(sess, " logs Show recent activity\n") 178 179 } 179 180 180 181 func (s *Server) ensureHostKey() error {
+1 -1
ssh/sftp.go
··· 39 39 }) 40 40 41 41 if err := server.Serve(); err == io.EOF { 42 - server.Close() 42 + _ = server.Close() 43 43 } else if err != nil { 44 44 logger.Error("SFTP server error", "err", err) 45 45 }
+2 -2
store/configs.go
··· 131 131 if err != nil { 132 132 return nil, fmt.Errorf("query configs: %w", err) 133 133 } 134 - defer rows.Close() 134 + defer func() { _ = rows.Close() }() 135 135 136 136 var configs []*Config 137 137 for rows.Next() { ··· 180 180 if err != nil { 181 181 return nil, fmt.Errorf("query due configs: %w", err) 182 182 } 183 - defer rows.Close() 183 + defer func() { _ = rows.Close() }() 184 184 185 185 var configs []*Config 186 186 for rows.Next() {
+7 -7
store/db.go
··· 123 123 124 124 func (db *DB) Close() error { 125 125 if db.stmts != nil { 126 - db.stmts.markItemSeen.Close() 127 - db.stmts.isItemSeen.Close() 128 - db.stmts.getSeenItems.Close() 129 - db.stmts.getConfig.Close() 130 - db.stmts.updateConfigRun.Close() 131 - db.stmts.updateFeedMeta.Close() 132 - db.stmts.cleanupSeenItems.Close() 126 + _ = db.stmts.markItemSeen.Close() 127 + _ = db.stmts.isItemSeen.Close() 128 + _ = db.stmts.getSeenItems.Close() 129 + _ = db.stmts.getConfig.Close() 130 + _ = db.stmts.updateConfigRun.Close() 131 + _ = db.stmts.updateFeedMeta.Close() 132 + _ = db.stmts.cleanupSeenItems.Close() 133 133 } 134 134 return db.DB.Close() 135 135 }
+10 -10
store/db_test.go
··· 14 14 t.Fatalf("failed to open test db: %v", err) 15 15 } 16 16 t.Cleanup(func() { 17 - db.Close() 17 + _ = db.Close() 18 18 }) 19 19 return db 20 20 } ··· 24 24 if err != nil { 25 25 t.Fatalf("Open failed: %v", err) 26 26 } 27 - defer db.Close() 27 + defer func() { _ = db.Close() }() 28 28 29 29 if db.DB == nil { 30 30 t.Error("DB.DB is nil") ··· 157 157 158 158 user, _ := db.GetOrCreateUser(ctx, "test-fp", "test-pubkey") 159 159 nextRun := time.Now().Add(time.Hour) 160 - db.CreateConfig(ctx, user.ID, "config1.herald", "user@example.com", "0 8 * * *", true, false, "raw1", nextRun) 161 - db.CreateConfig(ctx, user.ID, "config2.herald", "user@example.com", "0 9 * * *", false, true, "raw2", nextRun) 160 + _, _ = db.CreateConfig(ctx, user.ID, "config1.herald", "user@example.com", "0 8 * * *", true, false, "raw1", nextRun) 161 + _, _ = db.CreateConfig(ctx, user.ID, "config2.herald", "user@example.com", "0 9 * * *", false, true, "raw2", nextRun) 162 162 163 163 configs, err := db.ListConfigs(ctx, user.ID) 164 164 if err != nil { ··· 212 212 213 213 user, _ := db.GetOrCreateUser(ctx, "test-fp", "test-pubkey") 214 214 nextRun := time.Now().Add(time.Hour) 215 - db.CreateConfig(ctx, user.ID, "test.herald", "user@example.com", "0 8 * * *", true, false, "raw", nextRun) 215 + _, _ = db.CreateConfig(ctx, user.ID, "test.herald", "user@example.com", "0 8 * * *", true, false, "raw", nextRun) 216 216 217 217 err := db.DeleteConfig(ctx, user.ID, "test.herald") 218 218 if err != nil { ··· 253 253 user, _ := db.GetOrCreateUser(ctx, "test-fp", "test-pubkey") 254 254 nextRun := time.Now().Add(time.Hour) 255 255 cfg, _ := db.CreateConfig(ctx, user.ID, "test.herald", "user@example.com", "0 8 * * *", true, false, "raw", nextRun) 256 - db.CreateFeed(ctx, cfg.ID, "https://feed1.com/rss", "Feed 1") 257 - db.CreateFeed(ctx, cfg.ID, "https://feed2.com/atom", "Feed 2") 256 + _, _ = db.CreateFeed(ctx, cfg.ID, "https://feed1.com/rss", "Feed 1") 257 + _, _ = db.CreateFeed(ctx, cfg.ID, "https://feed2.com/atom", "Feed 2") 258 258 259 259 feeds, err := db.GetFeedsByConfig(ctx, cfg.ID) 260 260 if err != nil { ··· 318 318 feed, _ := db.CreateFeed(ctx, cfg.ID, "https://example.com/feed.xml", "") 319 319 320 320 // Mark some items as seen 321 - db.MarkItemSeen(ctx, feed.ID, "guid1", "Title 1", "link1") 322 - db.MarkItemSeen(ctx, feed.ID, "guid2", "Title 2", "link2") 321 + _ = db.MarkItemSeen(ctx, feed.ID, "guid1", "Title 1", "link1") 322 + _ = db.MarkItemSeen(ctx, feed.ID, "guid2", "Title 2", "link2") 323 323 324 324 // Query for seen GUIDs 325 325 seenSet, err := db.GetSeenGUIDs(ctx, feed.ID, []string{"guid1", "guid2", "guid3"}) ··· 348 348 feed, _ := db.CreateFeed(ctx, cfg.ID, "https://example.com/feed.xml", "") 349 349 350 350 // Mark item as seen 351 - db.MarkItemSeen(ctx, feed.ID, "old-item", "Old Item", "link") 351 + _ = db.MarkItemSeen(ctx, feed.ID, "old-item", "Old Item", "link") 352 352 353 353 // Wait to ensure timestamp is old enough 354 354 time.Sleep(50 * time.Millisecond)
+2 -2
store/feeds.go
··· 80 80 if err != nil { 81 81 return nil, fmt.Errorf("query feeds: %w", err) 82 82 } 83 - defer rows.Close() 83 + defer func() { _ = rows.Close() }() 84 84 85 85 var feeds []*Feed 86 86 for rows.Next() { ··· 119 119 if err != nil { 120 120 return nil, fmt.Errorf("query feeds: %w", err) 121 121 } 122 - defer rows.Close() 122 + defer func() { _ = rows.Close() }() 123 123 124 124 feedMap := make(map[int64][]*Feed) 125 125 for rows.Next() {
+2 -2
store/items.go
··· 70 70 if err != nil { 71 71 return nil, fmt.Errorf("query seen items: %w", err) 72 72 } 73 - defer rows.Close() 73 + defer func() { _ = rows.Close() }() 74 74 75 75 var items []*SeenItem 76 76 for rows.Next() { ··· 110 110 if err != nil { 111 111 return nil, fmt.Errorf("query seen guids: %w", err) 112 112 } 113 - defer rows.Close() 113 + defer func() { _ = rows.Close() }() 114 114 115 115 seenSet := make(map[string]bool) 116 116 for rows.Next() {
+2 -2
store/logs.go
··· 34 34 if err != nil { 35 35 return nil, fmt.Errorf("query logs: %w", err) 36 36 } 37 - defer rows.Close() 37 + defer func() { _ = rows.Close() }() 38 38 39 39 var logs []*Log 40 40 for rows.Next() { ··· 59 59 if err != nil { 60 60 return nil, fmt.Errorf("query recent logs: %w", err) 61 61 } 62 - defer rows.Close() 62 + defer func() { _ = rows.Close() }() 63 63 64 64 var logs []*Log 65 65 for rows.Next() {
+1
store/users.go
··· 1 + // Package store provides functionality for Herald. 1 2 package store 2 3 3 4 import (
+5 -5
web/handlers.go
··· 56 56 return 57 57 } 58 58 w.Header().Set("Content-Type", "text/css; charset=utf-8") 59 - w.Write(css) 59 + _, _ = w.Write(css) 60 60 } 61 61 62 62 type userPageData struct { ··· 298 298 } 299 299 } 300 300 301 - w.Write([]byte(xml.Header)) 301 + _, _ = w.Write([]byte(xml.Header)) 302 302 enc := xml.NewEncoder(w) 303 303 enc.Indent("", " ") 304 - enc.Encode(feed) 304 + _ = enc.Encode(feed) 305 305 } 306 306 307 307 type jsonFeed struct { ··· 427 427 428 428 enc := json.NewEncoder(w) 429 429 enc.SetIndent("", " ") 430 - enc.Encode(feed) 430 + _ = enc.Encode(feed) 431 431 } 432 432 433 433 func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request, fingerprint, filename string) { ··· 456 456 } 457 457 458 458 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 459 - w.Write([]byte(cfg.RawText)) 459 + _, _ = w.Write([]byte(cfg.RawText)) 460 460 } 461 461 462 462 type unsubscribePageData struct {
+5 -3
web/server.go
··· 1 + // Package web provides functionality for Herald. 1 2 package web 2 3 3 4 import ( ··· 59 60 mux.HandleFunc("/metrics", s.handleMetrics) 60 61 61 62 srv := &http.Server{ 62 - Addr: s.addr, 63 - Handler: s.loggingMiddleware(s.rateLimitMiddleware(mux)), 63 + Addr: s.addr, 64 + Handler: s.loggingMiddleware(s.rateLimitMiddleware(mux)), 65 + ReadHeaderTimeout: 10 * time.Second, 64 66 } 65 67 66 68 go func() { 67 69 <-ctx.Done() 68 - srv.Shutdown(context.Background()) 70 + _ = srv.Shutdown(context.Background()) 69 71 }() 70 72 71 73 s.logger.Info("web server listening", "addr", s.addr)