refactor: modernise Go code with go fix and manual improvements (#2027)

Apply go fix ./... analysers (Go 1.26) across backend and examples:
- interface{} → any (type alias, no behaviour change)
- for i := 0; i < N; i++ → for range N / for i := range N
- slices.Contains / slices.ContainsFunc replacing manual loops
- strings.SplitSeq replacing strings.Split in range (avoids allocation)
- strings.CutPrefix replacing HasPrefix+TrimPrefix
- min() replacing manual if/else
- fmt.Appendf replacing []byte(fmt.Sprintf(...))
- strings.Builder replacing string += concatenation
- wg.Go(func(){}) replacing wg.Add(1)/go/wg.Done() pattern
- removed redundant ii := i loop variable copies (unnecessary since Go 1.22)

omitempty on struct-typed JSON fields: go fix removed omitempty from
struct-typed fields (time.Time, PostInfo, UserDetailEntry) because
encoding/json's omitempty never applied to struct types — it was always
a no-op. Kept as bare tags (no omitzero replacement) to preserve the
existing serialisation behaviour.
This commit is contained in:
Dmitry Verkhoturov
2026-03-25 16:42:37 -05:00
committed by GitHub
parent 8aafc8fcd7
commit ba7c3aed94
38 changed files with 93 additions and 119 deletions
@@ -251,11 +251,11 @@ func (m *MemData) Flag(req engine.FlagRequest) (val bool, err error) {
// ListFlags get list of flagged keys, like blocked & verified user
// works for full locator (post flags) or with userID
func (m *MemData) ListFlags(req engine.FlagRequest) (res []interface{}, err error) {
func (m *MemData) ListFlags(req engine.FlagRequest) (res []any, err error) {
m.mu.RLock()
defer m.mu.RUnlock()
res = []interface{}{}
res = []any{}
switch req.Flag {
case engine.Verified:
@@ -198,7 +198,7 @@ func TestMemData_FindForUserPagination(t *testing.T) {
}
// write 200 comments
for i := 0; i < 200; i++ {
for i := range 200 {
c.ID = fmt.Sprintf("idd-%d", i)
c.Text = fmt.Sprintf("text #%d", i)
c.Timestamp = time.Date(2017, 12, 20, 15, 18, i, 0, time.Local)
@@ -484,7 +484,7 @@ func TestMemData_FlagVerified(t *testing.T) {
func TestMemData_FlagListVerified(t *testing.T) {
b := prepMem(t)
toIDs := func(inp []interface{}) (res []string) {
toIDs := func(inp []any) (res []string) {
res = make([]string, len(inp))
for i, v := range inp {
vv, ok := v.(string)
@@ -530,7 +530,7 @@ func TestMemData_FlagListBlocked(t *testing.T) {
return err
}
toBlocked := func(inp []interface{}) (res []store.BlockedUser) {
toBlocked := func(inp []any) (res []store.BlockedUser) {
res = make([]store.BlockedUser, len(inp))
for i, v := range inp {
vv, ok := v.(store.BlockedUser)
@@ -73,7 +73,7 @@ func (s *RPC) admEnabledHndl(id uint64, params json.RawMessage) (rr jrpc.Respons
// onEvent returns nothing, callback to OnEvent
func (s *RPC) admEventHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
var siteID string
var ps []interface{}
var ps []any
if err := json.Unmarshal(params, &ps); err != nil {
return jrpc.Response{Error: err.Error()}
}
@@ -217,7 +217,7 @@ func TestRPC_listFlagsHndl(t *testing.T) {
flags, err = re.ListFlags(verifyFlagReq)
require.NoError(t, err)
assert.Equal(t, []interface{}{"u1"}, flags)
assert.Equal(t, []any{"u1"}, flags)
verifiedUsers := make([]string, 0, len(flags))
for _, v := range flags {
verifiedUsers = append(verifiedUsers, v.(string))
@@ -21,7 +21,7 @@ import (
)
func chooseRandomUnusedPort() (port int) {
for i := 0; i < 10; i++ {
for range 10 {
port = 40000 + int(rand.Int31n(10000))
if ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)); err == nil {
_ = ln.Close()
@@ -34,7 +34,7 @@ func chooseRandomUnusedPort() (port int) {
func waitForHTTPServerStart(port int) {
// wait for up to 3 seconds for server to start before returning it
client := http.Client{Timeout: time.Second}
for i := 0; i < 300; i++ {
for range 300 {
time.Sleep(time.Millisecond * 10)
if resp, err := client.Get(fmt.Sprintf("http://localhost:%d", port)); err == nil {
_ = resp.Body.Close()
+1 -1
View File
@@ -179,7 +179,7 @@ func (cc *CleanupCommand) listComments(postURL string) ([]store.Comment, error)
commentsWithInfo := struct {
Comments []store.Comment `json:"comments"`
Info store.PostInfo `json:"info,omitempty"`
Info store.PostInfo `json:"info"`
}{}
if err = json.NewDecoder(r.Body).Decode(&commentsWithInfo); err != nil {
+1 -1
View File
@@ -173,7 +173,7 @@ func cleanupRoutes(t *testing.T, r *chi.Mux, c *cleanedComments) {
commentsWithInfo := struct {
Comments []store.Comment `json:"comments"`
Info store.PostInfo `json:"info,omitempty"`
Info store.PostInfo `json:"info"`
}{}
switch r.URL.Query().Get("url") {
+2 -6
View File
@@ -11,6 +11,7 @@ import (
"os/signal"
"path"
"regexp"
"slices"
"strings"
"syscall"
"time"
@@ -479,12 +480,7 @@ func stringsSetAndDifferent(s1, s2 string) bool {
}
func contains(s string, a []string) bool {
for _, t := range a {
if t == s {
return true
}
}
return false
return slices.Contains(a, s)
}
// newServerApp prepares application and return it with all active parts
+3 -3
View File
@@ -789,7 +789,7 @@ func Test_getAllowedDomains(t *testing.T) {
}
func chooseRandomUnusedPort() (port int) {
for i := 0; i < 10; i++ {
for range 10 {
port = 40000 + int(rand.Int31n(10000))
if ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)); err == nil {
_ = ln.Close()
@@ -803,7 +803,7 @@ func waitForHTTPServerStart(port int) {
// wait for up to 3 seconds for server to start before returning it
client := http.Client{Timeout: time.Second}
defer client.CloseIdleConnections()
for i := 0; i < 300; i++ {
for range 300 {
time.Sleep(time.Millisecond * 10)
if resp, err := client.Get(fmt.Sprintf("http://localhost:%d", port)); err == nil {
_ = resp.Body.Close()
@@ -814,7 +814,7 @@ func waitForHTTPServerStart(port int) {
func waitForHTTPSServerStart(port int) {
// wait for up to 3 seconds for HTTPS server to start
for i := 0; i < 300; i++ {
for range 300 {
time.Sleep(time.Millisecond * 10)
conn, _ := net.DialTimeout("tcp", fmt.Sprintf("localhost:%d", port), time.Millisecond*10)
if conn != nil {
+1 -4
View File
@@ -92,10 +92,7 @@ func logDeprecatedParams(params []cmd.DeprecatedFlag) {
func getDump() string {
maxSize := 5 * 1024 * 1024
stacktrace := make([]byte, maxSize)
length := runtime.Stack(stacktrace, true)
if length > maxSize {
length = maxSize
}
length := min(runtime.Stack(stacktrace, true), maxSize)
return string(stacktrace[:length])
}
+2 -2
View File
@@ -130,7 +130,7 @@ func TestGetDump(t *testing.T) {
}
func chooseRandomUnusedPort() (port int) {
for i := 0; i < 10; i++ {
for range 10 {
port = 40000 + int(rand.Int31n(10000))
if ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)); err == nil {
_ = ln.Close()
@@ -144,7 +144,7 @@ func waitForHTTPServerStart(port int) {
// wait for up to 10 seconds for server to start before returning it
client := http.Client{Timeout: time.Second}
defer client.CloseIdleConnections()
for i := 0; i < 100; i++ {
for range 100 {
time.Sleep(time.Millisecond * 100)
if resp, err := client.Get(fmt.Sprintf("http://localhost:%d", port)); err == nil {
_ = resp.Body.Close()
+1 -1
View File
@@ -48,7 +48,7 @@ type commentoCommenter struct {
Link string `json:"link"`
Photo string `json:"photo"`
Provider string `json:"provider,omitempty"`
JoinDate time.Time `json:"joinDate,omitempty"`
JoinDate time.Time `json:"joinDate"`
IsModerator bool `json:"isModerator"`
}
+3 -3
View File
@@ -38,7 +38,7 @@ func (u *URLMapper) loadRules(reader io.Reader) error {
u.rules = make(map[string]string)
for _, row := range strings.Split(rulesText, "\n") {
for row := range strings.SplitSeq(rulesText, "\n") {
row = strings.TrimSpace(row)
urls := strings.Split(row, " ")
if len(urls) != 2 {
@@ -64,8 +64,8 @@ func (u *URLMapper) URL(url string) string {
}
oldURL = strings.TrimSuffix(oldURL, "*")
newURL = strings.TrimSuffix(newURL, "*")
if strings.HasPrefix(url, oldURL) {
return newURL + strings.TrimPrefix(url, oldURL)
if after, ok := strings.CutPrefix(url, oldURL); ok {
return newURL + after
}
}
// search failed, return given url
+1 -1
View File
@@ -162,7 +162,7 @@ func TestNative_ImportManyWithError(t *testing.T) {
buf := &bytes.Buffer{}
buf.WriteString(`{"version":1, "users":[], "posts":[]}` + "\n")
for i := 0; i < 100; i++ {
for i := range 100 {
fmt.Fprintf(buf, goodRec, i)
}
buf.WriteString("{}\n")
+1 -1
View File
@@ -96,7 +96,7 @@ func TestService_Many(t *testing.T) {
s := NewService(nil, 5, d1, d2)
assert.NotNil(t, s)
for i := 0; i < 10; i++ {
for i := range 10 {
s.Submit(Request{Comment: store.Comment{ID: fmt.Sprintf("%d", 100+i)}})
s.SubmitVerification(VerificationRequest{User: fmt.Sprintf("%d", 100+i)})
time.Sleep(time.Millisecond * time.Duration(rand.Int31n(20)))
+1 -1
View File
@@ -15,7 +15,7 @@ import (
)
type tgRequester interface {
Request(ctx context.Context, method string, b []byte, data interface{}) error
Request(ctx context.Context, method string, b []byte, data any) error
}
// TGUpdatesReceiver used to dispatch telegram updates to multiple receivers
+1 -1
View File
@@ -39,7 +39,7 @@ type mockTGRequester struct {
t *testing.T
}
func (m *mockTGRequester) Request(_ context.Context, _ string, _ []byte, data interface{}) error {
func (m *mockTGRequester) Request(_ context.Context, _ string, _ []byte, data any) error {
if m.hit < 2 {
m.hit++
assert.NoError(m.t, json.Unmarshal([]byte(getUpdatesResp), data))
+3 -3
View File
@@ -87,7 +87,7 @@ func TestAdmin_Delete(t *testing.T) {
// check count updated
res, code = get(t, ts.URL+"/api/v1/count?site=remark42&url=https://radio-t.com/blah")
assert.Equal(t, http.StatusOK, code)
b := map[string]interface{}{}
b := map[string]any{}
err = json.Unmarshal([]byte(res), &b)
assert.NoError(t, err)
t.Logf("%#v", b)
@@ -718,7 +718,7 @@ func TestAdmin_DeleteMeRequest(t *testing.T) {
User: &token.User{
ID: "user1",
Picture: "pic.image",
Attributes: map[string]interface{}{
Attributes: map[string]any{
"delete_me": true,
},
},
@@ -786,7 +786,7 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
},
User: &token.User{
ID: "provider1_user1",
Attributes: map[string]interface{}{
Attributes: map[string]any{
"delete_me": true,
},
},
+3 -3
View File
@@ -276,8 +276,8 @@ func TestMigrator_ImportDouble(t *testing.T) {
"picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,
"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"remark42","url":"https://radio-t.com/blah1"},"score":0,
"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"}`
recs := []string{}
for i := 0; i < 50; i++ {
recs := make([]string, 0, 50)
for i := range 50 {
recs = append(recs, fmt.Sprintf(tmpl, i))
}
r := strings.NewReader(`{"version":1}` + strings.Join(recs, "\n")) // reader with 10k records
@@ -329,7 +329,7 @@ func TestMigrator_ImportWaitExpired(t *testing.T) {
"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"}`
nRecs := 50
recs := make([]string, 0, nRecs)
for i := 0; i < nRecs; i++ {
for i := range nRecs {
recs = append(recs, fmt.Sprintf(tmpl, i))
}
r := strings.NewReader(`{"version":1}` + strings.Join(recs, "\n")) // reader with `nRecs` records
+3 -3
View File
@@ -96,12 +96,12 @@ const lastCommentsScope = "last"
type commentsWithInfo struct {
Comments []store.Comment `json:"comments"`
Info store.PostInfo `json:"info,omitempty"`
Info store.PostInfo `json:"info"`
}
type treeWithInfo struct {
*service.Tree
Info store.PostInfo `json:"info,omitempty"`
Info store.PostInfo `json:"info"`
}
// Run the lister and request's router, activate rest server
@@ -504,7 +504,7 @@ func addFileServer(r chi.Router, embedFS embed.FS, webRoot, version string) {
})
}
func encodeJSONWithHTML(v interface{}) ([]byte, error) {
func encodeJSONWithHTML(v any) ([]byte, error) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
+3 -3
View File
@@ -579,7 +579,7 @@ func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
}
// MustExecute behaves like template.Execute, but panics if an error occurs.
MustExecute := func(tmpl *template.Template, wr io.Writer, data interface{}) {
MustExecute := func(tmpl *template.Template, wr io.Writer, data any) {
if err := tmpl.Execute(wr, data); err != nil {
panic(err)
}
@@ -670,7 +670,7 @@ func (s *private) userAllDataCtrl(w http.ResponseWriter, r *http.Request) {
merr = multierror.Append(merr, write([]byte(`, "comments":`))) // send comments prefix
// get comments in 100 in each paginated request
for i := 0; i < 100; i++ {
for i := range 100 {
comments, errUser := s.dataService.User(siteID, user.ID, 100, i*100, rest.GetUserOrEmpty(r))
if errUser != nil {
rest.SendErrorJSON(w, r, http.StatusInternalServerError, errUser, "can't get user comments", rest.ErrInternal)
@@ -711,7 +711,7 @@ func (s *private) deleteMeCtrl(w http.ResponseWriter, r *http.Request) {
User: &token.User{
ID: user.ID,
Name: user.Name,
Attributes: map[string]interface{}{
Attributes: map[string]any{
"delete_me": true, // prevents this token from being used for login
},
},
+4 -4
View File
@@ -49,7 +49,7 @@ func TestRest_Create(t *testing.T) {
c := R.JSON{}
err = json.Unmarshal(b, &c)
assert.NoError(t, err)
loc := c["locator"].(map[string]interface{})
loc := c["locator"].(map[string]any)
assert.Equal(t, "remark42", loc["site"])
assert.Equal(t, "https://radio-t.com/blah1", loc["url"])
assert.True(t, len(c["id"].(string)) > 8)
@@ -71,7 +71,7 @@ func TestRest_CreateFilteredCode(t *testing.T) {
c := R.JSON{}
err = json.Unmarshal(b, &c)
require.NoError(t, err, string(b))
loc := c["locator"].(map[string]interface{})
loc := c["locator"].(map[string]any)
assert.Equal(t, "remark42", loc["site"])
assert.Equal(t, "https://radio-t.com/blah1", loc["url"])
assert.Equal(t, "`foo<bar>`", c["orig"])
@@ -123,7 +123,7 @@ func TestRest_CreateAndPreviewWithImage(t *testing.T) {
require.NoError(t, err, string(b))
assert.NotContains(t, c["text"], pngServer.URL)
assert.Contains(t, c["text"], srv.RemarkURL)
loc := c["locator"].(map[string]interface{})
loc := c["locator"].(map[string]any)
assert.Equal(t, "remark42", loc["site"])
assert.Equal(t, "https://radio-t.com/blah1", loc["url"])
assert.True(t, len(c["id"].(string)) > 8)
@@ -1456,7 +1456,7 @@ func TestRest_UserAllDataManyComments(t *testing.T) {
c := store.Comment{User: user, Text: "test test #1", Locator: store.Locator{SiteID: "remark42",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 5, 27, 1, 14, 10, 0, time.Local)}
for i := 0; i < 51; i++ {
for i := range 51 {
c.ID = fmt.Sprintf("id-%03d", i)
c.Timestamp = c.Timestamp.Add(time.Second)
_, err := srv.DataService.Create(c)
+1 -1
View File
@@ -930,7 +930,7 @@ func TestRest_Config(t *testing.T) {
err := json.Unmarshal([]byte(body), &j)
assert.NoError(t, err)
assert.Equal(t, 300.0, j["edit_duration"])
assert.EqualValues(t, []interface{}{"a1", "a2"}, j["admins"])
assert.EqualValues(t, []any{"a1", "a2"}, j["admins"])
assert.Equal(t, "admin@remark-42.com", j["admin_email"])
assert.Equal(t, 4000.0, j["max_comment_size"])
assert.Equal(t, -5.0, j["low_score"])
+3 -3
View File
@@ -437,7 +437,7 @@ func Test_validEmailAuth(t *testing.T) {
// randomPath pick a file or folder name which is not in use for sure
func randomPath(tempDir, basename, suffix string) (string, error) {
for i := 0; i < 10; i++ {
for range 10 {
fname := fmt.Sprintf("/%s/%s-%d%s", tempDir, basename, rand.Int31(), suffix)
fmt.Printf("fname %q", fname)
_, err := os.Stat(fname)
@@ -667,7 +667,7 @@ func requireAdminOnly(t *testing.T, req *http.Request) {
}
func chooseRandomUnusedPort() (port int) {
for i := 0; i < 10; i++ {
for range 10 {
port = 40000 + int(rand.Int31n(10000))
if ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)); err == nil {
_ = ln.Close()
@@ -679,7 +679,7 @@ func chooseRandomUnusedPort() (port int) {
func waitForHTTPSServerStart(port int) {
// wait for up to 3 seconds for HTTPS server to start
for i := 0; i < 300; i++ {
for range 300 {
time.Sleep(time.Millisecond * 10)
conn, _ := net.DialTimeout("tcp", fmt.Sprintf("localhost:%d", port), time.Millisecond*10)
if conn != nil {
+1 -1
View File
@@ -51,7 +51,7 @@ type errTmplData struct {
// error code is not included in render as it is intended for UI developers and not for the users
func SendErrorHTML(w http.ResponseWriter, r *http.Request, httpStatusCode int, err error, details string, errCode int) {
// MustExecute behaves like template.Execute, but panics if an error occurs.
MustExecute := func(tmpl *template.Template, wr io.Writer, data interface{}) {
MustExecute := func(tmpl *template.Template, wr io.Writer, data any) {
if err = tmpl.Execute(wr, data); err != nil {
panic(err)
}
+1 -1
View File
@@ -212,7 +212,7 @@ func TestImage_RoutesCachingImage(t *testing.T) {
func TestImage_RoutesUsingCachedImage(t *testing.T) {
// in order to validate that cached data used cache "will return" some other data from what http server would
testImage := []byte(fmt.Sprintf("%256s", "X"))
testImage := fmt.Appendf(nil, "%256s", "X")
imageStore := image.StoreMock{LoadFunc: func(string) ([]byte, error) {
return testImage, nil
}}
+1 -1
View File
@@ -56,7 +56,7 @@ func SetUserInfo(r *http.Request, user store.User) *http.Request {
Picture: user.Picture,
IP: user.IP,
Audience: user.SiteID,
Attributes: map[string]interface{}{
Attributes: map[string]any{
"blocked": user.Blocked,
"verified": user.Verified,
},
+2 -2
View File
@@ -50,8 +50,8 @@ type PostInfo struct {
CountLeft int `json:"count_left"` // used only with returning search results limited by number, otherwise zero
LastComment string `json:"last_comment,omitempty"` // used only with returning search results limited by number
ReadOnly bool `json:"read_only,omitempty" bson:"read_only,omitempty"` // can be attached to site-wide comments but won't be set then
FirstTS time.Time `json:"first_time,omitempty" bson:"first_time,omitempty"`
LastTS time.Time `json:"last_time,omitempty" bson:"last_time,omitempty"`
FirstTS time.Time `json:"first_time" bson:"first_time,omitempty"`
LastTS time.Time `json:"last_time" bson:"last_time,omitempty"`
}
// BlockedUser holds id and ts for blocked user
+5 -5
View File
@@ -346,13 +346,13 @@ func (b *BoltDB) Info(req InfoRequest) ([]store.PostInfo, error) {
// ListFlags get list of flagged keys, like blocked & verified user
// works for full locator (post flags) or with userID
func (b *BoltDB) ListFlags(req FlagRequest) (res []interface{}, err error) {
func (b *BoltDB) ListFlags(req FlagRequest) (res []any, err error) {
bdb, e := b.db(req.Locator.SiteID)
if e != nil {
return nil, e
}
res = []interface{}{}
res = []any{}
switch req.Flag {
case Verified:
err = bdb.View(func(tx *bolt.Tx) error {
@@ -957,7 +957,7 @@ func (b *BoltDB) getUserBucket(tx *bolt.Tx, userID string) (*bolt.Bucket, error)
}
// save marshaled value to key for bucket. Should run in update tx
func (b *BoltDB) save(bkt *bolt.Bucket, key string, value interface{}) (err error) {
func (b *BoltDB) save(bkt *bolt.Bucket, key string, value any) (err error) {
if value == nil {
return fmt.Errorf("can't save nil value for %s", key)
}
@@ -972,7 +972,7 @@ func (b *BoltDB) save(bkt *bolt.Bucket, key string, value interface{}) (err erro
}
// load and unmarshal json value by key from bucket. Should run in view tx
func (b *BoltDB) load(bkt *bolt.Bucket, key string, res interface{}) error {
func (b *BoltDB) load(bkt *bolt.Bucket, key string, res any) error {
value := bkt.Get([]byte(key))
if value == nil {
return fmt.Errorf("no value for %s", key)
@@ -1027,7 +1027,7 @@ func (b *BoltDB) db(siteID string) (*bolt.DB, error) {
// makeRef creates reference combining url and comment id
func (b *BoltDB) makeRef(comment store.Comment) []byte {
return []byte(fmt.Sprintf("%s!!%s", comment.Locator.URL, comment.ID))
return fmt.Appendf(nil, "%s!!%s", comment.Locator.URL, comment.ID)
}
// parseRef gets parts of reference
+3 -3
View File
@@ -236,7 +236,7 @@ func TestBoltDB_FindForUserPagination(t *testing.T) {
}
// write 200 comments
for i := 0; i < 200; i++ {
for i := range 200 {
c.ID = fmt.Sprintf("id-%d", i)
c.Text = fmt.Sprintf("text #%d", i)
c.Timestamp = time.Date(2017, 12, 20, 15, 18, i, 0, time.Local)
@@ -540,7 +540,7 @@ func TestBolt_FlagListVerified(t *testing.T) {
b, teardown := prep(t)
defer teardown()
toIDs := func(inp []interface{}) (res []string) {
toIDs := func(inp []any) (res []string) {
res = make([]string, len(inp))
for i, v := range inp {
vv, ok := v.(string)
@@ -580,7 +580,7 @@ func TestBolt_FlagListBlocked(t *testing.T) {
return err
}
toBlocked := func(inp []interface{}) (res []store.BlockedUser) {
toBlocked := func(inp []any) (res []store.BlockedUser) {
res = make([]store.BlockedUser, len(inp))
for i, v := range inp {
vv, ok := v.(store.BlockedUser)
+2 -2
View File
@@ -24,7 +24,7 @@ type Interface interface {
Count(req FindRequest) (int, error) // get count for post or user
Delete(req DeleteRequest) error // Delete post(s), user, comment, user details, or everything
Flag(req FlagRequest) (bool, error) // set and get flags
ListFlags(req FlagRequest) ([]interface{}, error) // get list of flagged keys, like blocked & verified user
ListFlags(req FlagRequest) ([]any, error) // get list of flagged keys, like blocked & verified user
// UserDetail sets or gets single detail value, or gets all details for requested site
// Returns list even for single entry request is a compromise in order to have both single detail getting and setting
@@ -45,7 +45,7 @@ type FindRequest struct {
Locator store.Locator `json:"locator"` // lack of URL means site operation
UserID string `json:"user_id,omitempty"` // presence of UserID treated as user-related find
Sort string `json:"sort,omitempty"` // sort order with +/-field syntax
Since time.Time `json:"since,omitempty"` // time limit for found results
Since time.Time `json:"since"` // time limit for found results
Limit int `json:"limit,omitempty"`
Skip int `json:"skip,omitempty"`
}
+5 -5
View File
@@ -71,24 +71,24 @@ func (r *RPC) Flag(req FlagRequest) (status bool, err error) {
return status, err
}
func unmarshalString(data []byte) ([]interface{}, error) {
func unmarshalString(data []byte) ([]any, error) {
var strings []string
if err := json.Unmarshal(data, &strings); err != nil {
return nil, err
}
list := make([]interface{}, 0, len(strings))
list := make([]any, 0, len(strings))
for _, w := range strings {
list = append(list, w)
}
return list, nil
}
func unmarshalBlockedUser(data []byte) ([]interface{}, error) {
func unmarshalBlockedUser(data []byte) ([]any, error) {
var blockedUsers []store.BlockedUser
if err := json.Unmarshal(data, &blockedUsers); err != nil {
return nil, err
}
list := make([]interface{}, 0, len(blockedUsers))
list := make([]any, 0, len(blockedUsers))
for _, w := range blockedUsers {
list = append(list, w)
}
@@ -96,7 +96,7 @@ func unmarshalBlockedUser(data []byte) ([]interface{}, error) {
}
// ListFlags get list of flagged keys, like blocked & verified user
func (r *RPC) ListFlags(req FlagRequest) ([]interface{}, error) {
func (r *RPC) ListFlags(req FlagRequest) ([]any, error) {
resp, err := r.Call("store.list_flags", req)
if err != nil {
return nil, err
+1 -1
View File
@@ -190,7 +190,7 @@ func TestFsStore_location(t *testing.T) {
}
svc := FileSystem{Location: "/tmp", Partitions: 10}
for i := 0; i < 1000; i++ {
for range 1000 {
v := randomID(rand.Intn(64))
location := svc.location("/tmp", v)
elems := strings.Split(location, "/")
+2 -4
View File
@@ -111,9 +111,7 @@ func (s *Service) Submit(idsFn func() []string) {
s.once.Do(func() {
log.Printf("[DEBUG] image submitter activated")
s.submitCh = make(chan submitReq, submitQueueSize)
s.wg.Add(1)
go func() {
defer s.wg.Done()
s.wg.Go(func() {
for req := range s.submitCh {
// wait for EditDuration expiration with emergency pass on term
for atomic.LoadInt32(&s.term) == 0 && time.Since(req.TS) <= s.EditDuration {
@@ -127,7 +125,7 @@ func (s *Service) Submit(idsFn func() []string) {
atomic.AddInt32(&s.submitCount, -1)
}
log.Printf("[INFO] image submitter terminated")
}()
})
})
atomic.AddInt32(&s.submitCount, 1)
@@ -1,6 +1,7 @@
package service
import (
"slices"
"strings"
"unicode"
"unicode/utf8"
@@ -47,12 +48,7 @@ func (m *RestrictedWordsMatcher) Match(siteID, text string) bool {
tokens := m.tokenize(text)
trie := newWildcardTrie(restrictedWords...)
for _, token := range tokens {
if trie.check(token) {
return true
}
}
return false
return slices.ContainsFunc(tokens, trie.check)
}
func (m *RestrictedWordsMatcher) tokenize(text string) []string {
+2 -7
View File
@@ -62,7 +62,7 @@ type UserMetaData struct {
Until time.Time `json:"until"`
} `json:"blocked"`
Verified bool `json:"verified"`
Details engine.UserDetailEntry `json:"details,omitempty"`
Details engine.UserDetailEntry `json:"details"`
}
// PostMetaData keeps info about post flags
@@ -676,12 +676,7 @@ func (s *DataStore) IsAdmin(siteID, userID string) bool {
log.Printf("[WARN] can't get admins for %s, %v", siteID, err)
return false
}
for _, a := range admins {
if a == userID {
return true
}
}
return false
return slices.Contains(admins, userID)
}
// IsReadOnly checks if post read-only
+10 -17
View File
@@ -438,13 +438,11 @@ func TestService_VoteAggressive(t *testing.T) {
// crazy vote +1 as user1
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for range 1000 {
wg.Go(func() {
_, _ = b.Vote(VoteReq{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, CommentID: res[0].ID,
UserID: "user1", Val: true})
}()
})
}
wg.Wait()
res, err = b.Last("radio-t", 0, time.Time{}, store.User{ID: "user1"})
@@ -458,14 +456,12 @@ func TestService_VoteAggressive(t *testing.T) {
assert.Equal(t, 0, len(res[0].VotedIPs), "vote ips hidden")
// random +1/-1 result should be [0..2]
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for range 100 {
wg.Go(func() {
val := rand.Intn(2) > 0
_, _ = b.Vote(VoteReq{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, CommentID: res[0].ID,
UserID: "user1", Val: val})
}()
})
}
wg.Wait()
res, err = b.Last("radio-t", 0, time.Time{}, store.User{})
@@ -492,14 +488,11 @@ func TestService_VoteConcurrent(t *testing.T) {
// concurrent vote +1 as multiple users for the same comment
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
ii := i
go func() {
defer wg.Done()
for i := range 100 {
wg.Go(func() {
_, _ = b.Vote(VoteReq{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, CommentID: res[0].ID,
UserID: fmt.Sprintf("user1-%d", ii), Val: true})
}()
UserID: fmt.Sprintf("user1-%d", i), Val: true})
})
}
wg.Wait()
res, err = b.Last("radio-t", 0, time.Time{}, store.User{})
+9 -10
View File
@@ -61,7 +61,7 @@ func TestTitle_Get(t *testing.T) {
_, err = ex.Get(ts.URL + "/bad")
require.Error(t, err)
for i := 0; i < 100; i++ {
for range 100 {
r, err := ex.Get(ts.URL + "/good")
require.NoError(t, err)
assert.Equal(t, "blah 123", r)
@@ -70,9 +70,9 @@ func TestTitle_Get(t *testing.T) {
}
func TestTitle_GetConcurrent(t *testing.T) {
body := ""
for n := 0; n < 1000; n++ {
body += "something something blah blah\n"
var body strings.Builder
for range 1000 {
body.WriteString("something something blah blah\n")
}
ex := NewTitleExtractor(http.Client{Timeout: 5 * time.Second}, []string{"127.0.0.1"})
defer ex.Close()
@@ -80,7 +80,7 @@ func TestTitle_GetConcurrent(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.String(), "/good") {
atomic.AddInt32(&hits, 1)
_, err := fmt.Fprintf(w, "<html><title>blah 123 %s</title><body>%s</body></html>", r.URL.String(), body)
_, err := fmt.Fprintf(w, "<html><title>blah 123 %s</title><body>%s</body></html>", r.URL.String(), body.String())
assert.NoError(t, err)
return
}
@@ -90,12 +90,11 @@ func TestTitle_GetConcurrent(t *testing.T) {
g := syncs.NewSizedGroup(10)
for i := 0; i < 100; i++ {
ii := i
for i := range 100 {
g.Go(func(_ context.Context) {
title, err := ex.Get(ts.URL + "/good/" + strconv.Itoa(ii))
title, err := ex.Get(ts.URL + "/good/" + strconv.Itoa(i))
require.NoError(t, err)
assert.Equal(t, "blah 123 "+"/good/"+strconv.Itoa(ii), title)
assert.Equal(t, "blah 123 "+"/good/"+strconv.Itoa(i), title)
})
}
g.Wait()
@@ -115,7 +114,7 @@ func TestTitle_GetFailed(t *testing.T) {
_, err := ex.Get(ts.URL + "/bad")
require.Error(t, err)
for i := 0; i < 100; i++ {
for range 100 {
r, err := ex.Get(ts.URL + "/bad")
require.NoError(t, err)
assert.Equal(t, "", r)