1package daemon23import (4 "context"5 "database/sql"6 "errors"7 "strings"89 "github.com/FelineStateMachine/tinyrelay/internal/community"10 "github.com/FelineStateMachine/tinyrelay/internal/event"11 "github.com/FelineStateMachine/tinyrelay/internal/policy"12 "github.com/FelineStateMachine/tinyrelay/internal/relay"13 "github.com/FelineStateMachine/tinyrelay/internal/replication"14 "github.com/FelineStateMachine/tinyrelay/internal/storage"15)1617type metadataProjection struct {18 next policy.Policy19 changed bool20}2122func (t *Tenant) publishReport(ctx context.Context, e event.Event) (string, error) {23 target := ""24 targetType := ""25 reportType := ""26 for _, tag := range e.Tags {27 if len(tag) > 1 && tag[0] == "e" && target == "" {28 target = tag[1]29 targetType = "event"30 if len(tag) > 2 {31 reportType = tag[2]32 }33 }34 if len(tag) > 1 && tag[0] == "p" && target == "" {35 target = tag[1]36 targetType = "pubkey"37 if len(tag) > 2 {38 reportType = tag[2]39 }40 }41 if len(tag) > 1 && tag[0] == "x" && target == "" {42 target = tag[1]43 targetType = "blob"44 if len(tag) > 2 {45 reportType = tag[2]46 }47 }48 }49 if target == "" {50 return "", errors.New("invalid: report needs an e or p tag")51 }52 if err := t.community.SubmitReportTarget(ctx, e.PubKey, target, targetType, reportType, e.Content, t.Policy().ReportThreshold); err != nil {53 return "", err54 }55 return "info: report received", nil56}5758func (t *Tenant) prepareClientEvent(ctx context.Context, e event.Event, s relay.Session, now int64) (eventCommitPlan, *metadataProjection, error) {59 metadata := &metadataProjection{}60 plan, err := t.prepareStoredEvent(ctx, e, replication.OriginClient, now)61 if err != nil {62 return plan, metadata, err63 }64 opts := plan.options65 if (e.Kind == event.KIND_EDIT_METADATA || e.Kind == event.KIND_PINS) && t.roomScope(e) == "" {66 role, roleErr := t.community.Role(ctx, e.PubKey)67 if roleErr != nil {68 return plan, metadata, roleErr69 }70 if role != "owner" && role != "moderator" {71 return plan, metadata, errors.New("restricted: not a group admin")72 }73 opts.AddBeforeCommit(func(txCtx context.Context, tx *sql.Tx) error {74 return t.community.HandleProjectionEventTx(txCtx, tx, e, func(sideTx *sql.Tx) error {75 if e.Kind == event.KIND_EDIT_METADATA {76 metadata.next = t.Policy()77 for _, tag := range e.Tags {78 if len(tag) < 2 {79 continue80 }81 switch tag[0] {82 case "name":83 metadata.next.Name = tag[1][:min(200, len(tag[1]))]84 case "about":85 metadata.next.Description = tag[1][:min(2000, len(tag[1]))]86 case "picture":87 metadata.next.Icon = tag[1][:min(2000, len(tag[1]))]88 }89 }90 metadata.changed = true91 return storage.PutSetting(txCtx, sideTx, "policy", metadata.next)92 }93 pins, err := parsePinTags(e.Tags)94 if err != nil {95 return err96 }97 return t.records.ReplacePinsTx(txCtx, sideTx, pins)98 })99 })100 }101 opts.Intents = append(opts.Intents, t.replication.Prepare(e, replication.OriginClient)...)102 if e.Kind == event.KIND_MARMOT_GROUP {103 principal, principalErr := t.gate.Principal(ctx, e, s)104 if principalErr != nil {105 return plan, metadata, principalErr106 }107 opts.AddBeforeCommit(func(txCtx context.Context, tx *sql.Tx) error {108 _, err := tx.ExecContext(txCtx, `INSERT OR REPLACE INTO marmot_principals(event_id,pubkey) VALUES(?,?)`, e.ID, principal)109 return err110 })111 }112 plan.options = opts113 return plan, metadata, nil114}115116func (t *Tenant) persistClientEvent(ctx context.Context, e event.Event, opts storage.SaveOptions) (string, error) {117 var err error118 persist := func(tx *sql.Tx) error {119 _, saveErr := storage.SaveTx(ctx, tx, e, opts)120 return saveErr121 }122 // reason is the OK message; an access request tells the asker it waits,123 // and a member asking again is told so.124 reason := ""125 room := t.roomScope(e)126 switch {127 case room != "" && community.RoomAdminKind(e.Kind):128 _, err = t.community.HandleRoomEventTx(ctx, e, persist)129 case room != "" && !event.IsEphemeral(e.Kind):130 err = t.community.HandleRoomMessageTx(ctx, e, persist)131 case e.Kind == event.KIND_JOIN, e.Kind == event.KIND_LEAVE, e.Kind == event.KIND_NIP43_JOIN, e.Kind == event.KIND_NIP43_LEAVE:132 var membership community.MembershipResult133 membership, err = t.community.HandleMembershipEventTx(ctx, e, persist)134 if err == nil && (membership.AccessRequest || strings.HasPrefix(membership.Message, "duplicate:")) {135 reason = membership.Message136 }137 if err == nil && membership.NewRequest {138 t.notifyJoinRequest(ctx, e)139 }140 case e.Kind == event.KIND_PUT_USER, e.Kind == event.KIND_REMOVE_USER, e.Kind == event.KIND_DELETE_EVENT, e.Kind == event.KIND_CREATE_INVITE:141 _, err = t.community.HandleModerationEventTx(ctx, e, persist)142 default:143 _, err = t.store.Save(ctx, e, opts)144 }145 return reason, err146}