/
teams.go
371 lines (298 loc) · 10.7 KB
/
teams.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
package main
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"golang.org/x/net/context"
"github.com/nicolai86/dash-annotations/dash"
)
var (
// ErrInvalidAccessKey is returned from a join request handler when the given access key
// does not match the teams access key
ErrInvalidAccessKey = errors.New("Invalid access key")
// ErrMissingRoleParameter is returned from a set_role handler when no role parameter is present
ErrMissingRoleParameter = errors.New("Missing parameter: role")
// ErrInvalidRoleParameter is returned from the set_role handler when the role is unknown
ErrInvalidRoleParameter = errors.New("Invalid parameter: role. Must either be member or moderator")
// ErrMissingUsernameParameter is returned when a target username parameter is required, but missing
ErrMissingUsernameParameter = errors.New("Missing parameter: username")
// ErrNotTeamOwner is returned when an action requires you to be the team owner, but you are not
ErrNotTeamOwner = errors.New("You need to be the teams owner")
// ErrUnknownUser is returned when an action requires a target user, but the given username is unknown
ErrUnknownUser = errors.New("Invalid parameter: username. Unknown user")
// ErrTeamNameExists is returned when a team should be created, and the name is already taken
ErrTeamNameExists = errors.New("A team with this name already exists")
// ErrMissingTeamName is returned when a team should be created and the name parameter is missing
ErrMissingTeamName = errors.New("Missing parameter: name")
)
type teamListResponse struct {
Status string `json:"status"`
Teams []dash.TeamMember `json:"teams,omitempty"`
}
// TeamList returns a list of all teams the current user is a member|moderator of
func TeamList(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
var user = ctx.Value(UserKey).(*dash.User)
json.NewEncoder(w).Encode(teamListResponse{
Status: "success",
Teams: user.TeamMemberships,
})
return nil
}
// TeamCreate tries to create a new team with a given name inside the database
func TeamCreate(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
var db = ctx.Value(DBKey).(*sql.DB)
var user = ctx.Value(UserKey).(*dash.User)
var payload map[string]interface{}
json.NewDecoder(req.Body).Decode(&payload)
var team = dash.Team{
Name: payload["name"].(string),
}
if team.Name == "" {
return ErrMissingTeamName
}
var tx, err = db.Begin()
if err != nil {
return err
}
var cnt = 0
tx.QueryRow(`SELECT count(*) FROM teams WHERE name = ?`, team.Name).Scan(&cnt)
if cnt != 0 {
return ErrTeamNameExists
}
var res, _ = tx.Exec(`INSERT INTO teams (name, access_key, created_at, updated_at) VALUES (?, ?, ?, ?)`, team.Name, "", time.Now(), time.Now())
var teamID int64
teamID, err = res.LastInsertId()
if err != nil {
return err
}
team.ID = int(teamID)
tx.Exec(`INSERT INTO team_user (team_id, user_id, role) VALUES (?, ?, ?)`, team.ID, user.ID, "owner")
if err := tx.Commit(); err != nil {
return err
}
json.NewEncoder(w).Encode(map[string]string{
"status": "success",
})
return nil
}
type teamJoinRequest struct {
AccessKey string `json:"access_key"`
}
// TeamJoin tries to add the current user to the requested team
func TeamJoin(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
var db = ctx.Value(DBKey).(*sql.DB)
var user = ctx.Value(UserKey).(*dash.User)
var targetTeam = ctx.Value(TeamKey).(*dash.Team)
var dec = json.NewDecoder(req.Body)
var payload teamJoinRequest
dec.Decode(&payload)
if !targetTeam.AccessKeysMatch(payload.AccessKey) {
return errors.New("Invalid access key")
}
if _, err := db.Exec(`INSERT INTO team_user (team_id, user_id, role) VALUES (?, ?, ?)`, targetTeam.ID, user.ID, "member"); err != nil {
return err
}
json.NewEncoder(w).Encode(map[string]string{
"status": "success",
})
return nil
}
// TeamLeave removes the current user from the requested team
func TeamLeave(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
var db = ctx.Value(DBKey).(*sql.DB)
var user = ctx.Value(UserKey).(*dash.User)
var team = ctx.Value(TeamKey).(*dash.Team)
var enc = json.NewEncoder(w)
var tx, err = db.Begin()
if err != nil {
return err
}
tx.Exec(`DELETE FROM team_user WHERE team_id = ? AND user_id = ?`, team.ID, user.ID)
var entryIDs = make([]interface{}, 0)
var rows *sql.Rows
rows, err = tx.Query(`SELECT e.id FROM entries e INNER JOIN entry_team et ON et.entry_id = e.id AND et.team_id = ? WHERE e.user_id = ?`, team.ID, user.ID)
defer rows.Close()
for rows.Next() {
var entryID int
if err := rows.Scan(&entryID); err != nil {
return err
}
entryIDs = append(entryIDs, entryID)
}
params := append([]interface{}{user.ID}, entryIDs...)
var query = fmt.Sprintf(`DELETE FROM votes WHERE user_id = ? AND entry_id IN (%s) `, strings.Join(strings.Split(strings.Repeat("?", len(entryIDs)), ""), ","))
tx.Exec(query, params...)
query = fmt.Sprintf(`DELETE FROM entry_team WHERE entry_id IN (%s) `, strings.Join(strings.Split(strings.Repeat("?", len(entryIDs)), ""), ","))
tx.Exec(query, entryIDs...)
query = fmt.Sprintf(`DELETE FROM entries WHERE id IN (%s) `, strings.Join(strings.Split(strings.Repeat("?", len(entryIDs)), ""), ","))
tx.Exec(query, entryIDs...)
var membershipCount = -1
tx.QueryRow(`SELECT count(*) from team_user WHERE team_id = ?`, team.ID).Scan(&membershipCount)
if membershipCount == 0 {
tx.Exec(`DELETE FROM teams WHERE id = ?`, team.ID)
}
if err := tx.Commit(); err != nil {
return err
}
enc.Encode(map[string]string{
"status": "success",
})
return nil
}
type teamSetRoleRequest struct {
Role string `json:"role"`
Username string `json:"username"`
}
// TeamSetRole changes the role of the target user to the requested role
func TeamSetRole(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
var db = ctx.Value(DBKey).(*sql.DB)
var user = ctx.Value(UserKey).(*dash.User)
var team = ctx.Value(TeamKey).(*dash.Team)
if team.OwnerID != user.ID {
return ErrNotTeamOwner
}
var dec = json.NewDecoder(req.Body)
var payload teamSetRoleRequest
dec.Decode(&payload)
if payload.Role == "" {
return ErrMissingRoleParameter
}
if payload.Role != "member" && payload.Role != "moderator" {
return ErrInvalidRoleParameter
}
if payload.Username == "" {
return ErrMissingUsernameParameter
}
var target, err = findUserByUsername(db, payload.Username)
if err != nil {
return ErrUnknownUser
}
if _, err := db.Exec(`UPDATE team_user SET role = ? WHERE team_id = ? AND user_id = ?`, payload.Role, team.ID, target.ID); err != nil {
return err
}
json.NewEncoder(w).Encode(map[string]string{
"status": "success",
})
return nil
}
type teamRemoveMemberRequest struct {
Username string `json:"username"`
}
// TeamRemoveMember allows a moderator to remove a member from a team
func TeamRemoveMember(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
var db = ctx.Value(DBKey).(*sql.DB)
var user = ctx.Value(UserKey).(*dash.User)
var team = ctx.Value(TeamKey).(*dash.Team)
if team.OwnerID != user.ID {
return ErrNotTeamOwner
}
var payload teamRemoveMemberRequest
json.NewDecoder(req.Body).Decode(&payload)
if payload.Username == "" {
return ErrMissingUsernameParameter
}
var target, err = findUserByUsername(db, payload.Username)
if err != nil {
return ErrUnknownUser
}
var tx *sql.Tx
if tx, err = db.Begin(); err != nil {
return err
}
tx.Exec(`DELETE FROM team_user WHERE team_id = ? AND user_id = ?`, team.ID, target.ID)
var entryIDs = make([]interface{}, 0)
var rows *sql.Rows
rows, err = tx.Query(`SELECT e.id FROM entries e INNER JOIN entry_team et ON et.entry_id = e.id AND et.team_id = ? WHERE e.user_id = ?`, team.ID, target.ID)
defer rows.Close()
if err != nil {
return err
}
for rows.Next() {
var entryID int
if err := rows.Scan(&entryID); err != nil {
return err
}
entryIDs = append(entryIDs, entryID)
}
params := append([]interface{}{target.ID}, entryIDs...)
var query = fmt.Sprintf(`DELETE FROM votes WHERE user_id = ? AND entry_id IN (%s) `, strings.Join(strings.Split(strings.Repeat("?", len(entryIDs)), ""), ","))
tx.Exec(query, params...)
query = fmt.Sprintf(`DELETE FROM entry_team WHERE entry_id IN (%s) `, strings.Join(strings.Split(strings.Repeat("?", len(entryIDs)), ""), ","))
tx.Exec(query, entryIDs...)
query = fmt.Sprintf(`DELETE FROM entries WHERE id IN (%s) `, strings.Join(strings.Split(strings.Repeat("?", len(entryIDs)), ""), ","))
tx.Exec(query, entryIDs...)
if err := tx.Commit(); err != nil {
return err
}
json.NewEncoder(w).Encode(map[string]string{
"status": "success",
})
return nil
}
type teamSetAccessKeyRequest struct {
AccessKey string `json:"access_key"`
}
// TeamSetAccessKey allows moderators to change the access key for a given team
func TeamSetAccessKey(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
var db = ctx.Value(DBKey).(*sql.DB)
var user = ctx.Value(UserKey).(*dash.User)
var team = ctx.Value(TeamKey).(*dash.Team)
if team.OwnerID != user.ID {
return ErrNotTeamOwner
}
var enc = json.NewEncoder(w)
var payload teamSetAccessKeyRequest
json.NewDecoder(req.Body).Decode(&payload)
team.ChangeAccessKey(payload.AccessKey)
if _, err := db.Exec(`UPDATE teams SET access_key = ? WHERE id = ?`, team.EncryptedAccessKey, team.ID); err != nil {
return err
}
enc.Encode(map[string]string{
"status": "success",
})
return nil
}
type membership struct {
Role string `json:"role"`
Username string `json:"name"`
}
type teamListMembersResponse struct {
Status string `json:"status"`
Members []membership `json:"members"`
HasAccessKey bool `json:"has_access_key"`
}
// TeamListMember allows moderators to list all members of a requested team
func TeamListMember(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
var db = ctx.Value(DBKey).(*sql.DB)
var user = ctx.Value(UserKey).(*dash.User)
var team = ctx.Value(TeamKey).(*dash.Team)
if team.OwnerID != user.ID {
return ErrNotTeamOwner
}
var payload map[string]interface{}
json.NewDecoder(req.Body).Decode(&payload)
var rows, err = db.Query(`SELECT tm.role, u.username FROM team_user AS tm INNER JOIN users AS u ON u.id = tm.user_id WHERE tm.team_id = ?`, team.ID)
if err != nil {
return err
}
defer rows.Close()
var memberships = make([]membership, 0)
for rows.Next() {
var membership = membership{}
if err := rows.Scan(&membership.Role, &membership.Username); err != nil {
return err
}
memberships = append(memberships, membership)
}
var resp = teamListMembersResponse{
Status: "success",
Members: memberships,
HasAccessKey: team.EncryptedAccessKey != "",
}
json.NewEncoder(w).Encode(resp)
return nil
}