polls.gno

3.49 Kb ยท 168 lines
  1package polls
  2
  3import (
  4	"time"
  5
  6	"gno.land/p/nt/avl"
  7	"gno.land/p/nt/seqid"
  8	pollsv1 "gno.land/p/zenao/polls/v1"
  9	"gno.land/p/zenao/realmid"
 10)
 11
 12var (
 13	polls *avl.Tree // string (seqid.ID) -> *Poll
 14	id    seqid.ID
 15)
 16
 17func init() {
 18	polls = avl.NewTree()
 19}
 20
 21type AuthFn func() (userID string, authorized bool)
 22
 23type Poll struct {
 24	ID       seqid.ID
 25	Question string
 26	Kind     pollsv1.PollKind
 27	Results  *avl.Tree // string (options) -> avl.Tree of string (realmid of users) -> struct{}
 28
 29	Duration int64
 30
 31	CreatedAt int64
 32	CreatedBy string
 33
 34	authFunc AuthFn
 35}
 36
 37func NewPoll(_ realm, question string, kind pollsv1.PollKind, duration int64, options []string, authFunc AuthFn) *Poll {
 38	if len(options) < 2 {
 39		panic("poll must have at least 2 options")
 40	}
 41	if len(options) > 8 {
 42		panic("poll must have at most 8 options")
 43	}
 44	minDuration := int64(time.Minute) * 15 / int64(time.Second)
 45	maxDuration := int64(time.Hour) * 24 * 30 / int64(time.Second)
 46	if duration < minDuration {
 47		panic("duration must be at least 15 minutes")
 48	}
 49	if duration > maxDuration {
 50		panic("duration must be at most 1 month")
 51	}
 52	poll := &Poll{
 53		ID:        id.Next(),
 54		Question:  question,
 55		Kind:      kind,
 56		Results:   avl.NewTree(),
 57		Duration:  duration,
 58		CreatedAt: time.Now().Unix(),
 59		CreatedBy: realmid.Previous(),
 60		authFunc:  authFunc,
 61	}
 62	for _, option := range options {
 63		if option == "" {
 64			panic("option cannot be empty")
 65		}
 66		if len(option) > 128 {
 67			panic("option cannot be longer than 128 characters")
 68		}
 69		if poll.Results.Has(option) {
 70			panic("duplicate option")
 71		}
 72		poll.Results.Set(option, avl.NewTree())
 73	}
 74
 75	polls.Set(poll.ID.String(), poll)
 76
 77	return poll
 78}
 79
 80func Vote(cur realm, pollID uint64, option string) {
 81	id := seqid.ID(pollID)
 82	pollRaw, ok := polls.Get(id.String())
 83	if !ok {
 84		panic("poll not found")
 85	}
 86
 87	poll := pollRaw.(*Poll)
 88	poll.Vote(cur, option)
 89}
 90
 91func GetInfo(pollID uint64, user string) *pollsv1.Poll {
 92	id := seqid.ID(pollID)
 93	pollRaw, ok := polls.Get(id.String())
 94	if !ok {
 95		panic("poll not found")
 96	}
 97	poll := pollRaw.(*Poll)
 98	return poll.GetInfo(user)
 99}
100
101func (p *Poll) Vote(_ realm, option string) {
102	callerID := p.auth()
103
104	optionRaw, ok := p.Results.Get(option)
105	if !ok {
106		panic("invalid option")
107	}
108
109	if !p.IsRunning() {
110		panic("poll is not running")
111	}
112
113	// Remove previous choice if multiple answers are not allowed
114	if p.Kind != pollsv1.POLL_KIND_MULTIPLE_CHOICE {
115		p.Results.Iterate("", "", func(key string, value interface{}) bool {
116			choices := value.(*avl.Tree)
117			if choices.Has(callerID) {
118				choices.Remove(callerID)
119				return true
120			}
121			return false
122		})
123	}
124
125	choices := optionRaw.(*avl.Tree)
126	if choices.Has(callerID) {
127		choices.Remove(callerID)
128	} else {
129		choices.Set(callerID, struct{}{})
130	}
131}
132
133func (p *Poll) GetInfo(user string) *pollsv1.Poll {
134	info := &pollsv1.Poll{
135		Question:  p.Question,
136		Results:   []*pollsv1.PollResult{},
137		Kind:      p.Kind,
138		Duration:  p.Duration,
139		CreatedAt: p.CreatedAt,
140		CreatedBy: p.CreatedBy,
141	}
142	p.Results.Iterate("", "", func(key string, value interface{}) bool {
143		count := value.(*avl.Tree)
144		info.Results = append(info.Results, &pollsv1.PollResult{
145			Option:       key,
146			Count:        uint32(count.Size()),
147			HasUserVoted: count.Has(user),
148		})
149		return false
150	})
151	return info
152}
153
154func (p *Poll) IsRunning() bool {
155	return time.Now().Unix() < p.CreatedAt+p.Duration
156}
157
158func (p *Poll) auth() string {
159	if p.authFunc == nil {
160		return realmid.Previous()
161	}
162
163	id, ok := p.authFunc()
164	if !ok {
165		panic("the user is not allowed to interact with this poll")
166	}
167	return id
168}