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 "github.com/FelineStateMachine/tinyrelay/internal/podcasts"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 gitrelay "github.com/FelineStateMachine/tinyrelay/tinygit"16)1718// eventCommitter prepares the projections common to client publication and19// replication. Admission and client-only protocol actions remain with their20// entry points; every projection here participates in the event transaction.21type eventCommitter struct {22 policy policy.Policy23 community *community.Service24 sites *sites.Service25 git *gitrelay.GitRelay26}2728type eventCommitPlan struct {29 options storage.SaveOptions30 repository gitrelay.Repository31 gitMetadata bool32 followups *storage.Intent33}3435func (c eventCommitter) prepare(ctx context.Context, e event.Event, origin replication.Origin, now int64) (eventCommitPlan, error) {36 plan := eventCommitPlan{options: storage.SaveOptions{Now: now}}37 if c.sites != nil {38 plan.options = c.sites.SaveOptions(e, now)39 }40 plan.options.SearchMode = c.policy.Features.Search41 if e.Kind == 30023 {42 plan.options.Intents = append(plan.options.Intents, storage.Intent{Kind: "view-publish", EventID: e.ID, Target: "articles", Payload: "{}"})43 }44 if e.Kind == podcasts.KindEpisode || e.Kind == podcasts.KindMetadata || e.Kind == podcasts.KindAuthored || e.Kind == event.KIND_DELETION {45 plan.options.Intents = append(plan.options.Intents, storage.Intent{Kind: "view-publish", EventID: e.ID, Target: "podcasts", Payload: "{}"})46 }47 if e.Kind == event.KIND_AGENT_GRANT || e.Kind == event.KIND_DELETION {48 plan.options.AddBeforeCommit(func(ctx context.Context, tx *sql.Tx) error {49 return c.community.ApplyAgentEventTx(ctx, tx, e, now)50 })51 }52 if c.policy.Features.Grasp && (e.Kind == event.KIND_REPO || e.Kind == event.KIND_REPO_STATE) {53 if err := c.prepareRepository(ctx, e, origin, &plan); err != nil {54 return eventCommitPlan{}, err55 }56 }57 return plan, nil58}5960func (c eventCommitter) prepareRepository(ctx context.Context, e event.Event, origin replication.Origin, plan *eventCommitPlan) error {61 validate := c.git.ValidateImported62 if origin == replication.OriginClient {63 validate = c.git.Validate64 }65 repo, err := validate(ctx, e)66 if err != nil {67 return err68 }69 raw, err := json.Marshal(repo)70 if err != nil {71 return err72 }73 plan.repository, plan.gitMetadata = repo, true74 plan.options.Intents = append(plan.options.Intents, storage.Intent{Kind: "git-metadata", EventID: e.ID, Target: repo.Owner + ":" + repo.Identifier, Payload: string(raw)})75 if e.Kind == event.KIND_REPO_STATE {76 plan.options.AddBeforeCommit(func(ctx context.Context, tx *sql.Tx) error {77 _, err := tx.ExecContext(ctx, "INSERT OR REPLACE INTO pending_events(id,reason) VALUES(?,'git objects')", e.ID)78 return err79 })80 }81 return nil82}8384func (t *Tenant) validateStoredEvent(ctx context.Context, e event.Event) error {85 if err := t.validatePrivateRepositoryPlacement(ctx, e); err != nil {86 return err87 }88 if err := validateReviewAnchor(e); err != nil {89 return err90 }91 if t.sites != nil {92 return sites.ValidateManifest(e)93 }94 return nil95}9697func (t *Tenant) prepareStoredEvent(ctx context.Context, e event.Event, origin replication.Origin, now int64) (eventCommitPlan, error) {98 committer := eventCommitter{policy: t.Policy(), community: t.community, sites: t.sites, git: t.git}99 plan, err := committer.prepare(ctx, e, origin, now)100 if err != nil {101 return plan, err102 }103 var push *replicationPushFollowupPayload104 if origin == replication.OriginClient && t.Policy().Features.Push {105 payload, _ := t.prepareReplicationPushFollowup(ctx, e, now)106 if len(payload.RegistrationIDs) != 0 || payload.Discover {107 push = &payload108 }109 }110 plan.followups = t.followups.prepareWithPush(ctx, e, now, false, push)111 if plan.followups != nil {112 plan.options.Intents = append(plan.options.Intents, *plan.followups)113 }114 return plan, nil115}116117func (t *Tenant) afterStoredEvent(ctx context.Context, e event.Event, followups *storage.Intent) {118 t.notifyDevices(ctx, e)119 t.followups.try(ctx, followups)120 if event.IsEphemeral(e.Kind) {121 t.notifyCallbacks(ctx, e)122 }123 t.customViews.cascadeViewDeletion(ctx, e)124 t.notifyWikiMerge(ctx, e)125 t.notifyWikiProposal(ctx, e)126}