-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
143 lines (121 loc) · 4.21 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package main
import (
"context"
"database/sql"
"database/sql/driver"
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
"time"
"golang.org/x/time/rate"
_ "modernc.org/sqlite"
)
func main() {
ctx := context.Background()
db, err := sql.Open("sqlite", "meetings.db?_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)")
if err != nil {
log.Fatal(err)
}
defer db.Close()
if err := initDB(db); err != nil {
log.Fatal(err)
}
limiter := rate.NewLimiter(rate.Every(time.Second), 1)
fs := flag.NewFlagSet("halifax-meetings", flag.ExitOnError)
var only commaSeparatedString
fs.Var(&only, "only", "only run these comma-separated actions")
fs.Parse(os.Args[1:])
type action struct {
name string
fn func(_ context.Context, _ *sql.DB, _ *rate.Limiter, args []string) error
}
actions := []action{
{"meetings", processMeetings},
{"urls", processExternalContentURLs},
}
for _, a := range actions {
if len(only.vals) > 0 {
if _, ok := only.vals[a.name]; !ok {
continue
}
}
if err := a.fn(ctx, db, limiter, fs.Args()); err != nil {
log.Fatal(err)
}
}
}
func initDB(db *sql.DB) error {
initQueries := []string{
`create table if not exists meeting_agenda_content (id text primary key, text text, html text)`,
`create table if not exists meetings (id text primary key, type text, date text, schedule_note text, last_observed datetime, agenda_url text, minutes_url text, video_url text, agenda_content_id references meeting_agenda_content (id))`,
`create table if not exists meeting_versions (meeting_id text references meetings (id), observed datetime, schedule_note text, agenda_url text, minutes_url text, video_url text, agenda_content_id references meeting_agenda_content (id), unique (meeting_id, schedule_note, agenda_url, minutes_url, video_url, agenda_content_id))`,
`create index if not exists meetings_agenda_content_id on meetings (agenda_content_id)`,
`create virtual table if not exists meeting_agenda_content_search using fts5(text, content=meeting_agenda_content)`,
`create table if not exists external_content (id text primary key, title text, text text)`,
`create virtual table if not exists external_content_search using fts5(title, text, content=external_content)`,
`create table if not exists external_content_urls (url text primary key, added datetime, fetched datetime, content_type text, size integer, last_modified datetime, etag text, error text, external_content_id text references external_content (id))`,
`create table if not exists meeting_external_content_urls (meeting_id text references meetings (id), agenda_content_id references meeting_agenda_content (id), external_content_url text references external_content_urls (url), unique (meeting_id, agenda_content_id, external_content_url))`,
`create index if not exists external_content_urls_external_content_id on external_content_urls (external_content_id)`,
`create index if not exists meeting_external_content_urls_external_content_url on meeting_external_content_urls (external_content_url)`,
}
for _, q := range initQueries {
if _, err := db.Exec(q); err != nil {
return fmt.Errorf("init db: %w", err)
}
}
return nil
}
const timeFormat = "2006-01-02 15:04:05.999"
type timeValue struct {
v *time.Time
}
func newTimeValue(v *time.Time) timeValue {
return timeValue{v: v}
}
func (s timeValue) Scan(src any) error {
if src == nil {
*s.v = time.Time{}
return nil
}
if vt, ok := src.(time.Time); ok {
*s.v = vt.UTC()
return nil
}
vs, ok := src.(string)
if !ok {
return fmt.Errorf("unknown timeValue.Scan type %T", src)
}
t, err := time.ParseInLocation(timeFormat, vs, time.UTC)
if err == nil {
*s.v = t
return nil
}
return fmt.Errorf("could not parse time string %q", vs)
}
func (s timeValue) Value() (driver.Value, error) {
if s.v.IsZero() {
return nil, nil
}
return s.v.UTC().Format(timeFormat), nil
}
type commaSeparatedString struct {
vals map[string]struct{}
}
func (c *commaSeparatedString) Set(s string) error {
c.vals = make(map[string]struct{})
for _, s := range strings.Split(s, ",") {
c.vals[s] = struct{}{}
}
return nil
}
func (c *commaSeparatedString) String() string {
var keys []string
for k := range c.vals {
keys = append(keys, k)
}
sort.Strings(keys)
return strings.Join(keys, ",")
}