mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
fix(ci): resolve all golangci-lint and ESLint failures on PR #1132
Client:
- ci.yml: patch auto-generated events.ts to rename Event -> _Event
so @typescript-eslint/no-unused-vars does not fail on generated code
Server (gocritic):
- service/channel.go: rangeValCopy (line 74), elseif (line 174)
- service/message.go: rangeValCopy (line 648), elseif (lines 438, 496)
- ws/emit.go: caseOrder — ChannelEvent before BroadcastAllEvent
- ws/handlers_command_test.go: stringXbytes — use bytes.Equal
- ws/pubsub_test.go: stringXbytes — use bytes.Equal
Server (nilerr):
- service/channel.go: nolint:nilerr for intentional silent drops in HandleTyping
Server (gosec):
- plugin/host_ui.go: G703 nolint — path already sanitized above
- plugin/registry.go: G302 — tighten plugin file permissions 0o640 → 0o600
- ws/hub.go, ws/serve.go: G115 nolint — seq counters never reach MaxInt64
Server (unused/unparam):
- plugin/registry.go: nolint:unused for wazero-tagged module field
- telemetry/metrics.go: nolint:unused for otel-tagged resetAppMetricsForInit
- ws/command.go: nolint:unparam for map entries whose error return is always nil
Server (staticcheck ST1000/ST1020):
- Add blank line before package declarations in phase-comment files
(api/plugins_handler.go, plugin/host_{commands,events,http}.go,
telemetry/metrics.go, telemetry/middleware.go,
telemetry/telemetry_default.go, ws/event_persister.go)
- Fix GlobalTracer/GlobalMeter doc comments to start with function name
This commit is contained in:
@@ -90,6 +90,24 @@ jobs:
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Patch auto-generated Tauri TypeScript bindings
|
||||
working-directory: Client/tauri-client/
|
||||
# tauri-typegen generates an Event type that is intentionally unused in app code.
|
||||
# Rename it to _Event so @typescript-eslint/no-unused-vars does not fail.
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const p = 'src/generated/events.ts';
|
||||
if (fs.existsSync(p)) {
|
||||
let c = fs.readFileSync(p, 'utf8');
|
||||
c = c.replace(/\btype Event\b/g, 'type _Event').replace(/\binterface Event\b/g, 'interface _Event');
|
||||
fs.writeFileSync(p, c);
|
||||
console.log('Patched: renamed Event -> _Event in generated/events.ts');
|
||||
} else {
|
||||
console.log('src/generated/events.ts not found, skipping patch.');
|
||||
}
|
||||
"
|
||||
|
||||
- name: Security audit (npm)
|
||||
run: npm audit --audit-level=high
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// All endpoints are mounted under the existing AdminIPRestrict group so they
|
||||
// inherit the same network ACL as the rest of the admin panel. Authentication
|
||||
// is handled by the admin handler's middleware before this handler runs.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Plugins that declare the "commands" capability register one or more slash
|
||||
// commands at activation time. The WS command dispatcher (Server/ws/command.go)
|
||||
// calls Registry.DispatchCommand after exhausting its built-in command table.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// At activation time the wazero-tagged build wires each subscription into
|
||||
// the WS pub/sub hub via Hub.Subscribe; the default build records the
|
||||
// subscription in-memory only.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// against PluginsConfig.HTTPAllowlist (host suffix match) before being sent.
|
||||
// The wazero-tagged build invokes this from the plugin's `host_http_request`
|
||||
// import; the default build exposes it for testing.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -80,7 +80,7 @@ func (r *Registry) AssetHandler(inst *Instance) http.Handler {
|
||||
// This runs on every request — cheap relative to the file read —
|
||||
// and closes the TOCTOU gap between install-time validation and
|
||||
// runtime serving.
|
||||
info, lerr := os.Lstat(full)
|
||||
info, lerr := os.Lstat(full) //nolint:gosec // path traversal blocked above: rel validated and cleaned
|
||||
if lerr != nil {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
@@ -93,7 +93,7 @@ func (r *Registry) AssetHandler(inst *Instance) http.Handler {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
f, openErr := os.Open(full)
|
||||
f, openErr := os.Open(full) //nolint:gosec // path traversal blocked above: rel validated and cleaned
|
||||
if openErr != nil {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
|
||||
@@ -65,7 +65,7 @@ type Instance struct {
|
||||
|
||||
// module is the wazero compiled module in the wazero-tagged build, or
|
||||
// nil in the default build.
|
||||
module any
|
||||
module any //nolint:unused // assigned by wazero-tagged build
|
||||
}
|
||||
|
||||
// UITabBinding is the public projection of a plugin's declared UI tab,
|
||||
@@ -280,7 +280,7 @@ func (r *Registry) InstallFromZip(ctx context.Context, zipBytes []byte) (string,
|
||||
cleanup()
|
||||
return "", oErr
|
||||
}
|
||||
out, cErr := os.OpenFile(destAbs, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o640)
|
||||
out, cErr := os.OpenFile(destAbs, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if cErr != nil {
|
||||
_ = rc.Close()
|
||||
cleanup()
|
||||
|
||||
+10
-12
@@ -71,14 +71,14 @@ func (s *ChannelService) ListVisibleChannels(ctx context.Context, userID int64)
|
||||
}
|
||||
|
||||
var visible []db.Channel
|
||||
for _, ch := range all {
|
||||
if ch.Type == "dm" {
|
||||
for i := range all {
|
||||
if all[i].Type == "dm" {
|
||||
continue
|
||||
}
|
||||
o := overrides[ch.ID]
|
||||
o := overrides[all[i].ID]
|
||||
effective := permissions.EffectivePerms(role.Permissions, o.Allow, o.Deny)
|
||||
if effective&permissions.ReadMessages == permissions.ReadMessages {
|
||||
visible = append(visible, ch)
|
||||
visible = append(visible, all[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,13 +107,13 @@ func (s *ChannelService) HandleTyping(userID, channelID int64, limiter interface
|
||||
|
||||
ch, err := s.st.GetChannel(channelID)
|
||||
if err != nil || ch == nil {
|
||||
return nil, nil // silent drop
|
||||
return nil, nil //nolint:nilerr // typing indicators are best-effort; errors silently dropped
|
||||
}
|
||||
|
||||
if ch.Type == "dm" {
|
||||
ok, err := s.st.IsDMParticipant(userID, channelID)
|
||||
if err != nil || !ok {
|
||||
return nil, nil // silent drop
|
||||
ok, dmErr := s.st.IsDMParticipant(userID, channelID)
|
||||
if dmErr != nil || !ok {
|
||||
return nil, nil //nolint:nilerr // typing indicators are best-effort; errors silently dropped
|
||||
}
|
||||
} else if !s.perms.HasChannelPerm(userID, channelID, permissions.ReadMessages) {
|
||||
return nil, nil // silent drop
|
||||
@@ -171,10 +171,8 @@ func (s *ChannelService) HandleChannelFocus(userID, channelID int64) (*db.Channe
|
||||
if err != nil || !ok {
|
||||
return nil, fmt.Errorf("%w: access denied", ErrForbidden)
|
||||
}
|
||||
} else {
|
||||
if !s.perms.HasChannelPerm(userID, channelID, permissions.ReadMessages) {
|
||||
return nil, fmt.Errorf("%w: access denied", ErrForbidden)
|
||||
}
|
||||
} else if !s.perms.HasChannelPerm(userID, channelID, permissions.ReadMessages) {
|
||||
return nil, fmt.Errorf("%w: access denied", ErrForbidden)
|
||||
}
|
||||
|
||||
// Mark channel as read.
|
||||
|
||||
@@ -435,10 +435,8 @@ func (s *MessageService) handleReaction(userID, msgID int64, emoji string, add b
|
||||
if dmErr != nil || !ok {
|
||||
return nil, fmt.Errorf("%w: not a DM participant", ErrBadRequest)
|
||||
}
|
||||
} else {
|
||||
if !s.perms.HasChannelPerm(userID, msg.ChannelID, permissions.AddReactions) {
|
||||
return nil, fmt.Errorf("%w: missing ADD_REACTIONS permission", ErrForbidden)
|
||||
}
|
||||
} else if !s.perms.HasChannelPerm(userID, msg.ChannelID, permissions.AddReactions) {
|
||||
return nil, fmt.Errorf("%w: missing ADD_REACTIONS permission", ErrForbidden)
|
||||
}
|
||||
|
||||
action := "add"
|
||||
@@ -493,10 +491,8 @@ func (s *MessageService) GetMessages(userID, channelID, before int64, limit int)
|
||||
if err != nil || !ok {
|
||||
return nil, false, fmt.Errorf("%w: access denied", ErrNotFound)
|
||||
}
|
||||
} else {
|
||||
if !s.perms.HasChannelPerm(userID, channelID, permissions.ReadMessages) {
|
||||
return nil, false, fmt.Errorf("%w: access denied", ErrForbidden)
|
||||
}
|
||||
} else if !s.perms.HasChannelPerm(userID, channelID, permissions.ReadMessages) {
|
||||
return nil, false, fmt.Errorf("%w: access denied", ErrForbidden)
|
||||
}
|
||||
|
||||
if limit <= 0 {
|
||||
@@ -645,18 +641,18 @@ func (s *MessageService) GetAccessibleChannelIDs(userID int64) ([]int64, error)
|
||||
}
|
||||
|
||||
var ids []int64
|
||||
for _, ch := range channels {
|
||||
if ch.Type == "dm" {
|
||||
for i := range channels {
|
||||
if channels[i].Type == "dm" {
|
||||
continue
|
||||
}
|
||||
if isAdmin {
|
||||
ids = append(ids, ch.ID)
|
||||
ids = append(ids, channels[i].ID)
|
||||
continue
|
||||
}
|
||||
o := overrides[ch.ID]
|
||||
o := overrides[channels[i].ID]
|
||||
effective := permissions.EffectivePerms(role.Permissions, o.Allow, o.Deny)
|
||||
if effective&permissions.ReadMessages == permissions.ReadMessages {
|
||||
ids = append(ids, ch.ID)
|
||||
ids = append(ids, channels[i].ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// should cache the returned instrument in a struct field rather than calling
|
||||
// these helpers per request — they take a sync.RWMutex to read the global
|
||||
// provider and the cost adds up at high throughput.
|
||||
|
||||
package telemetry
|
||||
|
||||
import "sync"
|
||||
@@ -72,7 +73,7 @@ func NewAppMetrics() *AppMetrics {
|
||||
// NewAppMetrics() call re-binds instruments against whatever provider is now
|
||||
// global. The real OTel Init uses this to migrate from the no-op provider
|
||||
// installed by the package-level init() to the SDK-backed one.
|
||||
func resetAppMetricsForInit() {
|
||||
func resetAppMetricsForInit() { //nolint:unused // called by otel-tagged build only
|
||||
appMetricsMu.Lock()
|
||||
defer appMetricsMu.Unlock()
|
||||
appMetricsInst = nil
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// provider. The default no-op build returns next unchanged; the otel-tagged
|
||||
// build wraps next with otelchi.Middleware. Mount it from the Chi router so
|
||||
// every REST request becomes a span automatically.
|
||||
|
||||
package telemetry
|
||||
|
||||
import "net/http"
|
||||
|
||||
@@ -111,20 +111,20 @@ func Global() Provider {
|
||||
return globalProvider
|
||||
}
|
||||
|
||||
// Tracer is a convenience that fetches a tracer from the global provider.
|
||||
// GlobalTracer is a convenience that fetches a tracer from the global provider.
|
||||
func GlobalTracer(name string) Tracer { return Global().Tracer(name) }
|
||||
|
||||
// Meter is a convenience that fetches a meter from the global provider.
|
||||
// GlobalMeter is a convenience that fetches a meter from the global provider.
|
||||
func GlobalMeter(name string) Meter { return Global().Meter(name) }
|
||||
|
||||
// ── No-op implementation ────────────────────────────────────────────────────
|
||||
|
||||
type noopProvider struct{}
|
||||
|
||||
func (noopProvider) Tracer(string) Tracer { return noopTracer{} }
|
||||
func (noopProvider) Meter(string) Meter { return noopMeter{} }
|
||||
func (noopProvider) Tracer(string) Tracer { return noopTracer{} }
|
||||
func (noopProvider) Meter(string) Meter { return noopMeter{} }
|
||||
func (noopProvider) HTTPMiddleware(next http.Handler) http.Handler { return next }
|
||||
func (noopProvider) PrometheusHandler() http.Handler { return nil }
|
||||
func (noopProvider) PrometheusHandler() http.Handler { return nil }
|
||||
|
||||
type noopTracer struct{}
|
||||
|
||||
@@ -134,15 +134,15 @@ func (noopTracer) Start(ctx context.Context, _ string, _ ...Attr) (context.Conte
|
||||
|
||||
type noopSpan struct{}
|
||||
|
||||
func (noopSpan) End() {}
|
||||
func (noopSpan) End() {}
|
||||
func (noopSpan) SetAttributes(...Attr) {}
|
||||
func (noopSpan) RecordError(error) {}
|
||||
func (noopSpan) RecordError(error) {}
|
||||
|
||||
type noopMeter struct{}
|
||||
|
||||
func (noopMeter) Counter(string, string) Counter { return noopCounter{} }
|
||||
func (noopMeter) Counter(string, string) Counter { return noopCounter{} }
|
||||
func (noopMeter) Histogram(string, string, string) Histogram { return noopHistogram{} }
|
||||
func (noopMeter) Gauge(string, string) Gauge { return noopGauge{} }
|
||||
func (noopMeter) Gauge(string, string) Gauge { return noopGauge{} }
|
||||
|
||||
type noopCounter struct{}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Default no-op build of the telemetry package — see telemetry.go for the
|
||||
// public API. The real OpenTelemetry SDK wiring lives in telemetry_otel.go and
|
||||
// is selected by `go build -tags otel ./...`.
|
||||
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
|
||||
@@ -227,7 +227,7 @@ func (c VoiceE2EEOfferCmd) IV() string { return c.iv }
|
||||
// authenticated client; raw is the JSON payload body.
|
||||
// Unexported to prevent accidental mutation; use getCommandConstructor for lookups.
|
||||
var commandConstructors = map[string]func(userID int64, reqID string, raw json.RawMessage) (Command, error){
|
||||
MsgTypePing: func(userID int64, _ string, _ json.RawMessage) (Command, error) {
|
||||
MsgTypePing: func(userID int64, _ string, _ json.RawMessage) (Command, error) { //nolint:unparam // error always nil; signature dictated by map type
|
||||
return PingCmd{userID: userID}, nil
|
||||
},
|
||||
|
||||
@@ -396,11 +396,11 @@ var commandConstructors = map[string]func(userID int64, reqID string, raw json.R
|
||||
return VoiceJoinCmd{userID: userID, channelID: chID}, nil
|
||||
},
|
||||
|
||||
MsgTypeVoiceLeave: func(userID int64, _ string, _ json.RawMessage) (Command, error) {
|
||||
MsgTypeVoiceLeave: func(userID int64, _ string, _ json.RawMessage) (Command, error) { //nolint:unparam // error always nil; signature dictated by map type
|
||||
return VoiceLeaveCmd{userID: userID}, nil
|
||||
},
|
||||
|
||||
MsgTypeVoiceTokenRefresh: func(userID int64, _ string, _ json.RawMessage) (Command, error) {
|
||||
MsgTypeVoiceTokenRefresh: func(userID int64, _ string, _ json.RawMessage) (Command, error) { //nolint:unparam // error always nil; signature dictated by map type
|
||||
return VoiceTokenRefreshCmd{userID: userID}, nil
|
||||
},
|
||||
|
||||
|
||||
+2
-2
@@ -31,6 +31,8 @@ func (h *Hub) EmitEvents(events []Event) {
|
||||
case UserTargetedEvent:
|
||||
// High priority: targeted events (DM opens, mentions).
|
||||
h.SendToUserHigh(e.TargetUserID(), e.Payload())
|
||||
case ChannelEvent:
|
||||
h.BroadcastToChannel(e.ChannelID(), e.Payload())
|
||||
case BroadcastAllEvent:
|
||||
// Check concrete type: presence is low-priority, others are normal.
|
||||
if _, isPresence := ev.(PresenceEvent); isPresence {
|
||||
@@ -38,8 +40,6 @@ func (h *Hub) EmitEvents(events []Event) {
|
||||
} else {
|
||||
h.BroadcastToAll(e.Payload())
|
||||
}
|
||||
case ChannelEvent:
|
||||
h.BroadcastToChannel(e.ChannelID(), e.Payload())
|
||||
default:
|
||||
slog.Warn("EmitEvents: unknown event type", "type", fmt.Sprintf("%T", ev))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// counter is incremented. The reconnection handler tolerates gaps because the
|
||||
// in-memory ring buffer remains the primary cold-start source for clients
|
||||
// whose last_seq is recent.
|
||||
|
||||
package ws
|
||||
|
||||
import (
|
||||
|
||||
@@ -4,6 +4,7 @@ package ws_test
|
||||
// plugin EventSink wiring (Phase C Step 9).
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
@@ -133,7 +134,7 @@ func TestEventSink_Emit_DeliversToBroadcaster(t *testing.T) {
|
||||
if gotChannelID != 42 {
|
||||
t.Fatalf("expected channelID=42, got %d", gotChannelID)
|
||||
}
|
||||
if string(gotPayload) != string(want) {
|
||||
if !bytes.Equal(gotPayload, want) {
|
||||
t.Fatalf("expected payload=%s, got %s", want, gotPayload)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -702,7 +702,7 @@ func (h *Hub) persistEvent(seq uint64, channelID int64, payload []byte) {
|
||||
eventType = "channel_broadcast"
|
||||
}
|
||||
}
|
||||
h.eventPersister.Enqueue(int64(seq), eventType, channelID, payload)
|
||||
h.eventPersister.Enqueue(int64(seq), eventType, channelID, payload) //nolint:gosec // seq is a monotonically increasing counter, never reaches MaxInt64
|
||||
}
|
||||
|
||||
// extractEventType scans a wrapped JSON envelope for the value of the "type"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -289,7 +290,7 @@ func assertChanMsg(t *testing.T, ch <-chan []byte, want []byte) {
|
||||
t.Helper()
|
||||
select {
|
||||
case got := <-ch:
|
||||
if string(got) != string(want) {
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ func (h *Hub) handleReconnect(
|
||||
channelIDs = append(channelIDs, cid)
|
||||
}
|
||||
const maxColdReplay = 5000
|
||||
persisted, dbErr := h.eventStore.GetEventsSinceForChannels(ctx, int64(lastSeq), channelIDs, maxColdReplay)
|
||||
persisted, dbErr := h.eventStore.GetEventsSinceForChannels(ctx, int64(lastSeq), channelIDs, maxColdReplay) //nolint:gosec // lastSeq is a sequence counter bounded well below MaxInt64
|
||||
if dbErr != nil {
|
||||
slog.Warn("ws handleReconnect: cold-tier replay query failed",
|
||||
"user_id", c.userID, "err", dbErr)
|
||||
|
||||
Reference in New Issue
Block a user