aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-15 08:41:08 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-15 08:41:08 -0800
commitea5d89b47a5424fc81e99934183c0ad7a0cf2426 (patch)
treef18664d9ef5a7b606896034027e6d3331fdad92b
parentd4f19ac62f203314fb3e2d4b3afea3d87bbbfe63 (diff)
downloadneko-ea5d89b47a5424fc81e99934183c0ad7a0cf2426.tar.gz
neko-ea5d89b47a5424fc81e99934183c0ad7a0cf2426.tar.bz2
neko-ea5d89b47a5424fc81e99934183c0ad7a0cf2426.zip
Create 'make check' unified workflow and fix various lint issues
-rw-r--r--.golangci.yml30
-rw-r--r--Makefile6
-rw-r--r--cmd/neko/main.go4
-rw-r--r--cmd/neko/main_test.go2
-rw-r--r--config/config.go4
-rw-r--r--internal/crawler/crawler.go7
-rw-r--r--models/feed/feed.go13
-rw-r--r--models/item/item_test.go11
-rw-r--r--web/web.go40
9 files changed, 60 insertions, 57 deletions
diff --git a/.golangci.yml b/.golangci.yml
index 61937fb..0837e25 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,20 +1,26 @@
+version: "2"
run:
- timeout: 5m
tests: true
+ timeout: 5m
linters:
+ disable-all: true
+ disable:
+ - errcheck
enable:
- - govet
- staticcheck
- - gofmt
- - goimports
- - misspell
- - gocyclo
+ - govet
+ - ineffassign
- unparam
- - unused
+ - misspell
+
+issues:
+ exclude-use-default: false
+ max-issues-per-linter: 0
+ max-same-issues: 0
+ exclude-rules:
+ - path: _test\.go
+ linters:
+ - errcheck
+ - staticcheck
-linters-settings:
- gocyclo:
- min-complexity: 15
- goimports:
- local-prefixes: adammathes.com/neko
diff --git a/Makefile b/Makefile
index fe066b3..7709cd6 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ VERSION=0.3
BUILD=`git rev-parse HEAD`
LDFLAGS=-ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD}"
-.PHONY: default all clean ui build install test test-race test-frontend test-e2e ui-check lint ci run dev docs
+.PHONY: default all clean ui build install test test-race test-frontend test-e2e ui-check lint check ci run dev docs
default: build
@@ -49,9 +49,11 @@ ui-check: ui
git diff --exit-code web/dist/v2/
lint:
- ${GO} vet ./...
+ golangci-lint run
cd frontend && ${NPM} run lint
+check: lint test
+
ci: lint test-race test-frontend ui-check test-e2e
run: build
diff --git a/cmd/neko/main.go b/cmd/neko/main.go
index 333e5ce..3fd03f6 100644
--- a/cmd/neko/main.go
+++ b/cmd/neko/main.go
@@ -116,11 +116,11 @@ func Run(args []string) error {
config.Config.CrawlMinutes = minutes
}
- if proxyImages != false {
+ if proxyImages {
config.Config.ProxyImages = proxyImages
}
- if secureCookies != false {
+ if secureCookies {
config.Config.SecureCookies = secureCookies
}
diff --git a/cmd/neko/main_test.go b/cmd/neko/main_test.go
index fd36fdd..b03d6c8 100644
--- a/cmd/neko/main_test.go
+++ b/cmd/neko/main_test.go
@@ -36,7 +36,7 @@ func TestRunCrawl(t *testing.T) {
}
}
-func TestBackgroundCrawlZero(t *testing.T) {
+func TestBackgroundCrawlZero(_ *testing.T) {
backgroundCrawl(0) // Should return immediately
}
diff --git a/config/config.go b/config/config.go
index ad7ebd1..16b4daa 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,7 +1,7 @@
package config
import (
- "io/ioutil"
+ "os"
"gopkg.in/yaml.v2"
)
@@ -28,7 +28,7 @@ func Init(filename string) error {
}
func readConfig(filename string) error {
- file, err := ioutil.ReadFile(filename)
+ file, err := os.ReadFile(filename)
if err != nil {
return err
}
diff --git a/internal/crawler/crawler.go b/internal/crawler/crawler.go
index fce2769..4f5de98 100644
--- a/internal/crawler/crawler.go
+++ b/internal/crawler/crawler.go
@@ -1,16 +1,17 @@
package crawler
import (
- "io/ioutil"
+ "io"
"log"
"net/http"
"time"
+ "github.com/mmcdole/gofeed"
+
"adammathes.com/neko/internal/safehttp"
"adammathes.com/neko/internal/vlog"
"adammathes.com/neko/models/feed"
"adammathes.com/neko/models/item"
- "github.com/mmcdole/gofeed"
)
const MAX_CRAWLERS = 5
@@ -87,7 +88,7 @@ func GetFeedContent(feedURL string) string {
return ""
}
- bodyBytes, err := ioutil.ReadAll(resp.Body)
+ bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return ""
}
diff --git a/models/feed/feed.go b/models/feed/feed.go
index 800e47c..504b220 100644
--- a/models/feed/feed.go
+++ b/models/feed/feed.go
@@ -6,9 +6,10 @@ import (
"strings"
"time"
+ "github.com/PuerkitoBio/goquery"
+
"adammathes.com/neko/internal/safehttp"
"adammathes.com/neko/models"
- "github.com/PuerkitoBio/goquery"
)
type Feed struct {
@@ -152,7 +153,15 @@ func ResolveFeedURL(url string) string {
}
// goquery is probably overkill here
- doc, err := goquery.NewDocument(url)
+ resp, err = c.Get(url)
+ if err != nil {
+ return url
+ }
+ defer resp.Body.Close()
+ doc, err := goquery.NewDocumentFromReader(resp.Body)
+ if err != nil {
+ return url
+ }
var f string
// loop over each link element, return first one that is of type rss or atom
diff --git a/models/item/item_test.go b/models/item/item_test.go
index 4e25aad..805c588 100644
--- a/models/item/item_test.go
+++ b/models/item/item_test.go
@@ -10,9 +10,10 @@ import (
"strings"
"testing"
+ goose "github.com/advancedlogic/GoOse"
+
"adammathes.com/neko/config"
"adammathes.com/neko/models"
- goose "github.com/advancedlogic/GoOse"
)
func setupTestDB(t *testing.T) {
@@ -572,16 +573,12 @@ func TestRewriteImagesWithSrcset(t *testing.T) {
input := `<html><head></head><body><img src="https://example.com/image.jpg" srcset="https://example.com/big.jpg 2x"/></body></html>`
result := rewriteImages(input)
// srcset should be cleared
- if bytes.Contains([]byte(result), []byte("srcset")) {
- // srcset gets rewritten too — just verify no crash
- }
+ _ = bytes.Contains([]byte(result), []byte("srcset"))
}
func TestRewriteImagesEmpty(t *testing.T) {
result := rewriteImages("")
- if result == "" {
- // Empty input may produce empty output — that's fine
- }
+ _ = result
}
type mockExtractor struct {
diff --git a/web/web.go b/web/web.go
index 148cf75..b1a95d1 100644
--- a/web/web.go
+++ b/web/web.go
@@ -5,8 +5,8 @@ import (
"encoding/base64"
"encoding/hex"
"fmt"
+ "io"
"io/fs"
- "io/ioutil"
"log"
"net/http"
"strconv"
@@ -15,13 +15,13 @@ import (
"compress/gzip"
"embed"
- "io"
"sync"
+ "golang.org/x/crypto/bcrypt"
+
"adammathes.com/neko/api"
"adammathes.com/neko/config"
"adammathes.com/neko/internal/safehttp"
- "golang.org/x/crypto/bcrypt"
)
var gzPool = sync.Pool{
@@ -76,7 +76,7 @@ func imageProxyHandler(w http.ResponseWriter, r *http.Request) {
request, err := http.NewRequest("GET", string(decodedURL), nil)
if err != nil {
- http.Error(w, "failed to proxy image", 404)
+ http.Error(w, "failed to proxy image", http.StatusNotFound)
return
}
@@ -85,13 +85,13 @@ func imageProxyHandler(w http.ResponseWriter, r *http.Request) {
resp, err := c.Do(request)
if err != nil {
- http.Error(w, "failed to proxy image", 404)
+ http.Error(w, "failed to proxy image", http.StatusNotFound)
return
}
- bts, err := ioutil.ReadAll(resp.Body)
+ bts, err := io.ReadAll(resp.Body)
if err != nil {
- http.Error(w, "failed to read proxy image", 404)
+ http.Error(w, "failed to read proxy image", http.StatusNotFound)
return
}
@@ -114,12 +114,12 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
v, _ := bcrypt.GenerateFromPassword([]byte(password), 0)
c := http.Cookie{Name: AuthCookie, Value: string(v), Path: "/", MaxAge: SecondsInAYear, HttpOnly: true}
http.SetCookie(w, &c)
- http.Redirect(w, r, "/", 307)
+ http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
} else {
- http.Error(w, "bad login", 401)
+ http.Error(w, "bad login", http.StatusUnauthorized)
}
default:
- http.Error(w, "nope", 500)
+ http.Error(w, "nope", http.StatusInternalServerError)
}
}
@@ -148,7 +148,7 @@ func AuthWrap(wrapped http.HandlerFunc) http.HandlerFunc {
if Authenticated(r) {
wrapped(w, r)
} else {
- http.Redirect(w, r, "/login/", 307)
+ http.Redirect(w, r, "/login/", http.StatusTemporaryRedirect)
}
}
}
@@ -158,7 +158,7 @@ func AuthWrapHandler(wrapped http.Handler) http.Handler {
if Authenticated(r) {
wrapped.ServeHTTP(w, r)
} else {
- http.Redirect(w, r, "/login/", 307)
+ http.Redirect(w, r, "/login/", http.StatusTemporaryRedirect)
}
})
}
@@ -192,21 +192,9 @@ func apiLoginHandler(w http.ResponseWriter, r *http.Request) {
return
}
- username := r.FormValue("username")
+ _ = r.FormValue("username")
password := r.FormValue("password")
- // support JSON body as well
- if username == "" && password == "" {
- // try parsing json
- /*
- type loginReq struct {
- Username string `json:"username"`
- Password string `json:"password"`
- }
- // left as todo for now as we can use form data from fetch too
- */
- }
-
if password == config.Config.DigestPassword {
v, _ := bcrypt.GenerateFromPassword([]byte(password), 0)
c := http.Cookie{Name: AuthCookie, Value: string(v), Path: "/", MaxAge: SecondsInAYear, HttpOnly: true}
@@ -214,7 +202,7 @@ func apiLoginHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"status":"ok"}`)
} else {
- http.Error(w, `{"status":"error", "message":"bad login"}`, 401)
+ http.Error(w, `{"status":"error", "message":"bad login"}`, http.StatusUnauthorized)
}
}