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.18 Kb · 171 lines
  1package memba_notes_v1
  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// RedactPublicContent removes one public title (field 1) or body (field 2)
 74// version from realm storage. Public history keeps the version's SHA-256 and a
 75// content-redact entry naming the moderator. Redacting the current version also
 76// replaces it (placeholder title, empty body) under a new field revision. Gno
 77// refunds the freed storage to the moderator who frees it.
 78func RedactPublicContent(cur realm, id [16]byte, baseRevision uint64, field uint8, fieldRevision uint64, opID [16]byte) {
 79	who := enter(cur, false)
 80	defer leave()
 81	mutateRedactPublicContent(cur, who, id, baseRevision, field, fieldRevision, opID)
 82}
 83func mutateRedactPublicContent(cur realm, who address, id [16]byte, baseRevision uint64, field uint8, fieldRevision uint64, opID [16]byte) {
 84	n := find(id)
 85	if !moderator(who) {
 86		panic("notes: moderator required")
 87	}
 88	expected(n, baseRevision)
 89	if !public(n.Mode) {
 90		panic("notes: public note required")
 91	}
 92	name := ""
 93	switch field {
 94	case 1:
 95		name = "title"
 96	case 2:
 97		name = "body"
 98	default:
 99		panic("notes: bad redaction field")
100	}
101	v, ok := historyBlobs.Get(name + ":" + historyKey(n.ID, fieldRevision))
102	if !ok {
103		panic("notes: history version missing")
104	}
105	b := v.(*historyBlob)
106	if b.Redacted {
107		panic("notes: already redacted")
108	}
109	operation(n, opID)
110	if !n.Deleted && field == 1 && fieldRevision == n.TitleRevision {
111		if n.TitleRevision == ^uint64(0) {
112			panic("notes: title revision exhausted")
113		}
114		n.Title = []byte(RedactedTitle)
115		n.TitleRevision++
116	}
117	if !n.Deleted && field == 2 && fieldRevision == n.BodyRevision {
118		if n.BodyRevision == ^uint64(0) {
119			panic("notes: body revision exhausted")
120		}
121		n.Body = []byte{}
122		n.BodyBytes = 0
123		n.BodyRevision++
124	}
125	b.Bytes, b.Redacted = nil, true
126	bumpHistory(n, who, opID, "content-redact", `,"field":`+q(name)+`,"field_revision":`+u(fieldRevision)+`,"sha256":`+q(b.Hash))
127}
128
129// RedactPublicComment removes a public comment's anchor and body from both the
130// live comment (which becomes hidden and cannot be shown again) and its public
131// history. A purged comment keeps only its history record, which is redacted
132// the same way; baseRevision is then ignored (pass 0).
133func RedactPublicComment(cur realm, id, cid [16]byte, baseRevision uint64, opID [16]byte) {
134	who := enter(cur, false)
135	defer leave()
136	mutateRedactPublicComment(cur, who, id, cid, baseRevision, opID)
137}
138func mutateRedactPublicComment(cur realm, who address, id, cid [16]byte, baseRevision uint64, opID [16]byte) {
139	n := find(id)
140	if !moderator(who) {
141		panic("notes: moderator required")
142	}
143	if !public(n.Mode) {
144		panic("notes: public note required")
145	}
146	key := commentKey(id, cid)
147	v, ok := historyComments.Get(key)
148	if !ok {
149		panic("notes: missing public comment history")
150	}
151	archived := v.(*historyComment)
152	if archived.Redacted {
153		panic("notes: already redacted")
154	}
155	cv, live := comments.Get(key)
156	if !live {
157		if opID == ([16]byte{}) {
158			panic("notes: empty operation id")
159		}
160		archived.Initial.Anchor, archived.Initial.Body, archived.Redacted = nil, nil, true
161		e := historyAppend(n, who, opID, cid, "comment-redact", `{"comment_id":`+q(idText(cid))+`,"purged":true}`)
162		archived.LatestSeq = e.Seq
163		return
164	}
165	c := cv.(*comment)
166	commentExpected(c, baseRevision, opID)
167	archived.Initial.Anchor, archived.Initial.Body, archived.Redacted = nil, nil, true
168	c.Anchor, c.Body, c.Hidden = nil, nil, true
169	commentReceipt(c, who, opID)
170	historyCommentChange(n, c, "comment-redact")
171}