Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

moderation.gno

5.52 Kb · 175 lines
  1package memba_notes_rc1
  2
  3import keys "gno.land/r/samcrew/enckeys_v1"
  4
  5const MaxModerators = 32
  6
  7var moderators []address
  8
  9func moderator(who address) bool { return who == admin || contains(moderators, who) }
 10func AddModerator(cur realm, to address) {
 11	who := enter(cur, false)
 12	defer leave()
 13	administrator(who)
 14	to = keys.CanonicalAddress(to)
 15	if contains(moderators, to) {
 16		panic("notes: moderator exists")
 17	}
 18	if len(moderators) >= MaxModerators {
 19		panic("notes: moderator cap")
 20	}
 21	moderators = append(moderators, to)
 22}
 23func RemoveModerator(cur realm, to address) {
 24	who := enter(cur, false)
 25	defer leave()
 26	administrator(who)
 27	to = keys.CanonicalAddress(to)
 28	for i, a := range moderators {
 29		if a == to {
 30			moderators = append(moderators[:i], moderators[i+1:]...)
 31			return
 32		}
 33	}
 34	panic("notes: moderator missing")
 35}
 36func ModeratorsJSON() string {
 37	out := "["
 38	for i, a := range moderators {
 39		if i > 0 {
 40			out += ","
 41		}
 42		out += q(string(a))
 43	}
 44	return out + "]"
 45}
 46
 47// Directory moderation is separate from owner discoverability. Owners cannot
 48// undo a moderation hide by toggling their own listing preference.
 49func ModerateListing(cur realm, id [16]byte, baseRevision uint64, visible bool, opID [16]byte) {
 50	who := enter(cur, false)
 51	defer leave()
 52	mutateModerateListing(cur, who, id, baseRevision, visible, opID)
 53}
 54func mutateModerateListing(cur realm, who address, id [16]byte, baseRevision uint64, visible bool, opID [16]byte) {
 55	n := live(id)
 56	if !moderator(who) {
 57		panic("notes: moderator required")
 58	}
 59	expected(n, baseRevision)
 60	if !public(n.Mode) {
 61		panic("notes: public note required")
 62	}
 63	operation(n, opID)
 64	unindex(n)
 65	n.ModeratorHidden = !visible
 66	index(n)
 67	bump(n, who, opID, "moderate-listing")
 68}
 69
 70// RedactedTitle replaces a redacted current title, which must stay valid.
 71const RedactedTitle = "Redacted by moderation"
 72
 73// RedactPublicContentAtSeq removes one public title (field 1) or body (field 2)
 74// from realm storage: the one recorded by the note-level history event seq (a
 75// version's recorded_seq). The name carries the addressing so a caller written
 76// for field revisions cannot act on the wrong version. History stores each field
 77// revision once, so every version that kept that title or body shows it
 78// redacted. Public history keeps its SHA-256 and a content-redact entry naming
 79// the moderator and the field revision. Redacting the current version also
 80// replaces it (placeholder title, empty body) under a new field revision. Gno
 81// refunds the freed storage to the moderator who frees it.
 82func RedactPublicContentAtSeq(cur realm, id [16]byte, baseRevision uint64, field uint8, seq uint64, opID [16]byte) {
 83	who := enter(cur, false)
 84	defer leave()
 85	mutateRedactPublicContentAtSeq(cur, who, id, baseRevision, field, seq, opID)
 86}
 87func mutateRedactPublicContentAtSeq(cur realm, who address, id [16]byte, baseRevision uint64, field uint8, seq uint64, opID [16]byte) {
 88	n := find(id)
 89	if !moderator(who) {
 90		panic("notes: moderator required")
 91	}
 92	expected(n, baseRevision)
 93	if !public(n.Mode) {
 94		panic("notes: public note required")
 95	}
 96	if field != 1 && field != 2 {
 97		panic("notes: bad redaction field")
 98	}
 99	v := historyVersionAt(n.ID, seq)
100	if v == nil {
101		panic("notes: history version missing")
102	}
103	name, b, fieldRevision := "title", v.Title, v.TitleRevision
104	if field == 2 {
105		name, b, fieldRevision = "body", v.Body, v.BodyRevision
106	}
107	if b == nil {
108		panic("notes: history version missing")
109	}
110	if b.Redacted {
111		panic("notes: already redacted")
112	}
113	operation(n, opID)
114	if !n.Deleted && field == 1 && fieldRevision == n.TitleRevision {
115		if n.TitleRevision == ^uint64(0) {
116			panic("notes: title revision exhausted")
117		}
118		n.Title = []byte(RedactedTitle)
119		n.TitleRevision++
120	}
121	if !n.Deleted && field == 2 && fieldRevision == n.BodyRevision {
122		if n.BodyRevision == ^uint64(0) {
123			panic("notes: body revision exhausted")
124		}
125		n.Body = []byte{}
126		n.BodyBytes = 0
127		n.BodyRevision++
128	}
129	b.Bytes, b.Redacted = nil, true
130	bumpHistory(n, who, opID, "content-redact", `,"field":`+q(name)+`,"field_revision":`+u(fieldRevision)+`,"sha256":`+q(b.Hash))
131}
132
133// RedactPublicComment removes a public comment's anchor and body from both the
134// live comment (which becomes hidden and cannot be shown again) and its public
135// history. A purged comment keeps only its history record, which is redacted
136// the same way; baseRevision is then ignored (pass 0).
137func RedactPublicComment(cur realm, id, cid [16]byte, baseRevision uint64, opID [16]byte) {
138	who := enter(cur, false)
139	defer leave()
140	mutateRedactPublicComment(cur, who, id, cid, baseRevision, opID)
141}
142func mutateRedactPublicComment(cur realm, who address, id, cid [16]byte, baseRevision uint64, opID [16]byte) {
143	n := find(id)
144	if !moderator(who) {
145		panic("notes: moderator required")
146	}
147	if !public(n.Mode) {
148		panic("notes: public note required")
149	}
150	key := commentKey(id, cid)
151	v, ok := historyComments.Get(key)
152	if !ok {
153		panic("notes: missing public comment history")
154	}
155	archived := v.(*historyComment)
156	if archived.Redacted {
157		panic("notes: already redacted")
158	}
159	cv, live := comments.Get(key)
160	if !live {
161		if opID == ([16]byte{}) {
162			panic("notes: empty operation id")
163		}
164		archived.Anchor, archived.Body, archived.Redacted = nil, nil, true
165		e := historyAppend(n, who, opID, cid, "comment-redact", `{"comment_id":`+q(idText(cid))+`,"purged":true}`)
166		archived.LatestSeq = e.Seq
167		return
168	}
169	c := cv.(*comment)
170	commentExpected(c, baseRevision, opID)
171	archived.Anchor, archived.Body, archived.Redacted = nil, nil, true
172	c.Anchor, c.Body, c.Hidden = nil, nil, true
173	commentReceipt(c, who, opID)
174	historyCommentChange(n, c, "comment-redact")
175}