clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/standalone-relay
tinyrelay / internal / daemon / event_commit.go
event_commit.go 4.1K
1package daemon23import (4 "context"5 "database/sql"6 "encoding/json"78 "github.com/FelineStateMachine/tinyrelay/internal/community"9 "github.com/FelineStateMachine/tinyrelay/internal/event"10 gitrelay "github.com/FelineStateMachine/tinyrelay/tinygit"11 "github.com/FelineStateMachine/tinyrelay/internal/policy"12 "github.com/FelineStateMachine/tinyrelay/internal/replication"13 "github.com/FelineStateMachine/tinyrelay/internal/sites"14 "github.com/FelineStateMachine/tinyrelay/internal/storage"15)1617// eventCommitter prepares the projections common to client publication and18// replication. Admission and client-only protocol actions remain with their19// entry points; every projection here participates in the event transaction.20type eventCommitter struct {21 policy policy.Policy22 community *community.Service23 sites *sites.Service24 git *gitrelay.GitRelay25}2627type eventCommitPlan struct {28 options storage.SaveOptions29 repository gitrelay.Repository30 gitMetadata bool31 followups *storage.Intent32}3334func (c eventCommitter) prepare(ctx context.Context, e event.Event, origin replication.Origin, now int64) (eventCommitPlan, error) {35 plan := eventCommitPlan{options: storage.SaveOptions{Now: now}}36 if c.sites != nil {37 plan.options = c.sites.SaveOptions(e, now)38 }39 plan.options.SearchMode = c.policy.Features.Search40 if e.Kind == 30023 {41 plan.options.Intents = append(plan.options.Intents, storage.Intent{Kind: "view-publish", EventID: e.ID, Target: "articles", Payload: "{}"})42 }43 if e.Kind == event.KIND_AGENT_GRANT || e.Kind == event.KIND_DELETION {44 plan.options.AddBeforeCommit(func(ctx context.Context, tx *sql.Tx) error {45 return c.community.ApplyAgentEventTx(ctx, tx, e, now)46 })47 }48 if c.policy.Features.Grasp && (e.Kind == event.KIND_REPO || e.Kind == event.KIND_REPO_STATE) {49 if err := c.prepareRepository(ctx, e, origin, &plan); err != nil {50 return eventCommitPlan{}, err51 }52 }53 return plan, nil54}5556func (c eventCommitter) prepareRepository(ctx context.Context, e event.Event, origin replication.Origin, plan *eventCommitPlan) error {57 validate := c.git.ValidateImported58 if origin == replication.OriginClient {59 validate = c.git.Validate60 }61 repo, err := validate(ctx, e)62 if err != nil {63 return err64 }65 raw, err := json.Marshal(repo)66 if err != nil {67 return err68 }69 plan.repository, plan.gitMetadata = repo, true70 plan.options.Intents = append(plan.options.Intents, storage.Intent{Kind: "git-metadata", EventID: e.ID, Target: repo.Owner + ":" + repo.Identifier, Payload: string(raw)})71 if e.Kind == event.KIND_REPO_STATE {72 plan.options.AddBeforeCommit(func(ctx context.Context, tx *sql.Tx) error {73 _, err := tx.ExecContext(ctx, "INSERT OR REPLACE INTO pending_events(id,reason) VALUES(?,'git objects')", e.ID)74 return err75 })76 }77 return nil78}7980func (t *Tenant) validateStoredEvent(ctx context.Context, e event.Event) error {81 if err := t.validatePrivateRepositoryPlacement(ctx, e); err != nil {82 return err83 }84 if err := validateReviewAnchor(e); err != nil {85 return err86 }87 if t.sites != nil {88 return sites.ValidateManifest(e)89 }90 return nil91}9293func (t *Tenant) prepareStoredEvent(ctx context.Context, e event.Event, origin replication.Origin, now int64) (eventCommitPlan, error) {94 committer := eventCommitter{policy: t.Policy(), community: t.community, sites: t.sites, git: t.git}95 plan, err := committer.prepare(ctx, e, origin, now)96 if err != nil {97 return plan, err98 }99 var push *replicationPushFollowupPayload100 if origin == replication.OriginClient && t.Policy().Features.Push {101 payload, _ := t.prepareReplicationPushFollowup(ctx, e, now)102 if len(payload.RegistrationIDs) != 0 || payload.Discover {103 push = &payload104 }105 }106 plan.followups = t.followups.prepareWithPush(ctx, e, now, false, push)107 if plan.followups != nil {108 plan.options.Intents = append(plan.options.Intents, *plan.followups)109 }110 return plan, nil111}112113func (t *Tenant) afterStoredEvent(ctx context.Context, e event.Event, followups *storage.Intent) {114 t.notifyDevices(ctx, e)115 t.followups.try(ctx, followups)116 if event.IsEphemeral(e.Kind) {117 t.notifyCallbacks(ctx, e)118 }119 t.customViews.cascadeViewDeletion(ctx, e)120 t.notifyWikiMerge(ctx, e)121 t.notifyWikiProposal(ctx, e)122}