blob: 4b641409c62df180470c08c9c44b25e3f0a4a589 [file] [log] [blame]
DTabidze0d802592024-03-19 17:42:45 +04001package main
2
3import (
4 "database/sql"
5 "embed"
DTabidzed7744a62024-03-20 14:09:15 +04006 "encoding/json"
DTabidze0d802592024-03-19 17:42:45 +04007 "flag"
8 "fmt"
9 "html/template"
10 "log"
11 "net/http"
DTabidze0d802592024-03-19 17:42:45 +040012
13 "github.com/ncruces/go-sqlite3"
14 _ "github.com/ncruces/go-sqlite3/driver"
15 _ "github.com/ncruces/go-sqlite3/embed"
DTabidzed7744a62024-03-20 14:09:15 +040016
17 "github.com/gorilla/mux"
DTabidze0d802592024-03-19 17:42:45 +040018)
19
20var port = flag.Int("port", 8080, "ort to listen on")
21var dbPath = flag.String("db-path", "memberships.db", "Path to SQLite file")
22
23//go:embed index.html
24var indexHTML string
25
26//go:embed group.html
27var groupHTML string
28
29//go:embed static
30var staticResources embed.FS
31
32type Store interface {
33 CreateGroup(owner string, group Group) error
34 AddChildGroup(parent, child string) error
35 GetGroupsOwnedBy(user string) ([]Group, error)
DTabidzed7744a62024-03-20 14:09:15 +040036 GetGroupsUserBelongsTo(user string) ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040037 IsGroupOwner(user, group string) (bool, error)
38 AddGroupMember(user, group string) error
39 AddGroupOwner(user, group string) error
40 GetGroupOwners(group string) ([]string, error)
41 GetGroupMembers(group string) ([]string, error)
42 GetGroupDescription(group string) (string, error)
43 GetAvailableGroupsAsChild(group string) ([]string, error)
DTabidzec0b4d8f2024-03-22 17:25:10 +040044 GetAllTransitiveGroupsForUser(user string) ([]Group, error)
45 GetGroupsGroupBelongsTo(group string) ([]Group, error)
46 GetDirectChildrenGroups(group string) ([]Group, error)
47 GetAllTransitiveGroupsForGroup(group string) ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040048}
49
50type Server struct {
51 store Store
52}
53
54type Group struct {
55 Name string
56 Description string
57}
58
59type SQLiteStore struct {
60 db *sql.DB
61}
62
DTabidzec0b4d8f2024-03-22 17:25:10 +040063func NewSQLiteStore(db *sql.DB) (*SQLiteStore, error) {
64 _, err := db.Exec(`
DTabidze0d802592024-03-19 17:42:45 +040065 CREATE TABLE IF NOT EXISTS groups (
66 name TEXT PRIMARY KEY,
67 description TEXT
68 );
69
70 CREATE TABLE IF NOT EXISTS owners (
71 username TEXT,
72 group_name TEXT,
73 FOREIGN KEY(group_name) REFERENCES groups(name)
74 );
75
76 CREATE TABLE IF NOT EXISTS group_to_group (
77 parent_group TEXT,
78 child_group TEXT,
79 FOREIGN KEY(parent_group) REFERENCES groups(name),
80 FOREIGN KEY(child_group) REFERENCES groups(name)
81 );
82
83 CREATE TABLE IF NOT EXISTS user_to_group (
84 username TEXT,
85 group_name TEXT,
86 FOREIGN KEY(group_name) REFERENCES groups(name)
87 );`)
88 if err != nil {
89 return nil, err
90 }
91 return &SQLiteStore{db: db}, nil
92}
93
94func (s *SQLiteStore) queryGroups(query string, args ...interface{}) ([]Group, error) {
95 groups := make([]Group, 0)
96 rows, err := s.db.Query(query, args...)
97 if err != nil {
98 return nil, err
99 }
100 defer rows.Close()
101 for rows.Next() {
102 var group Group
103 if err := rows.Scan(&group.Name, &group.Description); err != nil {
104 return nil, err
105 }
106 groups = append(groups, group)
107 }
108 if err := rows.Err(); err != nil {
109 return nil, err
110 }
111 return groups, nil
112}
113
114func (s *SQLiteStore) GetGroupsOwnedBy(user string) ([]Group, error) {
115 query := `
116 SELECT groups.name, groups.description
117 FROM groups
118 JOIN owners ON groups.name = owners.group_name
119 WHERE owners.username = ?`
120 return s.queryGroups(query, user)
121}
122
DTabidzed7744a62024-03-20 14:09:15 +0400123func (s *SQLiteStore) GetGroupsUserBelongsTo(user string) ([]Group, error) {
DTabidze0d802592024-03-19 17:42:45 +0400124 query := `
125 SELECT groups.name, groups.description
126 FROM groups
127 JOIN user_to_group ON groups.name = user_to_group.group_name
128 WHERE user_to_group.username = ?`
129 return s.queryGroups(query, user)
130}
131
132func (s *SQLiteStore) CreateGroup(owner string, group Group) error {
133 tx, err := s.db.Begin()
134 if err != nil {
135 return err
136 }
137 defer tx.Rollback()
138 query := `INSERT INTO groups (name, description) VALUES (?, ?)`
139 if _, err := tx.Exec(query, group.Name, group.Description); err != nil {
140 sqliteErr, ok := err.(*sqlite3.Error)
141 if ok && sqliteErr.ExtendedCode() == 1555 {
142 return fmt.Errorf("Group with the name %s already exists", group.Name)
143 }
144 return err
145 }
146 query = `INSERT INTO owners (username, group_name) VALUES (?, ?)`
147 if _, err := tx.Exec(query, owner, group.Name); err != nil {
148 return err
149 }
150 if err := tx.Commit(); err != nil {
151 return err
152 }
153 return nil
154}
155
156func (s *SQLiteStore) IsGroupOwner(user, group string) (bool, error) {
157 query := `
158 SELECT EXISTS (
159 SELECT 1
160 FROM owners
161 WHERE username = ? AND group_name = ?
162 )`
163 var exists bool
164 if err := s.db.QueryRow(query, user, group).Scan(&exists); err != nil {
165 return false, err
166 }
167 return exists, nil
168}
169
170func (s *SQLiteStore) userGroupPairExists(tx *sql.Tx, table, user, group string) (bool, error) {
171 query := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM %s WHERE username = ? AND group_name = ?)", table)
172 var exists bool
173 if err := tx.QueryRow(query, user, group).Scan(&exists); err != nil {
174 return false, err
175 }
176 return exists, nil
177}
178
179func (s *SQLiteStore) AddGroupMember(user, group string) error {
180 tx, err := s.db.Begin()
181 if err != nil {
182 return err
183 }
184 defer tx.Rollback()
185 existsInUserToGroup, err := s.userGroupPairExists(tx, "user_to_group", user, group)
186 if err != nil {
187 return err
188 }
189 if existsInUserToGroup {
190 return fmt.Errorf("%s is already a member of group %s", user, group)
191 }
192 if _, err := tx.Exec(`INSERT INTO user_to_group (username, group_name) VALUES (?, ?)`, user, group); err != nil {
193 return err
194 }
195 if err := tx.Commit(); err != nil {
196 return err
197 }
198 return nil
199}
200
201func (s *SQLiteStore) AddGroupOwner(user, group string) error {
202 tx, err := s.db.Begin()
203 if err != nil {
204 return err
205 }
206 defer tx.Rollback()
207 existsInOwners, err := s.userGroupPairExists(tx, "owners", user, group)
208 if err != nil {
209 return err
210 }
211 if existsInOwners {
212 return fmt.Errorf("%s is already an owner of group %s", user, group)
213 }
214 if _, err = tx.Exec(`INSERT INTO owners (username, group_name) VALUES (?, ?)`, user, group); err != nil {
215 return err
216 }
217 if err := tx.Commit(); err != nil {
218 return err
219 }
220 return nil
221}
222
223func (s *SQLiteStore) getUsersByGroup(table, group string) ([]string, error) {
224 query := fmt.Sprintf("SELECT username FROM %s WHERE group_name = ?", table)
225 rows, err := s.db.Query(query, group)
226 if err != nil {
227 return nil, err
228 }
229 defer rows.Close()
230 var users []string
231 for rows.Next() {
232 var username string
233 if err := rows.Scan(&username); err != nil {
234 return nil, err
235 }
236 users = append(users, username)
237 }
238 if err := rows.Err(); err != nil {
239 return nil, err
240 }
241 return users, nil
242}
243
244func (s *SQLiteStore) GetGroupOwners(group string) ([]string, error) {
245 return s.getUsersByGroup("owners", group)
246}
247
248func (s *SQLiteStore) GetGroupMembers(group string) ([]string, error) {
249 return s.getUsersByGroup("user_to_group", group)
250}
251
252func (s *SQLiteStore) GetGroupDescription(group string) (string, error) {
253 var description string
254 query := `SELECT description FROM groups WHERE name = ?`
255 if err := s.db.QueryRow(query, group).Scan(&description); err != nil {
256 return "", err
257 }
258 return description, nil
259}
260
261func (s *SQLiteStore) parentChildGroupPairExists(tx *sql.Tx, parent, child string) (bool, error) {
262 query := `SELECT EXISTS (SELECT 1 FROM group_to_group WHERE parent_group = ? AND child_group = ?)`
263 var exists bool
264 if err := tx.QueryRow(query, parent, child).Scan(&exists); err != nil {
265 return false, err
266 }
267 return exists, nil
268}
269
270func (s *SQLiteStore) AddChildGroup(parent, child string) error {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400271 parentGroups, err := s.GetAllTransitiveGroupsForGroup(parent)
272 if err != nil {
273 return err
274 }
275 for _, group := range parentGroups {
276 if group.Name == child {
277 return fmt.Errorf("circular reference detected: group %s is already a parent of group %s", child, parent)
278 }
279 }
DTabidze0d802592024-03-19 17:42:45 +0400280 tx, err := s.db.Begin()
281 if err != nil {
282 return err
283 }
284 defer tx.Rollback()
285 existsInGroupToGroup, err := s.parentChildGroupPairExists(tx, parent, child)
286 if err != nil {
287 return err
288 }
289 if existsInGroupToGroup {
290 return fmt.Errorf("child group name %s already exists in group %s", child, parent)
291 }
292 if _, err := tx.Exec(`INSERT INTO group_to_group (parent_group, child_group) VALUES (?, ?)`, parent, child); err != nil {
293 return err
294 }
295 if err := tx.Commit(); err != nil {
296 return err
297 }
298 return nil
299}
300
301func (s *SQLiteStore) GetAvailableGroupsAsChild(group string) ([]string, error) {
302 // TODO(dtabidze): Might have to add further logic to filter available groups as children.
303 query := `
304 SELECT name FROM groups
305 WHERE name != ? AND name NOT IN (
306 SELECT child_group FROM group_to_group WHERE parent_group = ?
307 )
308 `
309 rows, err := s.db.Query(query, group, group)
310 if err != nil {
311 return nil, err
312 }
313 defer rows.Close()
314 var availableGroups []string
315 for rows.Next() {
316 var groupName string
317 if err := rows.Scan(&groupName); err != nil {
318 return nil, err
319 }
320 availableGroups = append(availableGroups, groupName)
321 }
322 return availableGroups, nil
323}
324
DTabidzec0b4d8f2024-03-22 17:25:10 +0400325func (s *SQLiteStore) GetAllTransitiveGroupsForUser(user string) ([]Group, error) {
326 if groups, err := s.GetGroupsUserBelongsTo(user); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400327 return nil, err
DTabidzec0b4d8f2024-03-22 17:25:10 +0400328 } else {
329 visited := map[string]struct{}{}
330 return s.getAllParentGroupsRecursive(groups, visited)
DTabidzed7744a62024-03-20 14:09:15 +0400331 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400332}
333
334func (s *SQLiteStore) GetAllTransitiveGroupsForGroup(group string) ([]Group, error) {
335 if p, err := s.GetGroupsGroupBelongsTo(group); err != nil {
336 return nil, err
337 } else {
338 // Mark initial group as visited
339 visited := map[string]struct{}{
340 group: struct{}{},
341 }
342 return s.getAllParentGroupsRecursive(p, visited)
343 }
344}
345
346func (s *SQLiteStore) getAllParentGroupsRecursive(groups []Group, visited map[string]struct{}) ([]Group, error) {
347 var ret []Group
348 for _, g := range groups {
349 if _, ok := visited[g.Name]; ok {
350 continue
351 }
352 visited[g.Name] = struct{}{}
353 ret = append(ret, g)
354 if p, err := s.GetGroupsGroupBelongsTo(g.Name); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400355 return nil, err
DTabidzec0b4d8f2024-03-22 17:25:10 +0400356 } else if res, err := s.getAllParentGroupsRecursive(p, visited); err != nil {
357 return nil, err
358 } else {
359 ret = append(ret, res...)
DTabidzed7744a62024-03-20 14:09:15 +0400360 }
361 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400362 return ret, nil
DTabidzed7744a62024-03-20 14:09:15 +0400363}
364
DTabidzec0b4d8f2024-03-22 17:25:10 +0400365func (s *SQLiteStore) GetGroupsGroupBelongsTo(group string) ([]Group, error) {
366 query := `
367 SELECT groups.name, groups.description
368 FROM groups
369 JOIN group_to_group ON groups.name = group_to_group.parent_group
370 WHERE group_to_group.child_group = ?`
DTabidzed7744a62024-03-20 14:09:15 +0400371 rows, err := s.db.Query(query, group)
372 if err != nil {
373 return nil, err
374 }
375 defer rows.Close()
DTabidzec0b4d8f2024-03-22 17:25:10 +0400376 var parentGroups []Group
DTabidzed7744a62024-03-20 14:09:15 +0400377 for rows.Next() {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400378 var parentGroup Group
379 if err := rows.Scan(&parentGroup.Name, &parentGroup.Description); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400380 return nil, err
381 }
382 parentGroups = append(parentGroups, parentGroup)
383 }
384 if err := rows.Err(); err != nil {
385 return nil, err
386 }
387 return parentGroups, nil
388}
389
DTabidzec0b4d8f2024-03-22 17:25:10 +0400390func (s *SQLiteStore) GetDirectChildrenGroups(group string) ([]Group, error) {
391 query := `
392 SELECT groups.name, groups.description
393 FROM groups
394 JOIN group_to_group ON groups.name = group_to_group.child_group
395 WHERE group_to_group.parent_group = ?`
396 rows, err := s.db.Query(query, group)
397 if err != nil {
398 return nil, err
399 }
400 defer rows.Close()
401 var childrenGroups []Group
402 for rows.Next() {
403 var childGroup Group
404 if err := rows.Scan(&childGroup.Name, &childGroup.Description); err != nil {
405 return nil, err
406 }
407 childrenGroups = append(childrenGroups, childGroup)
408 }
409 if err := rows.Err(); err != nil {
410 return nil, err
411 }
412 return childrenGroups, nil
413}
414
DTabidze0d802592024-03-19 17:42:45 +0400415func getLoggedInUser(r *http.Request) (string, error) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400416 if user := r.Header.Get("X-User"); user != "" {
417 return user, nil
418 } else {
419 return "", fmt.Errorf("unauthenticated")
420 }
DTabidze0d802592024-03-19 17:42:45 +0400421}
422
423type Status int
424
425const (
426 Owner Status = iota
427 Member
428)
429
430func convertStatus(status string) (Status, error) {
431 switch status {
432 case "Owner":
433 return Owner, nil
434 case "Member":
435 return Member, nil
436 default:
437 return Owner, fmt.Errorf("invalid status: %s", status)
438 }
439}
440
441func (s *Server) Start() {
DTabidzed7744a62024-03-20 14:09:15 +0400442 router := mux.NewRouter()
443 router.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticResources)))
444 router.HandleFunc("/group/{group-name}", s.groupHandler)
445 router.HandleFunc("/create-group", s.createGroupHandler)
446 router.HandleFunc("/add-user", s.addUserHandler)
447 router.HandleFunc("/add-child-group", s.addChildGroupHandler)
448 router.HandleFunc("/api/user/{username}", s.apiMemberOfHandler)
449 router.HandleFunc("/", s.homePageHandler)
450 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), router))
DTabidze0d802592024-03-19 17:42:45 +0400451}
452
453type GroupData struct {
454 Group Group
455 Membership string
456}
457
458func (s *Server) checkIsOwner(w http.ResponseWriter, user, group string) (bool, error) {
459 isOwner, err := s.store.IsGroupOwner(user, group)
460 if err != nil {
461 http.Error(w, err.Error(), http.StatusInternalServerError)
462 return false, err
463 }
464 if !isOwner {
465 http.Error(w, fmt.Sprintf("You are not the owner of the group %s", group), http.StatusUnauthorized)
466 return false, nil
467 }
468 return true, nil
469}
470
471func (s *Server) homePageHandler(w http.ResponseWriter, r *http.Request) {
472 loggedInUser, err := getLoggedInUser(r)
473 if err != nil {
474 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
475 return
476 }
477 ownerGroups, err := s.store.GetGroupsOwnedBy(loggedInUser)
478 if err != nil {
479 http.Error(w, err.Error(), http.StatusInternalServerError)
480 return
481 }
DTabidzed7744a62024-03-20 14:09:15 +0400482 membershipGroups, err := s.store.GetGroupsUserBelongsTo(loggedInUser)
DTabidze0d802592024-03-19 17:42:45 +0400483 if err != nil {
484 http.Error(w, err.Error(), http.StatusInternalServerError)
485 return
486 }
487 tmpl, err := template.New("index").Parse(indexHTML)
488 if err != nil {
489 http.Error(w, err.Error(), http.StatusInternalServerError)
490 return
491 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400492 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(loggedInUser)
493 if err != nil {
494 http.Error(w, err.Error(), http.StatusInternalServerError)
495 return
496 }
DTabidze0d802592024-03-19 17:42:45 +0400497 data := struct {
498 OwnerGroups []Group
499 MembershipGroups []Group
DTabidzec0b4d8f2024-03-22 17:25:10 +0400500 TransitiveGroups []Group
DTabidze0d802592024-03-19 17:42:45 +0400501 }{
502 OwnerGroups: ownerGroups,
503 MembershipGroups: membershipGroups,
DTabidzec0b4d8f2024-03-22 17:25:10 +0400504 TransitiveGroups: transitiveGroups,
DTabidze0d802592024-03-19 17:42:45 +0400505 }
506 w.Header().Set("Content-Type", "text/html")
507 if err := tmpl.Execute(w, data); err != nil {
508 http.Error(w, err.Error(), http.StatusInternalServerError)
509 return
510 }
511}
512
513func (s *Server) createGroupHandler(w http.ResponseWriter, r *http.Request) {
514 loggedInUser, err := getLoggedInUser(r)
515 if err != nil {
516 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
517 return
518 }
519 if r.Method != http.MethodPost {
520 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
521 return
522 }
523 if err := r.ParseForm(); err != nil {
524 http.Error(w, err.Error(), http.StatusInternalServerError)
525 return
526 }
527 var group Group
528 group.Name = r.PostFormValue("group-name")
529 group.Description = r.PostFormValue("description")
530 if err := s.store.CreateGroup(loggedInUser, group); err != nil {
531 http.Error(w, err.Error(), http.StatusInternalServerError)
532 return
533 }
534 http.Redirect(w, r, "/", http.StatusSeeOther)
535}
536
537func (s *Server) groupHandler(w http.ResponseWriter, r *http.Request) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400538 _, err := getLoggedInUser(r)
539 if err != nil {
540 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
541 return
542 }
DTabidzed7744a62024-03-20 14:09:15 +0400543 vars := mux.Vars(r)
544 groupName := vars["group-name"]
DTabidze0d802592024-03-19 17:42:45 +0400545 tmpl, err := template.New("group").Parse(groupHTML)
546 if err != nil {
547 http.Error(w, err.Error(), http.StatusInternalServerError)
548 return
549 }
550 owners, err := s.store.GetGroupOwners(groupName)
551 if err != nil {
552 http.Error(w, err.Error(), http.StatusInternalServerError)
553 return
554 }
555 members, err := s.store.GetGroupMembers(groupName)
556 if err != nil {
557 http.Error(w, err.Error(), http.StatusInternalServerError)
558 return
559 }
560 description, err := s.store.GetGroupDescription(groupName)
561 if err != nil {
562 http.Error(w, err.Error(), http.StatusInternalServerError)
563 return
564 }
565 availableGroups, err := s.store.GetAvailableGroupsAsChild(groupName)
566 if err != nil {
567 http.Error(w, err.Error(), http.StatusInternalServerError)
568 return
569 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400570 transitiveGroups, err := s.store.GetAllTransitiveGroupsForGroup(groupName)
571 if err != nil {
572 http.Error(w, err.Error(), http.StatusInternalServerError)
573 return
574 }
575 childGroups, err := s.store.GetDirectChildrenGroups(groupName)
576 if err != nil {
577 http.Error(w, err.Error(), http.StatusInternalServerError)
578 return
579 }
DTabidze0d802592024-03-19 17:42:45 +0400580 data := struct {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400581 GroupName string
582 Description string
583 Owners []string
584 Members []string
585 AvailableGroups []string
586 TransitiveGroups []Group
587 ChildGroups []Group
DTabidze0d802592024-03-19 17:42:45 +0400588 }{
DTabidzec0b4d8f2024-03-22 17:25:10 +0400589 GroupName: groupName,
590 Description: description,
591 Owners: owners,
592 Members: members,
593 AvailableGroups: availableGroups,
594 TransitiveGroups: transitiveGroups,
595 ChildGroups: childGroups,
DTabidze0d802592024-03-19 17:42:45 +0400596 }
597 if err := tmpl.Execute(w, data); err != nil {
598 http.Error(w, err.Error(), http.StatusInternalServerError)
599 return
600 }
601}
602
603func (s *Server) addUserHandler(w http.ResponseWriter, r *http.Request) {
604 if r.Method != http.MethodPost {
605 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
606 return
607 }
608 loggedInUser, err := getLoggedInUser(r)
609 if err != nil {
610 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
611 return
612 }
613 groupName := r.FormValue("group")
614 username := r.FormValue("username")
615 status, err := convertStatus(r.FormValue("status"))
616 if err != nil {
617 http.Error(w, err.Error(), http.StatusBadRequest)
618 return
619 }
620 if _, err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
621 return
622 }
623 switch status {
624 case Owner:
625 err = s.store.AddGroupOwner(username, groupName)
626 case Member:
627 err = s.store.AddGroupMember(username, groupName)
628 default:
629 http.Error(w, "Invalid status", http.StatusBadRequest)
630 return
631 }
632 if err != nil {
633 http.Error(w, err.Error(), http.StatusInternalServerError)
634 return
635 }
636 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
637}
638
639func (s *Server) addChildGroupHandler(w http.ResponseWriter, r *http.Request) {
640 // TODO(dtabidze): In future we might need to make one group OWNER of another and not just a member.
641 if r.Method != http.MethodPost {
642 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
643 return
644 }
645 loggedInUser, err := getLoggedInUser(r)
646 if err != nil {
647 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
648 return
649 }
650 parentGroup := r.FormValue("parent-group")
651 childGroup := r.FormValue("child-group")
652 if _, err := s.checkIsOwner(w, loggedInUser, parentGroup); err != nil {
653 return
654 }
655 if err := s.store.AddChildGroup(parentGroup, childGroup); err != nil {
656 http.Error(w, err.Error(), http.StatusInternalServerError)
657 return
658 }
659 http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)
660}
661
DTabidzed7744a62024-03-20 14:09:15 +0400662type UserInfo struct {
663 MemberOf []string `json:"memberOf"`
664}
665
666func (s *Server) apiMemberOfHandler(w http.ResponseWriter, r *http.Request) {
667 vars := mux.Vars(r)
668 user, ok := vars["username"]
669 if !ok {
670 http.Error(w, "Username parameter is required", http.StatusBadRequest)
671 return
672 }
673 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(user)
674 if err != nil {
675 http.Error(w, err.Error(), http.StatusInternalServerError)
676 return
677 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400678 var groupNames []string
679 for _, group := range transitiveGroups {
680 groupNames = append(groupNames, group.Name)
681 }
DTabidzed7744a62024-03-20 14:09:15 +0400682 w.Header().Set("Content-Type", "application/json")
DTabidzec0b4d8f2024-03-22 17:25:10 +0400683 if err := json.NewEncoder(w).Encode(UserInfo{MemberOf: groupNames}); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400684 http.Error(w, err.Error(), http.StatusInternalServerError)
685 return
686 }
687}
688
DTabidze0d802592024-03-19 17:42:45 +0400689func main() {
690 flag.Parse()
DTabidzec0b4d8f2024-03-22 17:25:10 +0400691 db, err := sql.Open("sqlite3", *dbPath)
DTabidze0d802592024-03-19 17:42:45 +0400692 if err != nil {
693 panic(err)
694 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400695 store, err := NewSQLiteStore(db)
696 if err != nil {
697 panic(err)
698 }
699 s := Server{store}
DTabidze0d802592024-03-19 17:42:45 +0400700 s.Start()
701}