File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/store/events.go
183 lines · 5.7kb · master
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package store
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"database/sql"
133728e barerepo 1mo
6
"strings"
133728e barerepo 1mo
7
"time"
133728e barerepo 1mo
8
)
133728e barerepo 1mo
9
133728e barerepo 1mo
10
// Event kinds from chapter 19.1, without run.succeeded, because a green build is not news.
133728e barerepo 1mo
11
const (
133728e barerepo 1mo
12
ProposalOpened = "proposal.opened"
133728e barerepo 1mo
13
ProposalUpdated = "proposal.updated"
133728e barerepo 1mo
14
ProposalMerged = "proposal.merged"
133728e barerepo 1mo
15
ThreadOpened = "thread.opened"
133728e barerepo 1mo
16
ThreadReplied = "thread.replied"
133728e barerepo 1mo
17
ThreadClosed = "thread.closed"
133728e barerepo 1mo
18
Pushed = "push"
133728e barerepo 1mo
19
RunFailed = "run.failed"
133728e barerepo 1mo
20
RepoTransferred = "repo.transferred"
133728e barerepo 1mo
21
)
133728e barerepo 1mo
22
133728e barerepo 1mo
23
// Kinds is chapter 19.1's list, complete, so a name outside it is a typo and not a future event.
133728e barerepo 1mo
24
var Kinds = []string{
133728e barerepo 1mo
25
ProposalOpened, ProposalUpdated, ProposalMerged,
133728e barerepo 1mo
26
ThreadOpened, ThreadReplied, ThreadClosed,
133728e barerepo 1mo
27
Pushed, RunFailed, RepoTransferred,
133728e barerepo 1mo
28
}
133728e barerepo 1mo
29
133728e barerepo 1mo
30
// KnownKind reports whether a name is one barerepo will ever send. Chapter 23.2 lets a file name any.
133728e barerepo 1mo
31
func KnownKind(name string) bool {
133728e barerepo 1mo
32
for _, k := range Kinds {
133728e barerepo 1mo
33
if k == name {
133728e barerepo 1mo
34
return true
133728e barerepo 1mo
35
}
133728e barerepo 1mo
36
}
133728e barerepo 1mo
37
return false
133728e barerepo 1mo
38
}
133728e barerepo 1mo
39
133728e barerepo 1mo
40
// EventLife bounds the inbox, which is a feed, because the repository is the archive. 19.3.
133728e barerepo 1mo
41
const EventLife = 90 * 24 * time.Hour
133728e barerepo 1mo
42
133728e barerepo 1mo
43
type Event struct {
133728e barerepo 1mo
44
ID int64
133728e barerepo 1mo
45
Kind string
133728e barerepo 1mo
46
Actor string
133728e barerepo 1mo
47
Repo string
133728e barerepo 1mo
48
Ref string
133728e barerepo 1mo
49
Number int
133728e barerepo 1mo
50
Title string
133728e barerepo 1mo
51
// Detail is the row's second line where Title is spoken for, as a failed build's is. 19.3.
133728e barerepo 1mo
52
Detail string
133728e barerepo 1mo
53
Created time.Time
133728e barerepo 1mo
54
}
133728e barerepo 1mo
55
133728e barerepo 1mo
56
// Record writes an event, and a failure must never fail the push, so callers log and carry on.
133728e barerepo 1mo
57
func (db *DB) Record(ctx context.Context, e Event) error {
133728e barerepo 1mo
58
_, err := db.ExecContext(ctx,
133728e barerepo 1mo
59
`INSERT INTO events (kind, actor, repo, ref, number, title, detail, created_at)
133728e barerepo 1mo
60
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
133728e barerepo 1mo
61
e.Kind, e.Actor, e.Repo, e.Ref, e.Number, e.Title, e.Detail, now().Unix())
133728e barerepo 1mo
62
return err
133728e barerepo 1mo
63
}
133728e barerepo 1mo
64
133728e barerepo 1mo
65
// TookPart is the subscription: there is no watch button, only touching a thread. 19.2.
133728e barerepo 1mo
66
func (db *DB) TookPart(ctx context.Context, account, repo string, number int) error {
133728e barerepo 1mo
67
if account == "" || number == 0 {
133728e barerepo 1mo
68
return nil
133728e barerepo 1mo
69
}
133728e barerepo 1mo
70
_, err := db.ExecContext(ctx,
133728e barerepo 1mo
71
`INSERT INTO participation (account, repo, number) VALUES (?, ?, ?)
133728e barerepo 1mo
72
ON CONFLICT DO NOTHING`, account, repo, number)
133728e barerepo 1mo
73
return err
133728e barerepo 1mo
74
}
133728e barerepo 1mo
75
133728e barerepo 1mo
76
// Inbox is what this account owns, plus every thread it took part in.
133728e barerepo 1mo
77
func (db *DB) Inbox(ctx context.Context, account string, limit int) ([]Event, error) {
133728e barerepo 1mo
78
cutoff := now().Add(-EventLife).Unix()
133728e barerepo 1mo
79
rows, err := db.QueryContext(ctx,
133728e barerepo 1mo
80
`SELECT id, kind, actor, repo, ref, number, title, detail, created_at FROM events
133728e barerepo 1mo
81
WHERE created_at > ?
133728e barerepo 1mo
82
AND (repo LIKE ? OR EXISTS (
133728e barerepo 1mo
83
SELECT 1 FROM participation p
133728e barerepo 1mo
84
WHERE p.account = ? AND p.repo = events.repo AND p.number = events.number))
133728e barerepo 1mo
85
ORDER BY id DESC LIMIT ?`,
133728e barerepo 1mo
86
cutoff, account+"/%", account, limit)
133728e barerepo 1mo
87
if err != nil {
133728e barerepo 1mo
88
return nil, err
133728e barerepo 1mo
89
}
133728e barerepo 1mo
90
defer rows.Close()
133728e barerepo 1mo
91
return scanEvents(rows)
133728e barerepo 1mo
92
}
133728e barerepo 1mo
93
133728e barerepo 1mo
94
// EventsFor returns one repository's events, for its feed.
133728e barerepo 1mo
95
func (db *DB) EventsFor(ctx context.Context, repo string, limit int) ([]Event, error) {
133728e barerepo 1mo
96
rows, err := db.QueryContext(ctx,
133728e barerepo 1mo
97
`SELECT id, kind, actor, repo, ref, number, title, detail, created_at FROM events
133728e barerepo 1mo
98
WHERE repo = ? ORDER BY id DESC LIMIT ?`, repo, limit)
133728e barerepo 1mo
99
if err != nil {
133728e barerepo 1mo
100
return nil, err
133728e barerepo 1mo
101
}
133728e barerepo 1mo
102
defer rows.Close()
133728e barerepo 1mo
103
return scanEvents(rows)
133728e barerepo 1mo
104
}
133728e barerepo 1mo
105
133728e barerepo 1mo
106
// EventsBy returns one account's activity, for their feed.
133728e barerepo 1mo
107
func (db *DB) EventsBy(ctx context.Context, account string, limit int) ([]Event, error) {
133728e barerepo 1mo
108
rows, err := db.QueryContext(ctx,
133728e barerepo 1mo
109
`SELECT id, kind, actor, repo, ref, number, title, detail, created_at FROM events
133728e barerepo 1mo
110
WHERE actor = ? ORDER BY id DESC LIMIT ?`, account, limit)
133728e barerepo 1mo
111
if err != nil {
133728e barerepo 1mo
112
return nil, err
133728e barerepo 1mo
113
}
133728e barerepo 1mo
114
defer rows.Close()
133728e barerepo 1mo
115
return scanEvents(rows)
133728e barerepo 1mo
116
}
133728e barerepo 1mo
117
133728e barerepo 1mo
118
func scanEvents(rows *sql.Rows) ([]Event, error) {
133728e barerepo 1mo
119
var out []Event
133728e barerepo 1mo
120
for rows.Next() {
133728e barerepo 1mo
121
var e Event
133728e barerepo 1mo
122
var created int64
133728e barerepo 1mo
123
if err := rows.Scan(&e.ID, &e.Kind, &e.Actor, &e.Repo, &e.Ref, &e.Number, &e.Title, &e.Detail, &created); err != nil {
133728e barerepo 1mo
124
return nil, err
133728e barerepo 1mo
125
}
133728e barerepo 1mo
126
e.Created = time.Unix(created, 0)
133728e barerepo 1mo
127
out = append(out, e)
133728e barerepo 1mo
128
}
133728e barerepo 1mo
129
return out, rows.Err()
133728e barerepo 1mo
130
}
133728e barerepo 1mo
131
133728e barerepo 1mo
132
// LastVisited places the inbox rule, and losing it costs a reader one line. 19.4.
133728e barerepo 1mo
133
func (db *DB) LastVisited(ctx context.Context, account string) (time.Time, error) {
133728e barerepo 1mo
134
var at sql.NullInt64
133728e barerepo 1mo
135
if err := db.QueryRowContext(ctx,
133728e barerepo 1mo
136
`SELECT last_visited FROM accounts WHERE name = ?`, account).Scan(&at); err != nil {
133728e barerepo 1mo
137
return time.Time{}, err
133728e barerepo 1mo
138
}
133728e barerepo 1mo
139
if !at.Valid {
133728e barerepo 1mo
140
return time.Time{}, nil
133728e barerepo 1mo
141
}
133728e barerepo 1mo
142
return time.Unix(at.Int64, 0), nil
133728e barerepo 1mo
143
}
133728e barerepo 1mo
144
133728e barerepo 1mo
145
func (db *DB) Visit(ctx context.Context, account string) error {
133728e barerepo 1mo
146
_, err := db.ExecContext(ctx,
133728e barerepo 1mo
147
`UPDATE accounts SET last_visited = ? WHERE name = ?`, now().Unix(), account)
133728e barerepo 1mo
148
return err
133728e barerepo 1mo
149
}
133728e barerepo 1mo
150
133728e barerepo 1mo
151
// DropOldEvents throws away everything past the window.
133728e barerepo 1mo
152
func (db *DB) DropOldEvents(ctx context.Context) error {
133728e barerepo 1mo
153
_, err := db.ExecContext(ctx,
133728e barerepo 1mo
154
`DELETE FROM events WHERE created_at < ?`, now().Add(-EventLife).Unix())
133728e barerepo 1mo
155
return err
133728e barerepo 1mo
156
}
133728e barerepo 1mo
157
133728e barerepo 1mo
158
// Owner reads the account a repository belongs to.
133728e barerepo 1mo
159
func Owner(repo string) string {
133728e barerepo 1mo
160
owner, _, _ := strings.Cut(repo, "/")
133728e barerepo 1mo
161
return owner
133728e barerepo 1mo
162
}
133728e barerepo 1mo
163
133728e barerepo 1mo
164
// ThreadKinds is what the threads page shows, so the threads feed carries the same. 19.5.
133728e barerepo 1mo
165
var ThreadKinds = []string{ProposalOpened, ProposalUpdated, ProposalMerged, ThreadOpened, ThreadReplied, ThreadClosed}
133728e barerepo 1mo
166
133728e barerepo 1mo
167
// ThreadEventsFor is one repository's discussion, for /<user>/<repo>/threads.atom.
133728e barerepo 1mo
168
func (db *DB) ThreadEventsFor(ctx context.Context, repo string, limit int) ([]Event, error) {
133728e barerepo 1mo
169
args := []any{repo}
133728e barerepo 1mo
170
for _, k := range ThreadKinds {
133728e barerepo 1mo
171
args = append(args, k)
133728e barerepo 1mo
172
}
133728e barerepo 1mo
173
args = append(args, limit)
133728e barerepo 1mo
174
q := `SELECT id, kind, actor, repo, ref, number, title, detail, created_at FROM events
133728e barerepo 1mo
175
WHERE repo = ? AND kind IN (?` + strings.Repeat(", ?", len(ThreadKinds)-1) + `)
133728e barerepo 1mo
176
ORDER BY id DESC LIMIT ?`
133728e barerepo 1mo
177
rows, err := db.QueryContext(ctx, q, args...)
133728e barerepo 1mo
178
if err != nil {
133728e barerepo 1mo
179
return nil, err
133728e barerepo 1mo
180
}
133728e barerepo 1mo
181
defer rows.Close()
133728e barerepo 1mo
182
return scanEvents(rows)
133728e barerepo 1mo
183
}
history · rawbarerepo 0.1.0