blob: 1358bb372c80d324f25032e3ca16e9eadf9f2f5b [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 {
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +040033 // Initializes store with admin user and their groups.
34 Init(owner string, groups []string) error
DTabidze0d802592024-03-19 17:42:45 +040035 CreateGroup(owner string, group Group) error
36 AddChildGroup(parent, child string) error
37 GetGroupsOwnedBy(user string) ([]Group, error)
DTabidzed7744a62024-03-20 14:09:15 +040038 GetGroupsUserBelongsTo(user string) ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040039 IsGroupOwner(user, group string) (bool, error)
40 AddGroupMember(user, group string) error
41 AddGroupOwner(user, group string) error
42 GetGroupOwners(group string) ([]string, error)
43 GetGroupMembers(group string) ([]string, error)
44 GetGroupDescription(group string) (string, error)
45 GetAvailableGroupsAsChild(group string) ([]string, error)
DTabidzec0b4d8f2024-03-22 17:25:10 +040046 GetAllTransitiveGroupsForUser(user string) ([]Group, error)
47 GetGroupsGroupBelongsTo(group string) ([]Group, error)
48 GetDirectChildrenGroups(group string) ([]Group, error)
49 GetAllTransitiveGroupsForGroup(group string) ([]Group, error)
DTabidze0d802592024-03-19 17:42:45 +040050}
51
52type Server struct {
53 store Store
54}
55
56type Group struct {
57 Name string
58 Description string
59}
60
61type SQLiteStore struct {
62 db *sql.DB
63}
64
DTabidzec0b4d8f2024-03-22 17:25:10 +040065func NewSQLiteStore(db *sql.DB) (*SQLiteStore, error) {
66 _, err := db.Exec(`
DTabidze0d802592024-03-19 17:42:45 +040067 CREATE TABLE IF NOT EXISTS groups (
68 name TEXT PRIMARY KEY,
69 description TEXT
70 );
71
72 CREATE TABLE IF NOT EXISTS owners (
73 username TEXT,
74 group_name TEXT,
75 FOREIGN KEY(group_name) REFERENCES groups(name)
76 );
77
78 CREATE TABLE IF NOT EXISTS group_to_group (
79 parent_group TEXT,
80 child_group TEXT,
81 FOREIGN KEY(parent_group) REFERENCES groups(name),
82 FOREIGN KEY(child_group) REFERENCES groups(name)
83 );
84
85 CREATE TABLE IF NOT EXISTS user_to_group (
86 username TEXT,
87 group_name TEXT,
88 FOREIGN KEY(group_name) REFERENCES groups(name)
89 );`)
90 if err != nil {
91 return nil, err
92 }
93 return &SQLiteStore{db: db}, nil
94}
95
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +040096func (s *SQLiteStore) Init(owner string, groups []string) error {
97 tx, err := s.db.Begin()
98 if err != nil {
99 return err
100 }
101 defer tx.Rollback()
102 row := tx.QueryRow("SELECT COUNT(*) FROM groups")
103 var count int
104 if err := row.Scan(&count); err != nil {
105 return err
106 }
107 if count != 0 {
108 return fmt.Errorf("store already initialised")
109 }
110 for _, g := range groups {
111 query := `INSERT INTO groups (name, description) VALUES (?, '')`
112 if _, err := tx.Exec(query, g); err != nil {
113 return err
114 }
115 query = `INSERT INTO owners (username, group_name) VALUES (?, ?)`
116 if _, err := tx.Exec(query, owner, g); err != nil {
117 return err
118 }
119 }
120 return tx.Commit()
121}
122
DTabidze0d802592024-03-19 17:42:45 +0400123func (s *SQLiteStore) queryGroups(query string, args ...interface{}) ([]Group, error) {
124 groups := make([]Group, 0)
125 rows, err := s.db.Query(query, args...)
126 if err != nil {
127 return nil, err
128 }
129 defer rows.Close()
130 for rows.Next() {
131 var group Group
132 if err := rows.Scan(&group.Name, &group.Description); err != nil {
133 return nil, err
134 }
135 groups = append(groups, group)
136 }
137 if err := rows.Err(); err != nil {
138 return nil, err
139 }
140 return groups, nil
141}
142
143func (s *SQLiteStore) GetGroupsOwnedBy(user string) ([]Group, error) {
144 query := `
145 SELECT groups.name, groups.description
146 FROM groups
147 JOIN owners ON groups.name = owners.group_name
148 WHERE owners.username = ?`
149 return s.queryGroups(query, user)
150}
151
DTabidzed7744a62024-03-20 14:09:15 +0400152func (s *SQLiteStore) GetGroupsUserBelongsTo(user string) ([]Group, error) {
DTabidze0d802592024-03-19 17:42:45 +0400153 query := `
154 SELECT groups.name, groups.description
155 FROM groups
156 JOIN user_to_group ON groups.name = user_to_group.group_name
157 WHERE user_to_group.username = ?`
158 return s.queryGroups(query, user)
159}
160
161func (s *SQLiteStore) CreateGroup(owner string, group Group) error {
162 tx, err := s.db.Begin()
163 if err != nil {
164 return err
165 }
166 defer tx.Rollback()
167 query := `INSERT INTO groups (name, description) VALUES (?, ?)`
168 if _, err := tx.Exec(query, group.Name, group.Description); err != nil {
169 sqliteErr, ok := err.(*sqlite3.Error)
170 if ok && sqliteErr.ExtendedCode() == 1555 {
171 return fmt.Errorf("Group with the name %s already exists", group.Name)
172 }
173 return err
174 }
175 query = `INSERT INTO owners (username, group_name) VALUES (?, ?)`
176 if _, err := tx.Exec(query, owner, group.Name); err != nil {
177 return err
178 }
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400179 return tx.Commit()
DTabidze0d802592024-03-19 17:42:45 +0400180}
181
182func (s *SQLiteStore) IsGroupOwner(user, group string) (bool, error) {
183 query := `
184 SELECT EXISTS (
185 SELECT 1
186 FROM owners
187 WHERE username = ? AND group_name = ?
188 )`
189 var exists bool
190 if err := s.db.QueryRow(query, user, group).Scan(&exists); err != nil {
191 return false, err
192 }
193 return exists, nil
194}
195
196func (s *SQLiteStore) userGroupPairExists(tx *sql.Tx, table, user, group string) (bool, error) {
197 query := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM %s WHERE username = ? AND group_name = ?)", table)
198 var exists bool
199 if err := tx.QueryRow(query, user, group).Scan(&exists); err != nil {
200 return false, err
201 }
202 return exists, nil
203}
204
205func (s *SQLiteStore) AddGroupMember(user, group string) error {
206 tx, err := s.db.Begin()
207 if err != nil {
208 return err
209 }
210 defer tx.Rollback()
211 existsInUserToGroup, err := s.userGroupPairExists(tx, "user_to_group", user, group)
212 if err != nil {
213 return err
214 }
215 if existsInUserToGroup {
216 return fmt.Errorf("%s is already a member of group %s", user, group)
217 }
218 if _, err := tx.Exec(`INSERT INTO user_to_group (username, group_name) VALUES (?, ?)`, user, group); err != nil {
219 return err
220 }
221 if err := tx.Commit(); err != nil {
222 return err
223 }
224 return nil
225}
226
227func (s *SQLiteStore) AddGroupOwner(user, group string) error {
228 tx, err := s.db.Begin()
229 if err != nil {
230 return err
231 }
232 defer tx.Rollback()
233 existsInOwners, err := s.userGroupPairExists(tx, "owners", user, group)
234 if err != nil {
235 return err
236 }
237 if existsInOwners {
238 return fmt.Errorf("%s is already an owner of group %s", user, group)
239 }
240 if _, err = tx.Exec(`INSERT INTO owners (username, group_name) VALUES (?, ?)`, user, group); err != nil {
241 return err
242 }
243 if err := tx.Commit(); err != nil {
244 return err
245 }
246 return nil
247}
248
249func (s *SQLiteStore) getUsersByGroup(table, group string) ([]string, error) {
250 query := fmt.Sprintf("SELECT username FROM %s WHERE group_name = ?", table)
251 rows, err := s.db.Query(query, group)
252 if err != nil {
253 return nil, err
254 }
255 defer rows.Close()
256 var users []string
257 for rows.Next() {
258 var username string
259 if err := rows.Scan(&username); err != nil {
260 return nil, err
261 }
262 users = append(users, username)
263 }
264 if err := rows.Err(); err != nil {
265 return nil, err
266 }
267 return users, nil
268}
269
270func (s *SQLiteStore) GetGroupOwners(group string) ([]string, error) {
271 return s.getUsersByGroup("owners", group)
272}
273
274func (s *SQLiteStore) GetGroupMembers(group string) ([]string, error) {
275 return s.getUsersByGroup("user_to_group", group)
276}
277
278func (s *SQLiteStore) GetGroupDescription(group string) (string, error) {
279 var description string
280 query := `SELECT description FROM groups WHERE name = ?`
281 if err := s.db.QueryRow(query, group).Scan(&description); err != nil {
282 return "", err
283 }
284 return description, nil
285}
286
287func (s *SQLiteStore) parentChildGroupPairExists(tx *sql.Tx, parent, child string) (bool, error) {
288 query := `SELECT EXISTS (SELECT 1 FROM group_to_group WHERE parent_group = ? AND child_group = ?)`
289 var exists bool
290 if err := tx.QueryRow(query, parent, child).Scan(&exists); err != nil {
291 return false, err
292 }
293 return exists, nil
294}
295
296func (s *SQLiteStore) AddChildGroup(parent, child string) error {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400297 parentGroups, err := s.GetAllTransitiveGroupsForGroup(parent)
298 if err != nil {
299 return err
300 }
301 for _, group := range parentGroups {
302 if group.Name == child {
303 return fmt.Errorf("circular reference detected: group %s is already a parent of group %s", child, parent)
304 }
305 }
DTabidze0d802592024-03-19 17:42:45 +0400306 tx, err := s.db.Begin()
307 if err != nil {
308 return err
309 }
310 defer tx.Rollback()
311 existsInGroupToGroup, err := s.parentChildGroupPairExists(tx, parent, child)
312 if err != nil {
313 return err
314 }
315 if existsInGroupToGroup {
316 return fmt.Errorf("child group name %s already exists in group %s", child, parent)
317 }
318 if _, err := tx.Exec(`INSERT INTO group_to_group (parent_group, child_group) VALUES (?, ?)`, parent, child); err != nil {
319 return err
320 }
321 if err := tx.Commit(); err != nil {
322 return err
323 }
324 return nil
325}
326
327func (s *SQLiteStore) GetAvailableGroupsAsChild(group string) ([]string, error) {
328 // TODO(dtabidze): Might have to add further logic to filter available groups as children.
329 query := `
330 SELECT name FROM groups
331 WHERE name != ? AND name NOT IN (
332 SELECT child_group FROM group_to_group WHERE parent_group = ?
333 )
334 `
335 rows, err := s.db.Query(query, group, group)
336 if err != nil {
337 return nil, err
338 }
339 defer rows.Close()
340 var availableGroups []string
341 for rows.Next() {
342 var groupName string
343 if err := rows.Scan(&groupName); err != nil {
344 return nil, err
345 }
346 availableGroups = append(availableGroups, groupName)
347 }
348 return availableGroups, nil
349}
350
DTabidzec0b4d8f2024-03-22 17:25:10 +0400351func (s *SQLiteStore) GetAllTransitiveGroupsForUser(user string) ([]Group, error) {
352 if groups, err := s.GetGroupsUserBelongsTo(user); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400353 return nil, err
DTabidzec0b4d8f2024-03-22 17:25:10 +0400354 } else {
355 visited := map[string]struct{}{}
356 return s.getAllParentGroupsRecursive(groups, visited)
DTabidzed7744a62024-03-20 14:09:15 +0400357 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400358}
359
360func (s *SQLiteStore) GetAllTransitiveGroupsForGroup(group string) ([]Group, error) {
361 if p, err := s.GetGroupsGroupBelongsTo(group); err != nil {
362 return nil, err
363 } else {
364 // Mark initial group as visited
365 visited := map[string]struct{}{
366 group: struct{}{},
367 }
368 return s.getAllParentGroupsRecursive(p, visited)
369 }
370}
371
372func (s *SQLiteStore) getAllParentGroupsRecursive(groups []Group, visited map[string]struct{}) ([]Group, error) {
373 var ret []Group
374 for _, g := range groups {
375 if _, ok := visited[g.Name]; ok {
376 continue
377 }
378 visited[g.Name] = struct{}{}
379 ret = append(ret, g)
380 if p, err := s.GetGroupsGroupBelongsTo(g.Name); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400381 return nil, err
DTabidzec0b4d8f2024-03-22 17:25:10 +0400382 } else if res, err := s.getAllParentGroupsRecursive(p, visited); err != nil {
383 return nil, err
384 } else {
385 ret = append(ret, res...)
DTabidzed7744a62024-03-20 14:09:15 +0400386 }
387 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400388 return ret, nil
DTabidzed7744a62024-03-20 14:09:15 +0400389}
390
DTabidzec0b4d8f2024-03-22 17:25:10 +0400391func (s *SQLiteStore) GetGroupsGroupBelongsTo(group string) ([]Group, error) {
392 query := `
393 SELECT groups.name, groups.description
394 FROM groups
395 JOIN group_to_group ON groups.name = group_to_group.parent_group
396 WHERE group_to_group.child_group = ?`
DTabidzed7744a62024-03-20 14:09:15 +0400397 rows, err := s.db.Query(query, group)
398 if err != nil {
399 return nil, err
400 }
401 defer rows.Close()
DTabidzec0b4d8f2024-03-22 17:25:10 +0400402 var parentGroups []Group
DTabidzed7744a62024-03-20 14:09:15 +0400403 for rows.Next() {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400404 var parentGroup Group
405 if err := rows.Scan(&parentGroup.Name, &parentGroup.Description); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400406 return nil, err
407 }
408 parentGroups = append(parentGroups, parentGroup)
409 }
410 if err := rows.Err(); err != nil {
411 return nil, err
412 }
413 return parentGroups, nil
414}
415
DTabidzec0b4d8f2024-03-22 17:25:10 +0400416func (s *SQLiteStore) GetDirectChildrenGroups(group string) ([]Group, error) {
417 query := `
418 SELECT groups.name, groups.description
419 FROM groups
420 JOIN group_to_group ON groups.name = group_to_group.child_group
421 WHERE group_to_group.parent_group = ?`
422 rows, err := s.db.Query(query, group)
423 if err != nil {
424 return nil, err
425 }
426 defer rows.Close()
427 var childrenGroups []Group
428 for rows.Next() {
429 var childGroup Group
430 if err := rows.Scan(&childGroup.Name, &childGroup.Description); err != nil {
431 return nil, err
432 }
433 childrenGroups = append(childrenGroups, childGroup)
434 }
435 if err := rows.Err(); err != nil {
436 return nil, err
437 }
438 return childrenGroups, nil
439}
440
DTabidze0d802592024-03-19 17:42:45 +0400441func getLoggedInUser(r *http.Request) (string, error) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400442 if user := r.Header.Get("X-User"); user != "" {
443 return user, nil
444 } else {
445 return "", fmt.Errorf("unauthenticated")
446 }
DTabidze0d802592024-03-19 17:42:45 +0400447}
448
449type Status int
450
451const (
452 Owner Status = iota
453 Member
454)
455
456func convertStatus(status string) (Status, error) {
457 switch status {
458 case "Owner":
459 return Owner, nil
460 case "Member":
461 return Member, nil
462 default:
463 return Owner, fmt.Errorf("invalid status: %s", status)
464 }
465}
466
467func (s *Server) Start() {
DTabidzed7744a62024-03-20 14:09:15 +0400468 router := mux.NewRouter()
469 router.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticResources)))
470 router.HandleFunc("/group/{group-name}", s.groupHandler)
471 router.HandleFunc("/create-group", s.createGroupHandler)
472 router.HandleFunc("/add-user", s.addUserHandler)
473 router.HandleFunc("/add-child-group", s.addChildGroupHandler)
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400474 router.HandleFunc("/api/init", s.apiInitHandler)
DTabidzed7744a62024-03-20 14:09:15 +0400475 router.HandleFunc("/api/user/{username}", s.apiMemberOfHandler)
476 router.HandleFunc("/", s.homePageHandler)
477 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), router))
DTabidze0d802592024-03-19 17:42:45 +0400478}
479
480type GroupData struct {
481 Group Group
482 Membership string
483}
484
485func (s *Server) checkIsOwner(w http.ResponseWriter, user, group string) (bool, error) {
486 isOwner, err := s.store.IsGroupOwner(user, group)
487 if err != nil {
488 http.Error(w, err.Error(), http.StatusInternalServerError)
489 return false, err
490 }
491 if !isOwner {
492 http.Error(w, fmt.Sprintf("You are not the owner of the group %s", group), http.StatusUnauthorized)
493 return false, nil
494 }
495 return true, nil
496}
497
498func (s *Server) homePageHandler(w http.ResponseWriter, r *http.Request) {
499 loggedInUser, err := getLoggedInUser(r)
500 if err != nil {
501 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
502 return
503 }
504 ownerGroups, err := s.store.GetGroupsOwnedBy(loggedInUser)
505 if err != nil {
506 http.Error(w, err.Error(), http.StatusInternalServerError)
507 return
508 }
DTabidzed7744a62024-03-20 14:09:15 +0400509 membershipGroups, err := s.store.GetGroupsUserBelongsTo(loggedInUser)
DTabidze0d802592024-03-19 17:42:45 +0400510 if err != nil {
511 http.Error(w, err.Error(), http.StatusInternalServerError)
512 return
513 }
514 tmpl, err := template.New("index").Parse(indexHTML)
515 if err != nil {
516 http.Error(w, err.Error(), http.StatusInternalServerError)
517 return
518 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400519 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(loggedInUser)
520 if err != nil {
521 http.Error(w, err.Error(), http.StatusInternalServerError)
522 return
523 }
DTabidze0d802592024-03-19 17:42:45 +0400524 data := struct {
525 OwnerGroups []Group
526 MembershipGroups []Group
DTabidzec0b4d8f2024-03-22 17:25:10 +0400527 TransitiveGroups []Group
DTabidze0d802592024-03-19 17:42:45 +0400528 }{
529 OwnerGroups: ownerGroups,
530 MembershipGroups: membershipGroups,
DTabidzec0b4d8f2024-03-22 17:25:10 +0400531 TransitiveGroups: transitiveGroups,
DTabidze0d802592024-03-19 17:42:45 +0400532 }
533 w.Header().Set("Content-Type", "text/html")
534 if err := tmpl.Execute(w, data); err != nil {
535 http.Error(w, err.Error(), http.StatusInternalServerError)
536 return
537 }
538}
539
540func (s *Server) createGroupHandler(w http.ResponseWriter, r *http.Request) {
541 loggedInUser, err := getLoggedInUser(r)
542 if err != nil {
543 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
544 return
545 }
546 if r.Method != http.MethodPost {
547 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
548 return
549 }
550 if err := r.ParseForm(); err != nil {
551 http.Error(w, err.Error(), http.StatusInternalServerError)
552 return
553 }
554 var group Group
555 group.Name = r.PostFormValue("group-name")
556 group.Description = r.PostFormValue("description")
557 if err := s.store.CreateGroup(loggedInUser, group); err != nil {
558 http.Error(w, err.Error(), http.StatusInternalServerError)
559 return
560 }
561 http.Redirect(w, r, "/", http.StatusSeeOther)
562}
563
564func (s *Server) groupHandler(w http.ResponseWriter, r *http.Request) {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400565 _, err := getLoggedInUser(r)
566 if err != nil {
567 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
568 return
569 }
DTabidzed7744a62024-03-20 14:09:15 +0400570 vars := mux.Vars(r)
571 groupName := vars["group-name"]
DTabidze0d802592024-03-19 17:42:45 +0400572 tmpl, err := template.New("group").Parse(groupHTML)
573 if err != nil {
574 http.Error(w, err.Error(), http.StatusInternalServerError)
575 return
576 }
577 owners, err := s.store.GetGroupOwners(groupName)
578 if err != nil {
579 http.Error(w, err.Error(), http.StatusInternalServerError)
580 return
581 }
582 members, err := s.store.GetGroupMembers(groupName)
583 if err != nil {
584 http.Error(w, err.Error(), http.StatusInternalServerError)
585 return
586 }
587 description, err := s.store.GetGroupDescription(groupName)
588 if err != nil {
589 http.Error(w, err.Error(), http.StatusInternalServerError)
590 return
591 }
592 availableGroups, err := s.store.GetAvailableGroupsAsChild(groupName)
593 if err != nil {
594 http.Error(w, err.Error(), http.StatusInternalServerError)
595 return
596 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400597 transitiveGroups, err := s.store.GetAllTransitiveGroupsForGroup(groupName)
598 if err != nil {
599 http.Error(w, err.Error(), http.StatusInternalServerError)
600 return
601 }
602 childGroups, err := s.store.GetDirectChildrenGroups(groupName)
603 if err != nil {
604 http.Error(w, err.Error(), http.StatusInternalServerError)
605 return
606 }
DTabidze0d802592024-03-19 17:42:45 +0400607 data := struct {
DTabidzec0b4d8f2024-03-22 17:25:10 +0400608 GroupName string
609 Description string
610 Owners []string
611 Members []string
612 AvailableGroups []string
613 TransitiveGroups []Group
614 ChildGroups []Group
DTabidze0d802592024-03-19 17:42:45 +0400615 }{
DTabidzec0b4d8f2024-03-22 17:25:10 +0400616 GroupName: groupName,
617 Description: description,
618 Owners: owners,
619 Members: members,
620 AvailableGroups: availableGroups,
621 TransitiveGroups: transitiveGroups,
622 ChildGroups: childGroups,
DTabidze0d802592024-03-19 17:42:45 +0400623 }
624 if err := tmpl.Execute(w, data); err != nil {
625 http.Error(w, err.Error(), http.StatusInternalServerError)
626 return
627 }
628}
629
630func (s *Server) addUserHandler(w http.ResponseWriter, r *http.Request) {
631 if r.Method != http.MethodPost {
632 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
633 return
634 }
635 loggedInUser, err := getLoggedInUser(r)
636 if err != nil {
637 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
638 return
639 }
640 groupName := r.FormValue("group")
641 username := r.FormValue("username")
642 status, err := convertStatus(r.FormValue("status"))
643 if err != nil {
644 http.Error(w, err.Error(), http.StatusBadRequest)
645 return
646 }
647 if _, err := s.checkIsOwner(w, loggedInUser, groupName); err != nil {
648 return
649 }
650 switch status {
651 case Owner:
652 err = s.store.AddGroupOwner(username, groupName)
653 case Member:
654 err = s.store.AddGroupMember(username, groupName)
655 default:
656 http.Error(w, "Invalid status", http.StatusBadRequest)
657 return
658 }
659 if err != nil {
660 http.Error(w, err.Error(), http.StatusInternalServerError)
661 return
662 }
663 http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
664}
665
666func (s *Server) addChildGroupHandler(w http.ResponseWriter, r *http.Request) {
667 // TODO(dtabidze): In future we might need to make one group OWNER of another and not just a member.
668 if r.Method != http.MethodPost {
669 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
670 return
671 }
672 loggedInUser, err := getLoggedInUser(r)
673 if err != nil {
674 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
675 return
676 }
677 parentGroup := r.FormValue("parent-group")
678 childGroup := r.FormValue("child-group")
679 if _, err := s.checkIsOwner(w, loggedInUser, parentGroup); err != nil {
680 return
681 }
682 if err := s.store.AddChildGroup(parentGroup, childGroup); err != nil {
683 http.Error(w, err.Error(), http.StatusInternalServerError)
684 return
685 }
686 http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)
687}
688
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400689type initRequest struct {
690 Owner string `json:"owner"`
691 Groups []string `json:"groups"`
692}
693
694func (s *Server) apiInitHandler(w http.ResponseWriter, r *http.Request) {
695 var req initRequest
696 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
697 http.Error(w, err.Error(), http.StatusBadRequest)
698 return
699 }
700 if err := s.store.Init(req.Owner, req.Groups); err != nil {
701 http.Error(w, err.Error(), http.StatusInternalServerError)
702 return
703 }
704}
705
706type userInfo struct {
DTabidzed7744a62024-03-20 14:09:15 +0400707 MemberOf []string `json:"memberOf"`
708}
709
710func (s *Server) apiMemberOfHandler(w http.ResponseWriter, r *http.Request) {
711 vars := mux.Vars(r)
712 user, ok := vars["username"]
713 if !ok {
714 http.Error(w, "Username parameter is required", http.StatusBadRequest)
715 return
716 }
717 transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(user)
718 if err != nil {
719 http.Error(w, err.Error(), http.StatusInternalServerError)
720 return
721 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400722 var groupNames []string
723 for _, group := range transitiveGroups {
724 groupNames = append(groupNames, group.Name)
725 }
DTabidzed7744a62024-03-20 14:09:15 +0400726 w.Header().Set("Content-Type", "application/json")
Giorgi Lekveishvili942c7612024-03-22 19:27:48 +0400727 if err := json.NewEncoder(w).Encode(userInfo{groupNames}); err != nil {
DTabidzed7744a62024-03-20 14:09:15 +0400728 http.Error(w, err.Error(), http.StatusInternalServerError)
729 return
730 }
731}
732
DTabidze0d802592024-03-19 17:42:45 +0400733func main() {
734 flag.Parse()
DTabidzec0b4d8f2024-03-22 17:25:10 +0400735 db, err := sql.Open("sqlite3", *dbPath)
DTabidze0d802592024-03-19 17:42:45 +0400736 if err != nil {
737 panic(err)
738 }
DTabidzec0b4d8f2024-03-22 17:25:10 +0400739 store, err := NewSQLiteStore(db)
740 if err != nil {
741 panic(err)
742 }
743 s := Server{store}
DTabidze0d802592024-03-19 17:42:45 +0400744 s.Start()
745}