owpengram-server/internal/store/postgres/channel_admin_log.go

159 lines
5.7 KiB
Go

package postgres
import (
"context"
"fmt"
"github.com/jackc/pgx/v5"
"strings"
"telesrv/internal/domain"
)
func (s *ChannelStore) ListAdminLog(ctx context.Context, req domain.ChannelAdminLogRequest) (domain.ChannelAdminLogResult, error) {
if req.UserID == 0 || req.ChannelID == 0 || req.MaxID < 0 || req.MinID < 0 {
return domain.ChannelAdminLogResult{}, domain.ErrChannelInvalid
}
channel, member, err := s.getChannelForMember(ctx, s.db, req.UserID, req.ChannelID)
if err != nil {
return domain.ChannelAdminLogResult{}, err
}
if !isChannelAdmin(member) {
return domain.ChannelAdminLogResult{}, domain.ErrChannelAdminRequired
}
limit := req.Limit
if limit <= 0 || limit > domain.MaxChannelAdminLogLimit {
limit = domain.MaxChannelAdminLogLimit
}
where := []string{"channel_id = $1"}
args := []any{req.ChannelID}
nextArg := func(v any) string {
args = append(args, v)
return fmt.Sprintf("$%d", len(args))
}
if req.MaxID > 0 {
where = append(where, "id < "+nextArg(req.MaxID))
}
if req.MinID > 0 {
where = append(where, "id > "+nextArg(req.MinID))
}
if len(req.AdminUserIDs) > 0 {
where = append(where, "actor_user_id = ANY("+nextArg(int64s(req.AdminUserIDs))+"::bigint[])")
}
if types := adminLogEventTypesForFilter(req.Filter); len(types) > 0 {
where = append(where, "event_type = ANY("+nextArg(types)+"::text[])")
} else if !req.Filter.Empty() {
return domain.ChannelAdminLogResult{Channel: channel}, nil
}
query := strings.ToLower(strings.TrimSpace(req.Query))
if query != "" {
like := adminLogLikePattern(query)
where = append(where, `(lower(prev_string) LIKE `+nextArg(like)+` ESCAPE '\' OR lower(new_string) LIKE `+nextArg(like)+` ESCAPE '\' OR lower(query) LIKE `+nextArg(like)+` ESCAPE '\')`)
}
args = append(args, limit)
rows, err := s.db.Query(ctx, `
SELECT channel_id, id, actor_user_id, event_date, event_type, prev_string, new_string, prev_bool, new_bool, prev_int, new_int,
prev_participant::text, new_participant::text, participant::text, message::text, prev_message::text, new_message::text, query
FROM channel_admin_log_events
WHERE `+strings.Join(where, " AND ")+`
ORDER BY id DESC
LIMIT $`+fmt.Sprint(len(args)), args...)
if err != nil {
return domain.ChannelAdminLogResult{}, fmt.Errorf("list channel admin log: %w", err)
}
defer rows.Close()
events := make([]domain.ChannelAdminLogEvent, 0, limit)
for rows.Next() {
event, err := scanChannelAdminLogEvent(rows)
if err != nil {
return domain.ChannelAdminLogResult{}, err
}
events = append(events, event)
}
if err := rows.Err(); err != nil {
return domain.ChannelAdminLogResult{}, err
}
return domain.ChannelAdminLogResult{Channel: channel, Events: events}, nil
}
func (s *ChannelStore) insertChannelAdminLogTx(ctx context.Context, tx pgx.Tx, event domain.ChannelAdminLogEvent) error {
if event.ChannelID == 0 || event.UserID == 0 || event.Type == "" {
return nil
}
if event.Date == 0 {
event.Date = nowUnix()
}
id, err := nextChannelAdminLogIDTx(ctx, tx, event.ChannelID)
if err != nil {
return err
}
prevParticipant, err := marshalJSON(event.PrevParticipant, "{}")
if err != nil {
return err
}
newParticipant, err := marshalJSON(event.NewParticipant, "{}")
if err != nil {
return err
}
participant, err := marshalJSON(event.Participant, "{}")
if err != nil {
return err
}
message, err := marshalJSON(event.Message, "{}")
if err != nil {
return err
}
prevMessage, err := marshalJSON(event.PrevMessage, "{}")
if err != nil {
return err
}
newMessage, err := marshalJSON(event.NewMessage, "{}")
if err != nil {
return err
}
query := adminLogSearchText(event)
if _, err := tx.Exec(ctx, `
INSERT INTO channel_admin_log_events (
channel_id, id, actor_user_id, event_date, event_type, prev_string, new_string,
prev_bool, new_bool, prev_int, new_int, prev_participant, new_participant,
participant, message, prev_message, new_message, query
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18)`,
event.ChannelID, id, event.UserID, event.Date, string(event.Type), event.PrevString, event.NewString,
event.PrevBool, event.NewBool, event.PrevInt, event.NewInt, prevParticipant, newParticipant,
participant, message, prevMessage, newMessage, query); err != nil {
return fmt.Errorf("insert channel admin log: %w", err)
}
return nil
}
func nextChannelAdminLogIDTx(ctx context.Context, tx pgx.Tx, channelID int64) (int64, error) {
var id int64
if err := tx.QueryRow(ctx, `
UPDATE channels
SET admin_log_seq = admin_log_seq + 1, updated_at = now()
WHERE id = $1
RETURNING admin_log_seq`, channelID).Scan(&id); err != nil {
return 0, fmt.Errorf("allocate channel admin log id: %w", err)
}
return id, nil
}
func scanChannelAdminLogEvent(row rowScanner) (domain.ChannelAdminLogEvent, error) {
var event domain.ChannelAdminLogEvent
var typ string
var prevParticipant, newParticipant, participant, message, prevMessage, newMessage string
if err := row.Scan(
&event.ChannelID, &event.ID, &event.UserID, &event.Date, &typ,
&event.PrevString, &event.NewString, &event.PrevBool, &event.NewBool,
&event.PrevInt, &event.NewInt, &prevParticipant, &newParticipant, &participant,
&message, &prevMessage, &newMessage, &event.Query,
); err != nil {
return domain.ChannelAdminLogEvent{}, err
}
event.Type = domain.ChannelAdminLogEventType(typ)
event.PrevParticipant = decodeJSONPtr[domain.ChannelMember](prevParticipant)
event.NewParticipant = decodeJSONPtr[domain.ChannelMember](newParticipant)
event.Participant = decodeJSONPtr[domain.ChannelMember](participant)
event.Message = decodeJSONPtr[domain.ChannelMessage](message)
event.PrevMessage = decodeJSONPtr[domain.ChannelMessage](prevMessage)
event.NewMessage = decodeJSONPtr[domain.ChannelMessage](newMessage)
return event, nil
}