Repository navigation
Expand file tree
/
Copy pathdb.go
More file actions
150 lines (120 loc) · 3.26 KB
/
Copy pathdb.go
File metadata and controls
150 lines (120 loc) · 3.26 KB
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
144
145
146
147
148
149
150
package main
import (
"encoding/json"
"io"
"sync"
)
// DatabaseEvent is a single database event
type DatabaseEvent struct {
Create *EventCreate `json:"create,omitempty"`
Delete *EventDelete `json:"delete,omitempty"`
}
// EventCreate is a database event for creating a claim
type EventCreate struct {
DiscordID string `json:"discord_id"`
AdventID string `json:"aoc_id"`
}
// NewEventCreate creates a new database event for creating a claim
func NewEventCreate(discordID, adventID string) *DatabaseEvent {
return &DatabaseEvent{
Create: &EventCreate{
DiscordID: discordID,
AdventID: adventID,
},
}
}
// EventDelete is a database event for deleting a claim
type EventDelete struct {
DiscordID string `json:"discord_id"`
}
// NewEventDelete creates a new database event for deleting a claim
func NewEventDelete(discordID string) *DatabaseEvent {
return &DatabaseEvent{
Delete: &EventDelete{
DiscordID: discordID,
},
}
}
// Database keeps an append-only log file of bot operation
//
// - tracks Advent of Code id claims
// - tracks Advent of Code id unclaims
type Database struct {
sync.RWMutex
// Where we write new events to
writer *json.Encoder
// In-memory, per guild, mapping of discord ids to Advent of Code ids
mappings map[string]string
}
// NewDatabase creates a new database
func NewDatabase(reader io.Reader, writer io.Writer) (*Database, error) {
// Create the database
database := &Database{
writer: json.NewEncoder(writer),
mappings: make(map[string]string),
}
decoder := json.NewDecoder(reader)
// Decode in a loop to avoid EOF errors
for {
var event DatabaseEvent
err := decoder.Decode(&event)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
switch {
case event.Create != nil:
database.mappings[event.Create.DiscordID] = event.Create.AdventID
case event.Delete != nil:
delete(database.mappings, event.Delete.DiscordID)
}
}
return database, nil
}
// Claim adds a new claim to the database
func (database *Database) Claim(discordID, adventID string) error {
database.Lock()
// Add the claim to the database
database.mappings[discordID] = adventID
// Write the event to the database
err := database.writer.Encode(NewEventCreate(discordID, adventID))
database.Unlock()
return err
}
// Unclaim removes a claim from the database
func (database *Database) Unclaim(discordID string) error {
database.Lock()
// Remove the claim from the database
if _, ok := database.mappings[discordID]; !ok {
database.Unlock()
return ErrDoesNotExist
}
delete(database.mappings, discordID)
// Write the event to the database
err := database.writer.Encode(NewEventDelete(discordID))
database.Unlock()
return err
}
// GetAdventID gets the Advent of Code ID for a discord user
func (database *Database) GetAdventID(discordID string) (string, bool) {
database.RLock()
// Get the Advent of Code ID
aocID, ok := database.mappings[discordID]
database.RUnlock()
return aocID, ok
}
// CheckClaim checks if an Advent of Code user has been claimed
func (database *Database) CheckClaim(adventID string) bool {
database.RLock()
// Check if the user has been claimed
for _, id := range database.mappings {
if id == adventID {
database.RUnlock()
return true
}
}
database.RUnlock()
return false
}