187 lines
6.5 KiB
Go
187 lines
6.5 KiB
Go
//go:build mysql
|
|
// +build mysql
|
|
|
|
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
t "github.com/tinode/chat/server/store/types"
|
|
)
|
|
|
|
func (a *adapter) TenantLoginAuditCreate(id t.TenantID, event *t.LoginAuditEvent) error {
|
|
tenantAdp, err := a.routedTenantAdapter(id)
|
|
if err != nil || event == nil || event.UserID.IsZero() {
|
|
return t.ErrMalformed
|
|
}
|
|
if tenantAdp != a {
|
|
return tenantAdp.TenantLoginAuditCreate(id, event)
|
|
}
|
|
metadata, err := json.Marshal(event.Metadata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = a.db.Exec(`INSERT INTO im_user_login_logs
|
|
(user_id,username,account_type,account_hash,account_mask,login_type,event_type,session_id,device_id,platform,app_version,ip,region,user_agent,risk_level,request_id,login_at,logout_at,metadata)
|
|
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
decodeUID(event.UserID), event.Username, event.AccountType, event.AccountHash, event.AccountMask,
|
|
event.LoginType, event.EventType, event.SessionID, event.DeviceID, event.Platform, event.AppVersion,
|
|
event.IP, event.Region, event.UserAgent, event.RiskLevel, event.RequestID, event.LoginAt, event.LogoutAt, nullableJSON(metadata))
|
|
return err
|
|
}
|
|
|
|
func (a *adapter) TenantContentModerationLogCreate(id t.TenantID, event *t.ContentModerationLog) error {
|
|
tenantAdp, err := a.routedTenantAdapter(id)
|
|
if err != nil || event == nil || strings.TrimSpace(event.RequestID) == "" {
|
|
return t.ErrMalformed
|
|
}
|
|
if tenantAdp != a {
|
|
return tenantAdp.TenantContentModerationLogCreate(id, event)
|
|
}
|
|
labels, err := json.Marshal(event.RiskLabels)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var actorID any
|
|
if !event.ActorID.IsZero() {
|
|
actorID = decodeUID(event.ActorID)
|
|
}
|
|
_, err = a.db.Exec(`INSERT INTO im_content_moderation_logs
|
|
(request_id,scene,topic,seqid,actor_id,provider,provider_request_id,risk_level,risk_labels,action,content_hash,latency_ms,error_code,created_at)
|
|
VALUES(?,?,?,?,?,?,?,?,CAST(? AS JSON),?,?,?,?,?)`, event.RequestID, event.Scene, event.Topic, event.SeqID, actorID,
|
|
event.Provider, event.ProviderRequestID, event.RiskLevel, string(labels), event.Action, event.ContentHash,
|
|
event.LatencyMS, event.ErrorCode, event.CreatedAt)
|
|
return err
|
|
}
|
|
|
|
func (a *adapter) TenantLoginFailureCreate(id t.TenantID, event *t.LoginFailureEvent) error {
|
|
tenantAdp, err := a.routedTenantAdapter(id)
|
|
if err != nil || event == nil {
|
|
return t.ErrMalformed
|
|
}
|
|
if tenantAdp != a {
|
|
return tenantAdp.TenantLoginFailureCreate(id, event)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *adapter) TenantLoginAuditClose(id t.TenantID, sessionID string, at time.Time) error {
|
|
tenantAdp, err := a.routedTenantAdapter(id)
|
|
if err != nil || strings.TrimSpace(sessionID) == "" {
|
|
return t.ErrMalformed
|
|
}
|
|
if tenantAdp != a {
|
|
return tenantAdp.TenantLoginAuditClose(id, sessionID, at)
|
|
}
|
|
_, err = a.db.Exec("UPDATE im_user_login_logs SET logout_at=? WHERE session_id=? AND logout_at IS NULL", at, sessionID)
|
|
return err
|
|
}
|
|
|
|
func (a *adapter) TenantMessageAdminApply(id t.TenantID, action *t.MessageAdminAction) error {
|
|
tenantAdp, err := a.routedTenantAdapter(id)
|
|
if err != nil || action == nil || action.Topic == "" || action.SeqID <= 0 || (action.Action != "hide" && action.Action != "delete") {
|
|
return t.ErrMalformed
|
|
}
|
|
if tenantAdp != a {
|
|
return tenantAdp.TenantMessageAdminApply(id, action)
|
|
}
|
|
ctx, cancel := a.getContext()
|
|
if cancel != nil {
|
|
defer cancel()
|
|
}
|
|
tx, err := a.db.BeginTxx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
var existing int64
|
|
if err = tx.GetContext(ctx, &existing, "SELECT COUNT(*) FROM im_message_admin_actions WHERE command_id=?", action.CommandID); err != nil {
|
|
return err
|
|
}
|
|
if existing > 0 {
|
|
return tx.Commit()
|
|
}
|
|
|
|
sender, beforeStatus, messageTable, err := lockAdminMessage(ctx, tx, id, action.ConversationType, action.Topic, action.SeqID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
before, _ := json.Marshal(map[string]any{"status": beforeStatus, "sender_user_id": sender})
|
|
afterStatus := "hidden"
|
|
if action.Action == "delete" {
|
|
afterStatus = "deleted"
|
|
}
|
|
if action.ConversationType == "group" {
|
|
_, err = tx.ExecContext(ctx, "UPDATE "+messageTable+" SET status=?,deleted_at=?,updated_at=? WHERE topic=? AND seqid=?", afterStatus, action.ProcessedAt, action.ProcessedAt, action.Topic, action.SeqID)
|
|
} else {
|
|
_, err = tx.ExecContext(ctx, "UPDATE "+messageTable+" SET deletedat=?,updatedat=? WHERE topic=? AND seqid=?", action.ProcessedAt, action.ProcessedAt, action.Topic, action.SeqID)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
after, _ := json.Marshal(map[string]any{"status": afterStatus, "sender_user_id": sender})
|
|
_, err = tx.ExecContext(ctx, `INSERT INTO im_message_admin_actions
|
|
(action_id,command_id,conversation_type,topic,seqid,sender_user_id,action,action_status,reason,actor_admin_id,request_id,before_snapshot,after_snapshot,processed_at)
|
|
VALUES(?,?,?,?,?,?,?,?,?,?,?,CAST(? AS JSON),CAST(? AS JSON),?)`,
|
|
action.ActionID, action.CommandID, action.ConversationType, action.Topic, action.SeqID, sender,
|
|
action.Action, "success", action.Reason, action.ActorAdminID, action.RequestID, string(before), string(after), action.ProcessedAt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return tx.Commit()
|
|
}
|
|
|
|
func lockAdminMessage(ctx context.Context, tx *sqlx.Tx, tenantID t.TenantID, conversationType, topic string, seqID int64) (int64, string, string, error) {
|
|
if conversationType == "group" {
|
|
tables, err := groupMessageTablesForSeq(tx, ctx, tenantID, topic, seqID)
|
|
if err != nil {
|
|
return 0, "", "", err
|
|
}
|
|
var row struct {
|
|
Sender int64 `db:"from_user"`
|
|
Status string `db:"status"`
|
|
}
|
|
for _, table := range tables {
|
|
err = tx.GetContext(ctx, &row, "SELECT from_user,status FROM "+table+" WHERE topic=? AND seqid=? FOR UPDATE", topic, seqID)
|
|
if err == nil {
|
|
return row.Sender, row.Status, table, nil
|
|
}
|
|
if !errors.Is(err, sql.ErrNoRows) {
|
|
return 0, "", "", err
|
|
}
|
|
}
|
|
return 0, "", "", t.ErrNotFound
|
|
}
|
|
var row struct {
|
|
Sender int64 `db:"from"`
|
|
DeletedAt sql.NullTime `db:"deletedat"`
|
|
RecalledAt sql.NullTime `db:"recalled_at"`
|
|
}
|
|
messageTable := tenantMessagesTable(tenantID)
|
|
err := tx.GetContext(ctx, &row, "SELECT `from`,deletedat,recalled_at FROM "+messageTable+" WHERE topic=? AND seqid=? FOR UPDATE", topic, seqID)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return 0, "", "", t.ErrNotFound
|
|
}
|
|
status := "normal"
|
|
if row.RecalledAt.Valid {
|
|
status = "recalled"
|
|
}
|
|
if row.DeletedAt.Valid {
|
|
status = "deleted"
|
|
}
|
|
return row.Sender, status, messageTable, err
|
|
}
|
|
|
|
func nullableJSON(raw []byte) any {
|
|
if len(raw) == 0 || string(raw) == "null" {
|
|
return nil
|
|
}
|
|
return string(raw)
|
|
}
|