Search Apps Documentation Source Content File Folder Download Copy Actions Download

memba_appstore_reviews_v1.gno

5.51 Kb · 147 lines
  1// Package memba_appstore_reviews_v1 is the dedicated on-chain reviews / web-of-trust realm for
  2// Memba App Store listings. Subject = an app's package path (e.g. gno.land/r/samcrew/…).
  3//
  4// It is a THIN wrapper over the shared, audited p/samcrew/reviews engine (extracted from the
  5// DoS-hardened memba_reviews_v2: RV-1 O(1) subject summary, RV-3 flaggedIDs GC). This realm
  6// owns one Store and supplies only what a package cannot: the caller (unsafe.PreviousRealm),
  7// the block height (runtime.ChainHeight), the moderator-multisig gate on takedowns, and a
  8// gnoweb Render() view. The ABI mirrors memba_reviews_v2 exactly so the frontend reviews
  9// client can target this realm by path with no code change.
 10//
 11// WHY DEDICATED (not the shared memba_reviews realm): reputation isolation. memba_reviews keeps
 12// a global per-author reputation; sharing it would let sockpuppet down-votes on an app review
 13// bleed into someone's validator web-of-trust. A separate Store + moderator wall that off.
 14package memba_appstore_reviews_v1
 15
 16import (
 17	"strings"
 18
 19	"chain/runtime"
 20	"chain/runtime/unsafe"
 21
 22	"gno.land/p/nt/ufmt/v0"
 23
 24	reviews "gno.land/p/samcrew/memba_reviews_core_v1"
 25)
 26
 27// ModeratorAddress is the Memba moderator multisig (samcrew-core-test1 on testnet).
 28// CONFIRM before deploy — takedowns (Hide*/Unhide) are gated to exactly this address.
 29const ModeratorAddress = "g1x7k4628w93a7wzdhqc06atzx0v50rnshweuxu0"
 30
 31var store *reviews.Store
 32
 33func init() { store = reviews.NewStore() }
 34
 35func caller() address { return unsafe.PreviousRealm().Address() }
 36
 37func height() int64 { return runtime.ChainHeight() }
 38
 39func assertModerator() {
 40	if caller() != address(ModeratorAddress) {
 41		panic("unauthorized: moderator multisig only")
 42	}
 43}
 44
 45// ── Writes ───────────────────────────────────────────────────
 46
 47// PostReview creates or in-place-updates the caller's review for `subject` (one per pair).
 48func PostReview(cur realm, subject string, rating int, body string) {
 49	store.PostReview(caller(), subject, rating, body, height())
 50}
 51
 52// EditReview updates the caller's existing review. Author only.
 53func EditReview(cur realm, reviewID uint64, rating int, body string) {
 54	store.EditReview(caller(), reviewID, rating, body, height())
 55}
 56
 57// DeleteReview tombstones the caller's review. Author only.
 58func DeleteReview(cur realm, reviewID uint64) {
 59	store.DeleteReview(caller(), reviewID)
 60}
 61
 62// PostComment posts a flat reply to a review.
 63func PostComment(cur realm, reviewID uint64, body string) {
 64	store.PostComment(caller(), reviewID, body, height())
 65}
 66
 67// EditComment updates the caller's comment. Author only.
 68func EditComment(cur realm, commentID uint64, body string) {
 69	store.EditComment(caller(), commentID, body, height())
 70}
 71
 72// DeleteComment tombstones the caller's comment. Author only.
 73func DeleteComment(cur realm, commentID uint64) {
 74	store.DeleteComment(caller(), commentID)
 75}
 76
 77// React toggles a like/dislike on a review or comment. Self-reactions rejected.
 78func React(cur realm, targetID uint64, kind string) {
 79	store.React(caller(), targetID, kind)
 80}
 81
 82// Flag records one community flag per account per target.
 83func Flag(cur realm, targetID uint64) {
 84	store.Flag(caller(), targetID)
 85}
 86
 87// ── Moderation (multisig only) ───────────────────────────────
 88
 89// HideReview soft-deletes a review. Moderator multisig only.
 90func HideReview(cur realm, id uint64) {
 91	assertModerator()
 92	store.HideReview(id)
 93}
 94
 95// HideComment soft-deletes a comment. Moderator multisig only.
 96func HideComment(cur realm, id uint64) {
 97	assertModerator()
 98	store.HideComment(id)
 99}
100
101// Unhide reverses a hide on a review or comment. Moderator multisig only.
102func Unhide(cur realm, targetID uint64) {
103	assertModerator()
104	store.Unhide(targetID)
105}
106
107// ── Reads (vm/qeval) ─────────────────────────────────────────
108
109// GetReviewsJSON returns a subject's non-hidden reviews (paginated JSON array).
110func GetReviewsJSON(subject string, offset, limit int) string {
111	return store.GetReviewsJSON(subject, offset, limit)
112}
113
114// GetCommentsJSON returns a review's non-hidden comments (paginated JSON array).
115func GetCommentsJSON(reviewID uint64, offset, limit int) string {
116	return store.GetCommentsJSON(reviewID, offset, limit)
117}
118
119// GetSubjectSummaryJSON returns {"count","average","sum"} over a subject's visible reviews (O(1)).
120func GetSubjectSummaryJSON(subject string) string {
121	return store.SubjectSummaryJSON(subject)
122}
123
124// GetReputation returns an address's net likes−dislikes reputation (0 if unknown).
125func GetReputation(addr string) int64 {
126	return store.GetReputation(addr)
127}
128
129// GetFlaggedJSON returns flagged target IDs for the moderator dashboard (paginated).
130func GetFlaggedJSON(offset, limit int) string {
131	return store.GetFlaggedJSON(offset, limit)
132}
133
134// ── Render (gnoweb) ──────────────────────────────────────────
135
136// Render — human gnoweb view. "" → home (total count); "s/<subject>" → subject review list.
137func Render(path string) string {
138	if path == "" {
139		return ufmt.Sprintf("# Memba App Reviews\n\nOn-chain reviews for App Store listings. %d reviews total.\n",
140			store.ReviewCount())
141	}
142	if strings.HasPrefix(path, "s/") {
143		subject := strings.TrimPrefix(path, "s/")
144		return "# Reviews for " + reviews.SanitizeForRender(subject) + "\n\n" + store.RenderSubject(subject)
145	}
146	return "# 404\n"
147}