blob: 05b09bc9ec09b71da0e50a4b03e70726a0e8693e [file] [log] [blame]
gio134be722025-07-20 19:01:17 +04001package main
2
3import (
4 "database/sql"
5 _ "embed"
6 "fmt"
7 "strings"
8
9 "github.com/ncruces/go-sqlite3"
10)
11
12//go:embed schema.sql
13var schema string
14
15const (
16 ErrorUniqueConstraintViolation = 2067
17 ErrorConstraintPrimaryKeyViolation = 1555
18)
19
20type scanner interface {
21 Scan(dest ...any) error
22}
23
24type Store interface {
25 // Initializes store with admin user and their groups.
26 Init(user User, groups []Group) error
27 CreateUser(tx *sql.Tx, user User) error
28 GetAllUsers(tx *sql.Tx) ([]User, error)
29 GetUser(tx *sql.Tx, id string) (User, error)
30 GetUsers(tx *sql.Tx, ids []string) ([]User, error)
31 CreateGroup(tx *sql.Tx, userId string, group Group) error
32 GetGroup(tx *sql.Tx, id string) (Group, error)
33 GetAllGroups(tx *sql.Tx) ([]Group, error)
34 AddMemberUser(tx *sql.Tx, groupId, userId string) error
35 AddOwnerUser(tx *sql.Tx, groupId, userId string) error
36 AddMemberGroup(tx *sql.Tx, groupId, otherId string) error
37 AddOwnerGroup(tx *sql.Tx, groupId, otherId string) error
38 GetMemberUsers(tx *sql.Tx, groupId string) ([]User, error)
39 GetOwnerUsers(tx *sql.Tx, groupId string) ([]User, error)
40 GetMemberGroups(tx *sql.Tx, groupId string) ([]Group, error)
41 GetOwnerGroups(tx *sql.Tx, groupId string) ([]Group, error)
42 RemoveMemberUser(tx *sql.Tx, groupId, userId string) error
43 RemoveOwnerUser(tx *sql.Tx, groupId, userId string) error
44 RemoveMemberGroup(tx *sql.Tx, groupId, otherId string) error
45 RemoveOwnerGroup(tx *sql.Tx, groupId, otherId string) error
46 GetGroupsUserCanActAs(tx *sql.Tx, userId string) ([]Group, error)
47 GetGroupsGroupCanActAs(tx *sql.Tx, groupId string) ([]Group, error)
48 GetGroupsUserOwns(tx *sql.Tx, userId string) ([]Group, error)
49 GetGroupsUserIsMemberOf(tx *sql.Tx, userId string) ([]Group, error)
50 GetUserPublicKeys(tx *sql.Tx, userId string) ([]string, error)
51 AddUserPublicKey(tx *sql.Tx, userId, publicKey string) error
52 RemoveUserPublicKey(tx *sql.Tx, userId, publicKey string) error
53}
54
55type SQLiteStore struct {
56 db *sql.DB
57}
58
59func NewSQLiteStore(db *sql.DB) (*SQLiteStore, error) {
60 _, err := db.Exec(schema)
61 if err != nil {
62 return nil, err
63 }
64 return &SQLiteStore{db: db}, nil
65}
66
67func (s *SQLiteStore) Init(user User, groups []Group) error {
68 tx, err := s.db.Begin()
69 if err != nil {
70 return err
71 }
72 defer tx.Rollback()
73 row := tx.QueryRow("SELECT COUNT(*) FROM groups")
74 var count int
75 if err := row.Scan(&count); err != nil {
76 return err
77 }
78 if count != 0 {
79 return fmt.Errorf("Store already initialised")
80 }
81 if err := s.CreateUser(tx, user); err != nil {
82 return err
83 }
84 for _, g := range groups {
85 if err := s.CreateGroup(tx, user.Id, g); err != nil {
86 return err
87 }
88 if err := s.AddMemberUser(tx, g.Id, user.Id); err != nil {
89 return err
90 }
91 }
92 return tx.Commit()
93}
94
95func (s *SQLiteStore) CreateUser(tx *sql.Tx, user User) (err error) {
96 if tx == nil {
97 tx, err = s.db.Begin()
98 if err != nil {
99 return
100 }
101 defer func() {
102 if err == nil {
103 err = tx.Commit()
104 } else {
105 tx.Rollback()
106 }
107 }()
108 }
109 query := "INSERT INTO users (id, username, email) VALUES (?, ?, ?)"
110 _, err = tx.Exec(query, user.Id, user.Username, user.Email)
111 return
112}
113
114func (s *SQLiteStore) scanUser(row scanner) (User, error) {
115 var ret User
116 if err := row.Scan(&ret.Id, &ret.Username, &ret.Email); err != nil {
117 return User{}, err
118 }
119 return ret, nil
120}
121
122func (s *SQLiteStore) scanUsers(rows *sql.Rows) ([]User, error) {
123 var ret []User
124 for rows.Next() {
125 if u, err := s.scanUser(rows); err != nil {
126 return nil, err
127 } else {
128 ret = append(ret, u)
129 }
130 }
131 if err := rows.Err(); err != nil {
132 return nil, err
133 }
134 return ret, nil
135}
136
137func (s *SQLiteStore) scanGroup(row scanner) (Group, error) {
138 var ret Group
139 if err := row.Scan(&ret.Id, &ret.Title, &ret.Description); err != nil {
140 return Group{}, err
141 }
142 return ret, nil
143}
144
145func (s *SQLiteStore) scanGroups(rows *sql.Rows) ([]Group, error) {
146 var ret []Group
147 for rows.Next() {
148 if g, err := s.scanGroup(rows); err != nil {
149 return nil, err
150 } else {
151 ret = append(ret, g)
152 }
153 }
154 if err := rows.Err(); err != nil {
155 return nil, err
156 }
157 return ret, nil
158}
159
160func (s *SQLiteStore) GetAllUsers(tx *sql.Tx) (ret []User, err error) {
161 if tx == nil {
162 tx, err = s.db.Begin()
163 if err != nil {
164 return
165 }
166 defer func() {
167 if err == nil {
168 err = tx.Commit()
169 }
170 if err != nil {
171 tx.Rollback()
172 }
173 }()
174 }
175 query := "SELECT id, username, email FROM users"
176 var rows *sql.Rows
177 if rows, err = tx.Query(query); err == nil {
178 ret, err = s.scanUsers(rows)
179 }
180 return
181}
182
183func (s *SQLiteStore) GetUser(tx *sql.Tx, id string) (ret User, err error) {
184 if tx == nil {
185 tx, err = s.db.Begin()
186 if err != nil {
187 return
188 }
189 defer func() {
190 if err == nil {
191 err = tx.Commit()
192 } else {
193 tx.Rollback()
194 }
195 }()
196 }
197 query := "SELECT id, username, email FROM users WHERE id = ?"
198 row := tx.QueryRow(query, id)
199 ret, err = s.scanUser(row)
200 return
201}
202
203func (s *SQLiteStore) GetUsers(tx *sql.Tx, ids []string) (ret []User, err error) {
204 if tx == nil {
205 tx, err = s.db.Begin()
206 if err != nil {
207 return
208 }
209 defer func() {
210 if err == nil {
211 err = tx.Commit()
212 }
213 if err != nil {
214 tx.Rollback()
215 }
216 }()
217 }
218 idPlaceholders := make([]string, len(ids))
219 idParams := make([]any, len(ids))
220 for i, id := range ids {
221 idPlaceholders[i] = "?"
222 idParams[i] = id
223 }
224 query := fmt.Sprintf("SELECT id, username, email FROM users WHERE id IN (%s)", strings.Join(idPlaceholders, ", "))
225 var rows *sql.Rows
226 if rows, err = tx.Query(query, idParams...); err == nil {
227 ret, err = s.scanUsers(rows)
228 }
229 return
230}
231
232func (s *SQLiteStore) CreateGroup(tx *sql.Tx, userId string, group Group) (err error) {
233 if tx == nil {
234 tx, err = s.db.Begin()
235 if err != nil {
236 return
237 }
238 defer func() {
239 if err == nil {
240 err = tx.Commit()
241 } else {
242 tx.Rollback()
243 }
244 }()
245 }
246 query := "INSERT INTO groups (id, title, description) VALUES (?, ?, ?)"
247 if _, err = tx.Exec(query, group.Id, group.Title, group.Description); err != nil {
248 sqliteErr, ok := err.(*sqlite3.Error)
249 if ok && sqliteErr.ExtendedCode() == ErrorConstraintPrimaryKeyViolation {
250 err = fmt.Errorf("Group with id %s already exists", group.Id)
251 }
252 return
253 }
254 err = s.addUser(tx, group.Id, userId, "owner")
255 return
256}
257
258func (s *SQLiteStore) GetGroup(tx *sql.Tx, id string) (ret Group, err error) {
259 if tx == nil {
260 tx, err = s.db.Begin()
261 if err != nil {
262 return
263 }
264 defer func() {
265 if err == nil {
266 err = tx.Commit()
267 } else {
268 tx.Rollback()
269 }
270 }()
271 }
272 query := "SELECT id, title, description FROM groups WHERE id = ?"
273 row := tx.QueryRow(query, id)
274 ret, err = s.scanGroup(row)
275 return
276}
277func (s *SQLiteStore) GetAllGroups(tx *sql.Tx) (ret []Group, err error) {
278 if tx == nil {
279 tx, err = s.db.Begin()
280 if err != nil {
281 return
282 }
283 defer func() {
284 if err == nil {
285 err = tx.Commit()
286 }
287 if err != nil {
288 tx.Rollback()
289 }
290 }()
291 }
292 query := "SELECT id, title, description FROM groups"
293 var rows *sql.Rows
294 if rows, err = tx.Query(query); err == nil {
295 ret, err = s.scanGroups(rows)
296 }
297 return
298}
299
300func (s *SQLiteStore) addUser(tx *sql.Tx, groupId, userId, membershipType string) (err error) {
301 if tx == nil {
302 tx, err = s.db.Begin()
303 if err != nil {
304 return
305 }
306 defer func() {
307 if err == nil {
308 err = tx.Commit()
309 }
310 if err != nil {
311 tx.Rollback()
312 }
313 }()
314 }
315 query := "INSERT INTO memberships (id, membership_type, member_type, user_id) VALUES (?, ?, ?, ?)"
316 _, err = tx.Exec(query, groupId, membershipType, "user", userId)
317 return
318}
319
320func (s *SQLiteStore) addGroup(tx *sql.Tx, groupId, otherId, membershipType string) (err error) {
321 if tx == nil {
322 tx, err = s.db.Begin()
323 if err != nil {
324 return
325 }
326 defer func() {
327 if err == nil {
328 err = tx.Commit()
329 }
330 if err != nil {
331 tx.Rollback()
332 }
333 }()
334 }
335 query := "INSERT INTO memberships (id, membership_type, member_type, group_id) VALUES (?, ?, ?, ?)"
336 _, err = tx.Exec(query, groupId, membershipType, "group", otherId)
337 return
338}
339
340func (s *SQLiteStore) removeUser(tx *sql.Tx, groupId, userId, membershipType string) (err error) {
341 if tx == nil {
342 tx, err = s.db.Begin()
343 if err != nil {
344 return
345 }
346 defer func() {
347 if err == nil {
348 err = tx.Commit()
349 }
350 if err != nil {
351 tx.Rollback()
352 }
353 }()
354 }
355 query := "DELETE FROM memberships WHERE id = ? AND membership_type = ? AND member_type = ? AND user_id = ?"
356 _, err = tx.Exec(query, groupId, membershipType, "user", userId)
357 return
358}
359
360func (s *SQLiteStore) removeGroup(tx *sql.Tx, groupId, otherId, membershipType string) (err error) {
361 if tx == nil {
362 tx, err = s.db.Begin()
363 if err != nil {
364 return
365 }
366 defer func() {
367 if err == nil {
368 err = tx.Commit()
369 }
370 if err != nil {
371 tx.Rollback()
372 }
373 }()
374 }
375 query := "DELETE FROM memberships WHERE id = ? AND membership_type = ? AND member_type = ? AND group_id = ?"
376 _, err = tx.Exec(query, groupId, membershipType, "group", otherId)
377 return
378}
379
380func (s *SQLiteStore) getUsers(tx *sql.Tx, groupId, membershipType string) (ret []User, err error) {
381 if tx == nil {
382 tx, err = s.db.Begin()
383 if err != nil {
384 return
385 }
386 defer func() {
387 if err == nil {
388 err = tx.Commit()
389 }
390 if err != nil {
391 tx.Rollback()
392 }
393 }()
394 }
395 query := `
396SELECT u.id, u.username, u.email
397FROM users u
398JOIN memberships m
399ON u.id = m.user_id
400WHERE m.id = ? AND membership_type = ? AND member_type = ?
401`
402 var rows *sql.Rows
403 rows, err = tx.Query(query, groupId, membershipType, "user")
404 if err != nil {
405 return
406 }
407 ret, err = s.scanUsers(rows)
408 return
409}
410
411func (s *SQLiteStore) getGroups(tx *sql.Tx, groupId, membershipType string) (ret []Group, err error) {
412 if tx == nil {
413 tx, err = s.db.Begin()
414 if err != nil {
415 return
416 }
417 defer func() {
418 if err == nil {
419 err = tx.Commit()
420 }
421 if err != nil {
422 tx.Rollback()
423 }
424 }()
425 }
426 query := `
427SELECT g.id, g.title, g.description
428FROM groups AS g
429JOIN memberships AS m
430ON g.id = m.group_id
431WHERE m.id = ? AND m.membership_type = ? AND m.member_type = ?`
432 var rows *sql.Rows
433 if rows, err = tx.Query(query, groupId, membershipType, "group"); err == nil {
434 ret, err = s.scanGroups(rows)
435 }
436 return
437}
438
439func (s *SQLiteStore) AddMemberUser(tx *sql.Tx, groupId, userId string) error {
440 return s.addUser(tx, groupId, userId, "member")
441}
442
443func (s *SQLiteStore) AddOwnerUser(tx *sql.Tx, groupId, userId string) error {
444 return s.addUser(tx, groupId, userId, "owner")
445}
446
447func (s *SQLiteStore) AddMemberGroup(tx *sql.Tx, groupId, otherId string) error {
448 return s.addGroup(tx, groupId, otherId, "member")
449}
450
451func (s *SQLiteStore) AddOwnerGroup(tx *sql.Tx, groupId, otherId string) error {
452 return s.addGroup(tx, groupId, otherId, "owner")
453}
454
455func (s *SQLiteStore) GetMemberUsers(tx *sql.Tx, groupId string) ([]User, error) {
456 return s.getUsers(tx, groupId, "member")
457}
458
459func (s *SQLiteStore) GetOwnerUsers(tx *sql.Tx, groupId string) ([]User, error) {
460 return s.getUsers(tx, groupId, "owner")
461}
462
463func (s *SQLiteStore) GetMemberGroups(tx *sql.Tx, groupId string) ([]Group, error) {
464 return s.getGroups(tx, groupId, "member")
465}
466
467func (s *SQLiteStore) GetOwnerGroups(tx *sql.Tx, groupId string) ([]Group, error) {
468 return s.getGroups(tx, groupId, "owner")
469}
470
471func (s *SQLiteStore) RemoveMemberUser(tx *sql.Tx, groupId, userId string) error {
472 return s.removeUser(tx, groupId, userId, "member")
473}
474
475func (s *SQLiteStore) RemoveOwnerUser(tx *sql.Tx, groupId, userId string) error {
476 return s.removeUser(tx, groupId, userId, "owner")
477}
478
479func (s *SQLiteStore) RemoveMemberGroup(tx *sql.Tx, groupId, otherId string) error {
480 return s.removeGroup(tx, groupId, otherId, "member")
481}
482
483func (s *SQLiteStore) RemoveOwnerGroup(tx *sql.Tx, groupId, otherId string) error {
484 return s.removeGroup(tx, groupId, otherId, "owner")
485}
486
487func (s *SQLiteStore) traverseGroups(tx *sql.Tx, groups ...Group) ([]Group, error) {
488 seen := map[string]struct{}{}
489 for _, g := range groups {
490 seen[g.Id] = struct{}{}
491 }
492 for i := 0; i < len(groups); i++ {
493 g := groups[i]
494 parents, err := s.getGroupGroups(tx, g.Id, "member")
495 if err != nil {
496 return nil, err
497 }
498 for _, p := range parents {
499 if _, ok := seen[p.Id]; !ok {
500 groups = append(groups, p)
501 seen[p.Id] = struct{}{}
502 }
503 }
504 }
505 return groups, nil
506}
507
508func (s *SQLiteStore) GetGroupsUserCanActAs(tx *sql.Tx, userId string) (ret []Group, err error) {
509 if tx == nil {
510 tx, err = s.db.Begin()
511 if err != nil {
512 return
513 }
514 defer func() {
515 if err == nil {
516 err = tx.Commit()
517 }
518 if err != nil {
519 tx.Rollback()
520 }
521 }()
522 }
523 var memberOf []Group
524 memberOf, err = s.GetGroupsUserIsMemberOf(tx, userId)
525 if err != nil {
526 return
527 }
528 ret, err = s.traverseGroups(tx, memberOf...)
529 return
530}
531
532func (s *SQLiteStore) GetGroupsGroupCanActAs(tx *sql.Tx, groupId string) (ret []Group, err error) {
533 if tx == nil {
534 tx, err = s.db.Begin()
535 if err != nil {
536 return
537 }
538 defer func() {
539 if err == nil {
540 err = tx.Commit()
541 }
542 if err != nil {
543 tx.Rollback()
544 }
545 }()
546 }
547 var g Group
548 g, err = s.GetGroup(tx, groupId)
549 if err != nil {
550 return
551 }
552 var groups []Group
553 if groups, err = s.traverseGroups(tx, g); err == nil {
554 ret = groups[1:]
555 }
556 return
557}
558
559func (s *SQLiteStore) getUserGroups(tx *sql.Tx, userId, membershipType string) (ret []Group, err error) {
560 if tx == nil {
561 tx, err = s.db.Begin()
562 if err != nil {
563 return
564 }
565 defer func() {
566 if err == nil {
567 err = tx.Commit()
568 }
569 if err != nil {
570 tx.Rollback()
571 }
572 }()
573 }
574 query := `
575SELECT g.id, g.title, g.description
576FROM groups AS g
577JOIN memberships AS m
578ON g.id = m.id
579WHERE m.user_id = ? AND m.membership_type = ? AND m.member_type = ?`
580 var rows *sql.Rows
581 if rows, err = tx.Query(query, userId, membershipType, "user"); err == nil {
582 ret, err = s.scanGroups(rows)
583 }
584 return
585}
586
587func (s *SQLiteStore) getGroupGroups(tx *sql.Tx, groupId, membershipType string) (ret []Group, err error) {
588 if tx == nil {
589 tx, err = s.db.Begin()
590 if err != nil {
591 return
592 }
593 defer func() {
594 if err == nil {
595 err = tx.Commit()
596 } else {
597 tx.Rollback()
598 }
599 }()
600 }
601 query := `
602SELECT g.id, g.title, g.description
603FROM groups AS g
604JOIN memberships AS m
605ON g.id = m.id
606WHERE m.group_id = ? AND m.membership_type = ? AND m.member_type = ?`
607 var rows *sql.Rows
608 if rows, err = tx.Query(query, groupId, membershipType, "group"); err == nil {
609 ret, err = s.scanGroups(rows)
610 }
611 return
612}
613
614func (s *SQLiteStore) GetGroupsUserOwns(tx *sql.Tx, userId string) ([]Group, error) {
615 return s.getUserGroups(tx, userId, "owner")
616}
617
618func (s *SQLiteStore) GetGroupsUserIsMemberOf(tx *sql.Tx, userId string) ([]Group, error) {
619 return s.getUserGroups(tx, userId, "member")
620}
621
622func (s *SQLiteStore) AddUserPublicKey(tx *sql.Tx, userId, publicKey string) (err error) {
623 if tx == nil {
624 tx, err = s.db.Begin()
625 if err != nil {
626 return
627 }
628 defer func() {
629 if err == nil {
630 err = tx.Commit()
631 } else {
632 tx.Rollback()
633 }
634 }()
635 }
636 query := "INSERT INTO keys (user_id, public_key) VALUES (?, ?)"
637 _, err = tx.Exec(query, userId, publicKey)
638 return
639}
640
641func (s *SQLiteStore) GetUserPublicKeys(tx *sql.Tx, userId string) (ret []string, err error) {
642 if tx == nil {
643 tx, err = s.db.Begin()
644 if err != nil {
645 return
646 }
647 defer func() {
648 if err == nil {
649 err = tx.Commit()
650 } else {
651 tx.Rollback()
652 }
653 }()
654 }
655 query := "SELECT public_key FROM keys WHERE user_id = ?"
656 var rows *sql.Rows
657 if rows, err = tx.Query(query, userId); err != nil {
658 return
659 }
660 defer rows.Close()
661
662 for rows.Next() {
663 var publicKey string
664 if err = rows.Scan(&publicKey); err != nil {
665 return
666 }
667 ret = append(ret, publicKey)
668 }
669 err = rows.Err()
670 return
671}
672
673func (s *SQLiteStore) RemoveUserPublicKey(tx *sql.Tx, userId, publicKey string) (err error) {
674 if tx == nil {
675 tx, err = s.db.Begin()
676 if err != nil {
677 return
678 }
679 defer func() {
680 if err == nil {
681 err = tx.Commit()
682 } else {
683 tx.Rollback()
684 }
685 }()
686 }
687 query := "DELETE FROM keys WHERE user_id = ? AND public_key = ?"
688 _, err = tx.Exec(query, userId, publicKey)
689 return
690}