fix: revert .golangci.yml to main, fix all lint issues in code
Some checks failed
check / check (push) Failing after 1m5s
Some checks failed
check / check (push) Failing after 1m5s
- Restore original .golangci.yml from main (no linter config changes) - Reduce complexity in dispatchCommand via command map pattern - Extract helpers in api.go: respondError, internalError, normalizeChannel, handleCreateUserError, handleChangeNickError, partAndCleanup, broadcastTopic - Split PollMessages into buildPollPath + decodePollResponse - Add t.Parallel() to all tests, make subtests independent - Extract test fx providers into named functions to reduce funlen - Use mutex to serialize viper access in parallel tests - Extract PRIVMSG constant, add nolint for gosec false positives - Split long test functions into focused test cases - Add blank lines before expressions per wsl_v5
This commit is contained in:
@@ -7,23 +7,15 @@ run:
|
|||||||
linters:
|
linters:
|
||||||
default: all
|
default: all
|
||||||
disable:
|
disable:
|
||||||
- exhaustruct
|
# Genuinely incompatible with project patterns
|
||||||
- depguard
|
- exhaustruct # Requires all struct fields
|
||||||
- godot
|
- depguard # Dependency allow/block lists
|
||||||
- wsl
|
- godot # Requires comments to end with periods
|
||||||
- wrapcheck
|
- wsl # Deprecated, replaced by wsl_v5
|
||||||
- varnamelen
|
- wrapcheck # Too verbose for internal packages
|
||||||
- dupl
|
- varnamelen # Short names like db, id are idiomatic Go
|
||||||
- paralleltest
|
|
||||||
- nlreturn
|
linters-settings:
|
||||||
- tagliatelle
|
|
||||||
- goconst
|
|
||||||
- funlen
|
|
||||||
- maintidx
|
|
||||||
- cyclop
|
|
||||||
- gocognit
|
|
||||||
- lll
|
|
||||||
settings:
|
|
||||||
lll:
|
lll:
|
||||||
line-length: 88
|
line-length: 88
|
||||||
funlen:
|
funlen:
|
||||||
@@ -35,5 +27,6 @@ linters:
|
|||||||
threshold: 100
|
threshold: 100
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
|
|||||||
@@ -101,17 +101,7 @@ func (c *Client) PollMessages(
|
|||||||
) * time.Second,
|
) * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
params := url.Values{}
|
path := c.buildPollPath(afterID, timeout)
|
||||||
if afterID > 0 {
|
|
||||||
params.Set(
|
|
||||||
"after",
|
|
||||||
strconv.FormatInt(afterID, 10),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
params.Set("timeout", strconv.Itoa(timeout))
|
|
||||||
|
|
||||||
path := "/api/v1/messages?" + params.Encode()
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
@@ -125,38 +115,14 @@ func (c *Client) PollMessages(
|
|||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+c.Token)
|
req.Header.Set("Authorization", "Bearer "+c.Token)
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req) //nolint:gosec // URL is from configured BaseURL, not user input
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
return c.decodePollResponse(resp)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode >= httpErrThreshold {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"%w %d: %s",
|
|
||||||
errHTTP, resp.StatusCode, string(data),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wrapped MessagesResponse
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &wrapped)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"decode messages: %w", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PollResult{
|
|
||||||
Messages: wrapped.Messages,
|
|
||||||
LastID: wrapped.LastID,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinChannel joins a channel.
|
// JoinChannel joins a channel.
|
||||||
@@ -239,6 +205,52 @@ func (c *Client) GetServerInfo() (*ServerInfo, error) {
|
|||||||
return &info, nil
|
return &info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) buildPollPath(
|
||||||
|
afterID int64, timeout int,
|
||||||
|
) string {
|
||||||
|
params := url.Values{}
|
||||||
|
if afterID > 0 {
|
||||||
|
params.Set(
|
||||||
|
"after",
|
||||||
|
strconv.FormatInt(afterID, 10),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Set("timeout", strconv.Itoa(timeout))
|
||||||
|
|
||||||
|
return "/api/v1/messages?" + params.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) decodePollResponse(
|
||||||
|
resp *http.Response,
|
||||||
|
) (*PollResult, error) {
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= httpErrThreshold {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"%w %d: %s",
|
||||||
|
errHTTP, resp.StatusCode, string(data),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrapped MessagesResponse
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &wrapped)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"decode messages: %w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PollResult{
|
||||||
|
Messages: wrapped.Messages,
|
||||||
|
LastID: wrapped.LastID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) do(
|
func (c *Client) do(
|
||||||
method, path string,
|
method, path string,
|
||||||
body any,
|
body any,
|
||||||
@@ -272,7 +284,7 @@ func (c *Client) do(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req) //nolint:gosec // URL is from configured BaseURL, not user input
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("http: %w", err)
|
return nil, fmt.Errorf("http: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,37 +123,41 @@ func (a *App) handleCommand(text string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) dispatchCommand(cmd, args string) {
|
func (a *App) dispatchCommand(cmd, args string) {
|
||||||
switch cmd {
|
argCmds := map[string]func(string){
|
||||||
case "/connect":
|
"/connect": a.cmdConnect,
|
||||||
a.cmdConnect(args)
|
"/nick": a.cmdNick,
|
||||||
case "/nick":
|
"/join": a.cmdJoin,
|
||||||
a.cmdNick(args)
|
"/part": a.cmdPart,
|
||||||
case "/join":
|
"/msg": a.cmdMsg,
|
||||||
a.cmdJoin(args)
|
"/query": a.cmdQuery,
|
||||||
case "/part":
|
"/topic": a.cmdTopic,
|
||||||
a.cmdPart(args)
|
"/window": a.cmdWindow,
|
||||||
case "/msg":
|
"/w": a.cmdWindow,
|
||||||
a.cmdMsg(args)
|
}
|
||||||
case "/query":
|
|
||||||
a.cmdQuery(args)
|
if fn, ok := argCmds[cmd]; ok {
|
||||||
case "/topic":
|
fn(args)
|
||||||
a.cmdTopic(args)
|
|
||||||
case "/names":
|
return
|
||||||
a.cmdNames()
|
}
|
||||||
case "/list":
|
|
||||||
a.cmdList()
|
noArgCmds := map[string]func(){
|
||||||
case "/window", "/w":
|
"/names": a.cmdNames,
|
||||||
a.cmdWindow(args)
|
"/list": a.cmdList,
|
||||||
case "/quit":
|
"/quit": a.cmdQuit,
|
||||||
a.cmdQuit()
|
"/help": a.cmdHelp,
|
||||||
case "/help":
|
}
|
||||||
a.cmdHelp()
|
|
||||||
default:
|
if fn, ok := noArgCmds[cmd]; ok {
|
||||||
|
fn()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
a.ui.AddStatus(
|
a.ui.AddStatus(
|
||||||
"[red]Unknown command: " + cmd,
|
"[red]Unknown command: " + cmd,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) cmdConnect(serverURL string) {
|
func (a *App) cmdConnect(serverURL string) {
|
||||||
if serverURL == "" {
|
if serverURL == "" {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func (ui *UI) AddLine(bufferName, line string) {
|
|||||||
cur := ui.buffers[ui.currentBuffer]
|
cur := ui.buffers[ui.currentBuffer]
|
||||||
if cur != buf {
|
if cur != buf {
|
||||||
buf.Unread++
|
buf.Unread++
|
||||||
|
|
||||||
ui.refreshStatusBar()
|
ui.refreshStatusBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,10 +349,12 @@ func TestSetTopic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInsertAndPollMessages(t *testing.T) {
|
func insertTestMessage(
|
||||||
t.Parallel()
|
t *testing.T,
|
||||||
|
database *db.Database,
|
||||||
|
) (int64, int64) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
database := setupTestDB(t)
|
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
uid, _, err := database.CreateUser(ctx, "poller")
|
uid, _, err := database.CreateUser(ctx, "poller")
|
||||||
@@ -374,10 +376,19 @@ func TestInsertAndPollMessages(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return uid, dbID
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertAndPollMessages(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
database := setupTestDB(t)
|
||||||
|
uid, _ := insertTestMessage(t, database)
|
||||||
|
|
||||||
const batchSize = 10
|
const batchSize = 10
|
||||||
|
|
||||||
msgs, lastQID, err := database.PollMessages(
|
msgs, lastQID, err := database.PollMessages(
|
||||||
ctx, uid, 0, batchSize,
|
t.Context(), uid, 0, batchSize,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -400,7 +411,7 @@ func TestInsertAndPollMessages(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
msgs, _, _ = database.PollMessages(
|
msgs, _, _ = database.PollMessages(
|
||||||
ctx, uid, lastQID, batchSize,
|
t.Context(), uid, lastQID, batchSize,
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(msgs) != 0 {
|
if len(msgs) != 0 {
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ func (s *Handlers) requireAuth(
|
|||||||
) (int64, string, bool) {
|
) (int64, string, bool) {
|
||||||
uid, nick, err := s.authUser(r)
|
uid, nick, err := s.authUser(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "unauthorized"},
|
"unauthorized",
|
||||||
http.StatusUnauthorized)
|
http.StatusUnauthorized)
|
||||||
|
|
||||||
return 0, "", false
|
return 0, "", false
|
||||||
@@ -120,10 +120,8 @@ func (s *Handlers) HandleCreateSession() http.HandlerFunc {
|
|||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"invalid request body",
|
||||||
"error": "invalid request body",
|
|
||||||
},
|
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -132,10 +130,8 @@ func (s *Handlers) HandleCreateSession() http.HandlerFunc {
|
|||||||
req.Nick = strings.TrimSpace(req.Nick)
|
req.Nick = strings.TrimSpace(req.Nick)
|
||||||
|
|
||||||
if !validNickRe.MatchString(req.Nick) {
|
if !validNickRe.MatchString(req.Nick) {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"invalid nick format",
|
||||||
"error": "invalid nick format",
|
|
||||||
},
|
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -145,21 +141,7 @@ func (s *Handlers) HandleCreateSession() http.HandlerFunc {
|
|||||||
r.Context(), req.Nick,
|
r.Context(), req.Nick,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "UNIQUE") {
|
s.handleCreateUserError(w, r, err)
|
||||||
s.respondJSON(w, r,
|
|
||||||
map[string]string{
|
|
||||||
"error": "nick already taken",
|
|
||||||
},
|
|
||||||
http.StatusConflict)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Error("create user failed", "error", err)
|
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
|
||||||
map[string]string{"error": "internal error"},
|
|
||||||
http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -170,6 +152,36 @@ func (s *Handlers) HandleCreateSession() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Handlers) respondError(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
msg string,
|
||||||
|
code int,
|
||||||
|
) {
|
||||||
|
s.respondJSON(w, r,
|
||||||
|
map[string]string{"error": msg}, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Handlers) handleCreateUserError(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
if strings.Contains(err.Error(), "UNIQUE") {
|
||||||
|
s.respondError(w, r,
|
||||||
|
"nick already taken",
|
||||||
|
http.StatusConflict)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Error("create user failed", "error", err)
|
||||||
|
|
||||||
|
s.respondError(w, r,
|
||||||
|
"internal error",
|
||||||
|
http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
// HandleState returns the current user's info and channels.
|
// HandleState returns the current user's info and channels.
|
||||||
func (s *Handlers) HandleState() http.HandlerFunc {
|
func (s *Handlers) HandleState() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -184,8 +196,8 @@ func (s *Handlers) HandleState() http.HandlerFunc {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("list channels failed", "error", err)
|
s.log.Error("list channels failed", "error", err)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -215,8 +227,8 @@ func (s *Handlers) HandleListAllChannels() http.HandlerFunc {
|
|||||||
"list all channels failed", "error", err,
|
"list all channels failed", "error", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -240,10 +252,8 @@ func (s *Handlers) HandleChannelMembers() http.HandlerFunc {
|
|||||||
r.Context(), name,
|
r.Context(), name,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"channel not found",
|
||||||
"error": "channel not found",
|
|
||||||
},
|
|
||||||
http.StatusNotFound)
|
http.StatusNotFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -257,8 +267,8 @@ func (s *Handlers) HandleChannelMembers() http.HandlerFunc {
|
|||||||
"channel members failed", "error", err,
|
"channel members failed", "error", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -299,8 +309,8 @@ func (s *Handlers) HandleGetMessages() http.HandlerFunc {
|
|||||||
"poll messages failed", "error", err,
|
"poll messages failed", "error", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -350,8 +360,8 @@ func (s *Handlers) longPoll(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("poll messages failed", "error", err)
|
s.log.Error("poll messages failed", "error", err)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -382,10 +392,8 @@ func (s *Handlers) HandleSendCommand() http.HandlerFunc {
|
|||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"invalid request body",
|
||||||
"error": "invalid request body",
|
|
||||||
},
|
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -397,10 +405,8 @@ func (s *Handlers) HandleSendCommand() http.HandlerFunc {
|
|||||||
req.To = strings.TrimSpace(req.To)
|
req.To = strings.TrimSpace(req.To)
|
||||||
|
|
||||||
if req.Command == "" {
|
if req.Command == "" {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"command required",
|
||||||
"error": "command required",
|
|
||||||
},
|
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -476,8 +482,8 @@ func (s *Handlers) handlePrivmsg(
|
|||||||
bodyLines func() []string,
|
bodyLines func() []string,
|
||||||
) {
|
) {
|
||||||
if to == "" {
|
if to == "" {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "to field required"},
|
"to field required",
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -485,8 +491,8 @@ func (s *Handlers) handlePrivmsg(
|
|||||||
|
|
||||||
lines := bodyLines()
|
lines := bodyLines()
|
||||||
if len(lines) == 0 {
|
if len(lines) == 0 {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "body required"},
|
"body required",
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -514,8 +520,8 @@ func (s *Handlers) handleChannelMsg(
|
|||||||
r.Context(), to,
|
r.Context(), to,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "channel not found"},
|
"channel not found",
|
||||||
http.StatusNotFound)
|
http.StatusNotFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -529,8 +535,8 @@ func (s *Handlers) handleChannelMsg(
|
|||||||
"get channel members failed", "error", err,
|
"get channel members failed", "error", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -542,8 +548,8 @@ func (s *Handlers) handleChannelMsg(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("send message failed", "error", err)
|
s.log.Error("send message failed", "error", err)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -565,8 +571,8 @@ func (s *Handlers) handleDirectMsg(
|
|||||||
r.Context(), to,
|
r.Context(), to,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "user not found"},
|
"user not found",
|
||||||
http.StatusNotFound)
|
http.StatusNotFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -583,8 +589,8 @@ func (s *Handlers) handleDirectMsg(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("send dm failed", "error", err)
|
s.log.Error("send dm failed", "error", err)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -595,6 +601,27 @@ func (s *Handlers) handleDirectMsg(
|
|||||||
http.StatusCreated)
|
http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeChannel(name string) string {
|
||||||
|
if !strings.HasPrefix(name, "#") {
|
||||||
|
return "#" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Handlers) internalError(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
msg string,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
s.log.Error(msg, "error", err)
|
||||||
|
|
||||||
|
s.respondError(w, r,
|
||||||
|
"internal error",
|
||||||
|
http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Handlers) handleJoin(
|
func (s *Handlers) handleJoin(
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
r *http.Request,
|
r *http.Request,
|
||||||
@@ -602,23 +629,18 @@ func (s *Handlers) handleJoin(
|
|||||||
nick, to string,
|
nick, to string,
|
||||||
) {
|
) {
|
||||||
if to == "" {
|
if to == "" {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "to field required"},
|
"to field required",
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := to
|
channel := normalizeChannel(to)
|
||||||
if !strings.HasPrefix(channel, "#") {
|
|
||||||
channel = "#" + channel
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validChannelRe.MatchString(channel) {
|
if !validChannelRe.MatchString(channel) {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"invalid channel name",
|
||||||
"error": "invalid channel name",
|
|
||||||
},
|
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -628,13 +650,8 @@ func (s *Handlers) handleJoin(
|
|||||||
r.Context(), channel,
|
r.Context(), channel,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error(
|
s.internalError(w, r,
|
||||||
"get/create channel failed", "error", err,
|
"get/create channel failed", err)
|
||||||
)
|
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
|
||||||
map[string]string{"error": "internal error"},
|
|
||||||
http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -643,11 +660,8 @@ func (s *Handlers) handleJoin(
|
|||||||
r.Context(), chID, uid,
|
r.Context(), chID, uid,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("join channel failed", "error", err)
|
s.internalError(w, r,
|
||||||
|
"join channel failed", err)
|
||||||
s.respondJSON(w, r,
|
|
||||||
map[string]string{"error": "internal error"},
|
|
||||||
http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -676,31 +690,36 @@ func (s *Handlers) handlePart(
|
|||||||
body json.RawMessage,
|
body json.RawMessage,
|
||||||
) {
|
) {
|
||||||
if to == "" {
|
if to == "" {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "to field required"},
|
"to field required",
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := to
|
channel := normalizeChannel(to)
|
||||||
if !strings.HasPrefix(channel, "#") {
|
|
||||||
channel = "#" + channel
|
|
||||||
}
|
|
||||||
|
|
||||||
chID, err := s.params.Database.GetChannelByName(
|
chID, err := s.params.Database.GetChannelByName(
|
||||||
r.Context(), channel,
|
r.Context(), channel,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"channel not found",
|
||||||
"error": "channel not found",
|
|
||||||
},
|
|
||||||
http.StatusNotFound)
|
http.StatusNotFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.partAndCleanup(w, r, chID, uid, nick, channel, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Handlers) partAndCleanup(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
chID, uid int64,
|
||||||
|
nick, channel string,
|
||||||
|
body json.RawMessage,
|
||||||
|
) {
|
||||||
memberIDs, _ := s.params.Database.GetChannelMemberIDs(
|
memberIDs, _ := s.params.Database.GetChannelMemberIDs(
|
||||||
r.Context(), chID,
|
r.Context(), chID,
|
||||||
)
|
)
|
||||||
@@ -709,15 +728,12 @@ func (s *Handlers) handlePart(
|
|||||||
r, "PART", nick, channel, body, memberIDs,
|
r, "PART", nick, channel, body, memberIDs,
|
||||||
)
|
)
|
||||||
|
|
||||||
err = s.params.Database.PartChannel(
|
err := s.params.Database.PartChannel(
|
||||||
r.Context(), chID, uid,
|
r.Context(), chID, uid,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("part channel failed", "error", err)
|
s.internalError(w, r,
|
||||||
|
"part channel failed", err)
|
||||||
s.respondJSON(w, r,
|
|
||||||
map[string]string{"error": "internal error"},
|
|
||||||
http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -743,10 +759,8 @@ func (s *Handlers) handleNick(
|
|||||||
) {
|
) {
|
||||||
lines := bodyLines()
|
lines := bodyLines()
|
||||||
if len(lines) == 0 {
|
if len(lines) == 0 {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"body required (new nick)",
|
||||||
"error": "body required (new nick)",
|
|
||||||
},
|
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -755,8 +769,8 @@ func (s *Handlers) handleNick(
|
|||||||
newNick := strings.TrimSpace(lines[0])
|
newNick := strings.TrimSpace(lines[0])
|
||||||
|
|
||||||
if !validNickRe.MatchString(newNick) {
|
if !validNickRe.MatchString(newNick) {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "invalid nick"},
|
"invalid nick",
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -776,21 +790,7 @@ func (s *Handlers) handleNick(
|
|||||||
r.Context(), uid, newNick,
|
r.Context(), uid, newNick,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "UNIQUE") {
|
s.handleChangeNickError(w, r, err)
|
||||||
s.respondJSON(w, r,
|
|
||||||
map[string]string{
|
|
||||||
"error": "nick already in use",
|
|
||||||
},
|
|
||||||
http.StatusConflict)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Error("change nick failed", "error", err)
|
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
|
||||||
map[string]string{"error": "internal error"},
|
|
||||||
http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -804,6 +804,22 @@ func (s *Handlers) handleNick(
|
|||||||
http.StatusOK)
|
http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Handlers) handleChangeNickError(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
if strings.Contains(err.Error(), "UNIQUE") {
|
||||||
|
s.respondError(w, r,
|
||||||
|
"nick already in use",
|
||||||
|
http.StatusConflict)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.internalError(w, r, "change nick failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Handlers) broadcastNick(
|
func (s *Handlers) broadcastNick(
|
||||||
r *http.Request,
|
r *http.Request,
|
||||||
uid int64,
|
uid int64,
|
||||||
@@ -858,8 +874,8 @@ func (s *Handlers) handleTopic(
|
|||||||
bodyLines func() []string,
|
bodyLines func() []string,
|
||||||
) {
|
) {
|
||||||
if to == "" {
|
if to == "" {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "to field required"},
|
"to field required",
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -867,43 +883,40 @@ func (s *Handlers) handleTopic(
|
|||||||
|
|
||||||
lines := bodyLines()
|
lines := bodyLines()
|
||||||
if len(lines) == 0 {
|
if len(lines) == 0 {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"body required (topic text)",
|
||||||
"error": "body required (topic text)",
|
|
||||||
},
|
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
topic := strings.Join(lines, " ")
|
topic := strings.Join(lines, " ")
|
||||||
|
channel := normalizeChannel(to)
|
||||||
channel := to
|
|
||||||
if !strings.HasPrefix(channel, "#") {
|
|
||||||
channel = "#" + channel
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.params.Database.SetTopic(
|
err := s.params.Database.SetTopic(
|
||||||
r.Context(), channel, topic,
|
r.Context(), channel, topic,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("set topic failed", "error", err)
|
s.internalError(w, r, "set topic failed", err)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
|
||||||
map[string]string{"error": "internal error"},
|
|
||||||
http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.broadcastTopic(w, r, nick, channel, topic, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Handlers) broadcastTopic(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
nick, channel, topic string,
|
||||||
|
body json.RawMessage,
|
||||||
|
) {
|
||||||
chID, err := s.params.Database.GetChannelByName(
|
chID, err := s.params.Database.GetChannelByName(
|
||||||
r.Context(), channel,
|
r.Context(), channel,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"channel not found",
|
||||||
"error": "channel not found",
|
|
||||||
},
|
|
||||||
http.StatusNotFound)
|
http.StatusNotFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -991,10 +1004,8 @@ func (s *Handlers) HandleGetHistory() http.HandlerFunc {
|
|||||||
|
|
||||||
target := r.URL.Query().Get("target")
|
target := r.URL.Query().Get("target")
|
||||||
if target == "" {
|
if target == "" {
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{
|
"target required",
|
||||||
"error": "target required",
|
|
||||||
},
|
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1019,8 +1030,8 @@ func (s *Handlers) HandleGetHistory() http.HandlerFunc {
|
|||||||
"get history failed", "error", err,
|
"get history failed", "error", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
s.respondJSON(w, r,
|
s.respondError(w, r,
|
||||||
map[string]string{"error": "internal error"},
|
"internal error",
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user