From 12eaddc8ee0b1de97f5001201e083753fcaac8e0 Mon Sep 17 00:00:00 2001 From: konrad Date: Thu, 18 Jul 2019 16:38:21 +0000 Subject: [PATCH] Added http endpoint to list all users on a list (#87) --- Featurecreep.md | 4 +- REST-Tests/auth.http | 2 +- REST-Tests/lists.http | 12 +- pkg/models/fixtures/list.yml | 18 +++ pkg/models/fixtures/namespaces.yml | 12 ++ pkg/models/fixtures/team_list.yml | 24 +++- pkg/models/fixtures/team_members.yml | 24 ++++ pkg/models/fixtures/team_namespaces.yml | 18 +++ pkg/models/fixtures/teams.yml | 20 ++- pkg/models/fixtures/users.yml | 49 ++++++++ pkg/models/fixtures/users_list.yml | 22 +++- pkg/models/fixtures/users_namespace.yml | 18 +++ pkg/models/list_read_test.go | 2 +- pkg/models/teams_test.go | 2 +- pkg/models/users_list.go | 88 +++++++++++++ pkg/models/users_list_test.go | 159 ++++++++++++++++++++++++ pkg/routes/api/v1/user_list.go | 43 +++++++ pkg/routes/routes.go | 1 + pkg/swagger/docs.go | 95 +++++++++++--- pkg/swagger/swagger.json | 93 ++++++++++++-- pkg/swagger/swagger.yaml | 72 +++++++++-- 21 files changed, 722 insertions(+), 56 deletions(-) create mode 100644 pkg/models/users_list_test.go diff --git a/Featurecreep.md b/Featurecreep.md index 4e1f65de18..e4d6480610 100644 --- a/Featurecreep.md +++ b/Featurecreep.md @@ -55,7 +55,7 @@ Sorry for some of them being in German, I'll tranlate them at some point. * [x] Wir brauchen noch ne gute idee, wie man die listen kriegt, auf die man nur so Zugriff hat (ohne namespace) * Dazu am Besten nen pseudonamespace anlegen (id -1 oder so), der hat das dann alles * [x] Testing mit locust: https://locust.io/ -* [ ] Endpoint to get all users who have access to a list - regardless of via team, user share or via namespace +* [x] Endpoint to get all users who have access to a list - regardless of via team, user share or via namespace #### Userstuff @@ -188,8 +188,8 @@ Sorry for some of them being in German, I'll tranlate them at some point. * [x] Adding users to a team should also use uuid * [x] Check if the team/user really exist before updating them on lists/namespaces * [x] Refactor config handling: Custom type "key" or so which holds the viper const and then mixins on that type to get the values from viper +* [x] Less files, but with some kind of logic * [ ] Have extra functions for logging to call so it is possible to call `log.Info` instead of `log.Log.Info` -* [ ] Less files, but with some kind of logic ### Linters diff --git a/REST-Tests/auth.http b/REST-Tests/auth.http index 61695db7aa..696b19678b 100644 --- a/REST-Tests/auth.http +++ b/REST-Tests/auth.http @@ -3,7 +3,7 @@ POST http://localhost:8080/api/v1/login Content-Type: application/json { - "username": "user6", + "username": "user3", "password": "1234" } diff --git a/REST-Tests/lists.http b/REST-Tests/lists.http index fc6c0dab88..a7e716be92 100644 --- a/REST-Tests/lists.http +++ b/REST-Tests/lists.http @@ -5,7 +5,7 @@ Authorization: Bearer {{auth_token}} ### # Get one list -GET http://localhost:8080/api/v1/lists/1172 +GET http://localhost:8080/api/v1/lists/3 Authorization: Bearer {{auth_token}} ### @@ -82,11 +82,11 @@ Authorization: Bearer {{auth_token}} ### # Give a user access to that list -PUT http://localhost:8080/api/v1/lists/1172/users +PUT http://localhost:8080/api/v1/lists/3/users Authorization: Bearer {{auth_token}} Content-Type: application/json -{"user_id":1, "right":1} +{"userID":"user4", "right":1} ### @@ -169,3 +169,9 @@ Content-Type: application/json } ### + +# Get all users who have access to a list +GET http://localhost:8080/api/v1/lists/3/users +Authorization: Bearer {{auth_token}} + +### diff --git a/pkg/models/fixtures/list.yml b/pkg/models/fixtures/list.yml index 7c08b79291..dd04a2912d 100644 --- a/pkg/models/fixtures/list.yml +++ b/pkg/models/fixtures/list.yml @@ -134,3 +134,21 @@ namespace_id: 12 updated: 0 created: 0 +# This list is owned by user 7, and several other users have access to it via different methods. +# It is used to test the listUsers method. +- + id: 18 + title: Test18 + description: Lorem Ipsum + owner_id: 7 + namespace_id: 13 + updated: 0 + created: 0 +- + id: 19 + title: Test19 + description: Lorem Ipsum + owner_id: 7 + namespace_id: 14 + updated: 0 + created: 0 diff --git a/pkg/models/fixtures/namespaces.yml b/pkg/models/fixtures/namespaces.yml index a746a84159..c30d8a3091 100644 --- a/pkg/models/fixtures/namespaces.yml +++ b/pkg/models/fixtures/namespaces.yml @@ -58,3 +58,15 @@ owner_id: 6 updated: 0 created: 0 +- id: 13 + name: testnamespace13 + description: Lorem Ipsum + owner_id: 7 + updated: 0 + created: 0 +- id: 14 + name: testnamespace14 + description: Lorem Ipsum + owner_id: 7 + updated: 0 + created: 0 diff --git a/pkg/models/fixtures/team_list.yml b/pkg/models/fixtures/team_list.yml index 6852db9fd6..47193f714e 100644 --- a/pkg/models/fixtures/team_list.yml +++ b/pkg/models/fixtures/team_list.yml @@ -4,7 +4,6 @@ right: 0 updated: 0 created: 0 - # This team has read only access on list 6 - id: 2 team_id: 2 @@ -12,7 +11,6 @@ right: 0 updated: 0 created: 0 - # This team has write access on list 7 - id: 3 team_id: 3 @@ -20,11 +18,31 @@ right: 1 updated: 0 created: 0 - # This team has admin access on list 8 - id: 4 team_id: 4 list_id: 8 right: 2 updated: 0 + created: 0 +# Readonly acces on list 19 +- id: 5 + team_id: 8 + list_id: 19 + right: 2 + updated: 0 + created: 0 +# Write acces on list 19 +- id: 6 + team_id: 9 + list_id: 19 + right: 1 + updated: 0 + created: 0 +# Admin acces on list 19 +- id: 7 + team_id: 10 + list_id: 19 + right: 2 + updated: 0 created: 0 \ No newline at end of file diff --git a/pkg/models/fixtures/team_members.yml b/pkg/models/fixtures/team_members.yml index bbcecb19c0..f91e871a8a 100644 --- a/pkg/models/fixtures/team_members.yml +++ b/pkg/models/fixtures/team_members.yml @@ -31,3 +31,27 @@ team_id: 7 user_id: 1 created: 0 +- + team_id: 8 + user_id: 1 + created: 0 +- + team_id: 9 + user_id: 2 + created: 0 +- + team_id: 10 + user_id: 3 + created: 0 +- + team_id: 11 + user_id: 8 + created: 0 +- + team_id: 12 + user_id: 9 + created: 0 +- + team_id: 13 + user_id: 10 + created: 0 diff --git a/pkg/models/fixtures/team_namespaces.yml b/pkg/models/fixtures/team_namespaces.yml index 4b206b9b88..fbb4de52ac 100644 --- a/pkg/models/fixtures/team_namespaces.yml +++ b/pkg/models/fixtures/team_namespaces.yml @@ -32,3 +32,21 @@ right: 2 updated: 0 created: 0 +- id: 6 + team_id: 11 + namespace_id: 14 + right: 0 + updated: 0 + created: 0 +- id: 7 + team_id: 12 + namespace_id: 14 + right: 1 + updated: 0 + created: 0 +- id: 8 + team_id: 13 + namespace_id: 14 + right: 2 + updated: 0 + created: 0 diff --git a/pkg/models/fixtures/teams.yml b/pkg/models/fixtures/teams.yml index 3852100b80..a6c8649f7e 100644 --- a/pkg/models/fixtures/teams.yml +++ b/pkg/models/fixtures/teams.yml @@ -19,4 +19,22 @@ created_by_id: 1 - id: 7 name: testteam4_admin_on_namespace9 - created_by_id: 1 \ No newline at end of file + created_by_id: 1 +- id: 8 + name: testteam8 + created_by_id: 7 +- id: 9 + name: testteam9 + created_by_id: 7 +- id: 10 + name: testteam10 + created_by_id: 7 +- id: 11 + name: testteam11 + created_by_id: 7 +- id: 12 + name: testteam12 + created_by_id: 7 +- id: 13 + name: testteam13 + created_by_id: 7 \ No newline at end of file diff --git a/pkg/models/fixtures/users.yml b/pkg/models/fixtures/users.yml index 22bba86146..0df0889614 100644 --- a/pkg/models/fixtures/users.yml +++ b/pkg/models/fixtures/users.yml @@ -46,3 +46,52 @@ is_active: true updated: 0 created: 0 +- id: 7 + username: 'user7' + password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 + email: 'user7@example.com' + is_active: true + updated: 0 + created: 0 +- id: 8 + username: 'user8' + password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 + email: 'user8@example.com' + is_active: true + updated: 0 + created: 0 +- id: 9 + username: 'user9' + password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 + email: 'user9@example.com' + is_active: true + updated: 0 + created: 0 +- id: 10 + username: 'user10' + password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 + email: 'user10@example.com' + is_active: true + updated: 0 + created: 0 +- id: 11 + username: 'user11' + password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 + email: 'user11@example.com' + is_active: true + updated: 0 + created: 0 +- id: 12 + username: 'user12' + password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 + email: 'user12@example.com' + is_active: true + updated: 0 + created: 0 +- id: 13 + username: 'user13' + password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 + email: 'user14@example.com' + is_active: true + updated: 0 + created: 0 diff --git a/pkg/models/fixtures/users_list.yml b/pkg/models/fixtures/users_list.yml index 4464e6e5b4..e7676105cb 100644 --- a/pkg/models/fixtures/users_list.yml +++ b/pkg/models/fixtures/users_list.yml @@ -4,31 +4,45 @@ right: 0 updated: 0 created: 0 - - id: 2 user_id: 2 list_id: 3 right: 0 updated: 0 created: 0 - - id: 3 user_id: 1 list_id: 9 right: 0 updated: 0 created: 0 - - id: 4 user_id: 1 list_id: 10 right: 1 updated: 0 created: 0 - - id: 5 user_id: 1 list_id: 11 right: 2 updated: 0 created: 0 +- id: 6 + user_id: 4 + list_id: 19 + right: 0 + updated: 0 + created: 0 +- id: 7 + user_id: 5 + list_id: 19 + right: 1 + updated: 0 + created: 0 +- id: 8 + user_id: 6 + list_id: 19 + right: 2 + updated: 0 + created: 0 diff --git a/pkg/models/fixtures/users_namespace.yml b/pkg/models/fixtures/users_namespace.yml index e12681ddb9..ebd44e4804 100644 --- a/pkg/models/fixtures/users_namespace.yml +++ b/pkg/models/fixtures/users_namespace.yml @@ -32,3 +32,21 @@ right: 2 updated: 0 created: 0 +- id: 6 + user_id: 11 + namespace_id: 14 + right: 0 + updated: 0 + created: 0 +- id: 7 + user_id: 12 + namespace_id: 14 + right: 1 + updated: 0 + created: 0 +- id: 8 + user_id: 13 + namespace_id: 14 + right: 2 + updated: 0 + created: 0 diff --git a/pkg/models/list_read_test.go b/pkg/models/list_read_test.go index c504e1a222..a61c1f17d7 100644 --- a/pkg/models/list_read_test.go +++ b/pkg/models/list_read_test.go @@ -40,7 +40,7 @@ func TestList_ReadAll(t *testing.T) { assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) s := reflect.ValueOf(lists3) - assert.Equal(t, 15, s.Len()) + assert.Equal(t, 16, s.Len()) // Try getting lists for a nonexistant user _, err = lists2.ReadAll("", &User{ID: 984234}, 1) diff --git a/pkg/models/teams_test.go b/pkg/models/teams_test.go index c02096d17b..5ef6061bae 100644 --- a/pkg/models/teams_test.go +++ b/pkg/models/teams_test.go @@ -59,7 +59,7 @@ func TestTeam_Create(t *testing.T) { assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice) s := reflect.ValueOf(ts) - assert.Equal(t, 8, s.Len()) + assert.Equal(t, 9, s.Len()) // Check inserting it with an empty name dummyteam.Name = "" diff --git a/pkg/models/users_list.go b/pkg/models/users_list.go index c7cfd68098..db13ff3965 100644 --- a/pkg/models/users_list.go +++ b/pkg/models/users_list.go @@ -16,6 +16,8 @@ package models +import "github.com/go-xorm/builder" + // ListUsers returns a list with all users, filtered by an optional searchstring func ListUsers(searchterm string) (users []User, err error) { @@ -33,3 +35,89 @@ func ListUsers(searchterm string) (users []User, err error) { return users, nil } + +// ListUIDs hold all kinds of user IDs from accounts who have somehow access to a list +type ListUIDs struct { + ListOwnerID int64 `xorm:"listOwner"` + NamespaceUserID int64 `xorm:"unID"` + ListUserID int64 `xorm:"ulID"` + NamespaceOwnerUserID int64 `xorm:"nOwner"` + TeamNamespaceUserID int64 `xorm:"tnUID"` + TeamListUserID int64 `xorm:"tlUID"` +} + +// ListUsersFromList returns a list with all users who have access to a list, regardless of the method which gave them access +func ListUsersFromList(l *List, search string) (users []*User, err error) { + + userids := []*ListUIDs{} + + err = x. + Select(`l.owner_id as listOwner, + un.user_id as unID, + ul.user_id as ulID, + n.owner_id as nOwner, + tm.user_id as tnUID, + tm2.user_id as tlUID`). + Table("list"). + Alias("l"). + // User stuff + Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id"). + Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). + Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id"). + // Team stuff + Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id"). + Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id"). + Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id"). + Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). + // The actual condition + Where( + builder.Or( + builder.Or(builder.Eq{"ul.right": RightRead}), + builder.Or(builder.Eq{"un.right": RightRead}), + builder.Or(builder.Eq{"tl.right": RightRead}), + builder.Or(builder.Eq{"tn.right": RightRead}), + + builder.Or(builder.Eq{"ul.right": RightWrite}), + builder.Or(builder.Eq{"un.right": RightWrite}), + builder.Or(builder.Eq{"tl.right": RightWrite}), + builder.Or(builder.Eq{"tn.right": RightWrite}), + + builder.Or(builder.Eq{"ul.right": RightAdmin}), + builder.Or(builder.Eq{"un.right": RightAdmin}), + builder.Or(builder.Eq{"tl.right": RightAdmin}), + builder.Or(builder.Eq{"tn.right": RightAdmin}), + ), + builder.Eq{"l.id": l.ID}, + ). + Find(&userids) + if err != nil { + return + } + + // Remove duplicates from the list of ids and make it a slice + uidmap := make(map[int64]bool) + uidmap[l.OwnerID] = true + for _, u := range userids { + uidmap[u.ListUserID] = true + uidmap[u.NamespaceOwnerUserID] = true + uidmap[u.NamespaceUserID] = true + uidmap[u.TeamListUserID] = true + uidmap[u.TeamNamespaceUserID] = true + } + + uids := make([]int64, len(uidmap)) + for id := range uidmap { + uids = append(uids, id) + } + + // Get all users + err = x. + Table("users"). + Select("*"). + In("id", uids). + And("username LIKE ?", "%"+search+"%"). + GroupBy("id"). + OrderBy("id"). + Find(&users) + return +} diff --git a/pkg/models/users_list_test.go b/pkg/models/users_list_test.go new file mode 100644 index 0000000000..62d0962187 --- /dev/null +++ b/pkg/models/users_list_test.go @@ -0,0 +1,159 @@ +package models + +import ( + "github.com/stretchr/testify/assert" + "gopkg.in/d4l3k/messagediff.v1" + "testing" +) + +func TestListUsersFromList(t *testing.T) { + + err := LoadFixtures() + assert.NoError(t, err) + + testuser1 := &User{ + ID: 1, + Username: "user1", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", + } + testuser2 := &User{ + ID: 2, + Username: "user2", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + AvatarURL: "ab53a2911ddf9b4817ac01ddcd3d975f", + } + testuser3 := &User{ + ID: 3, + Username: "user3", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + AvatarURL: "97d6d9441ff85fdc730e02a6068d267b", + PasswordResetToken: "passwordresettesttoken", + } + testuser4 := &User{ + ID: 4, + Username: "user4", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: false, + AvatarURL: "7e65550957227bd38fe2d7fbc6fd2f7b", + EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael", + } + testuser5 := &User{ + ID: 5, + Username: "user5", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: false, + AvatarURL: "cfa35b8cd2ec278026357769582fa563", + EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael", + } + testuser6 := &User{ + ID: 6, + Username: "user6", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "3efbe51f864c6666bc27caf4c6ff90ed", + } + testuser7 := &User{ + ID: 7, + Username: "user7", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "e80a711d4de44c30054806ebbd488464", + } + testuser8 := &User{ + ID: 8, + Username: "user8", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "2b9b320416cd31020bb6844c3fadefd1", + } + testuser9 := &User{ + ID: 9, + Username: "user9", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "f784fdb21d26dd2c64f5135f35ec401f", + } + testuser10 := &User{ + ID: 10, + Username: "user10", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "fce8ff4ff56d75ad587d1bbaa5ef0563", + } + testuser11 := &User{ + ID: 11, + Username: "user11", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "ad6d67d0c4495e186010732a7d360028", + } + testuser12 := &User{ + ID: 12, + Username: "user12", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "ef1debc1364806281c42eeedfdeb943b", + } + testuser13 := &User{ + ID: 13, + Username: "user13", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "b9e3f76032af53c9ff2df52d51ada717", + } + + type args struct { + l *List + search string + } + tests := []struct { + name string + args args + wantUsers []*User + wantErr bool + }{ + { + name: "Check owner only", + args: args{l: &List{ID: 18, OwnerID: 7}}, + wantUsers: []*User{testuser7}, + }, + { + // This list has another different user shared for each possible method + name: "Check with owner and other users", + args: args{l: &List{ID: 19, OwnerID: 7}}, + wantUsers: []*User{ + testuser1, // Shared Via Team readonly + testuser2, // Shared Via Team write + testuser3, // Shared Via Team admin + + testuser4, // Shared Via User readonly + testuser5, // Shared Via User write + testuser6, // Shared Via User admin + + testuser7, // Owner + + testuser8, // Shared Via NamespaceTeam readonly + testuser9, // Shared Via NamespaceTeam write + testuser10, // Shared Via NamespaceTeam admin + + testuser11, // Shared Via NamespaceUser readonly + testuser12, // Shared Via NamespaceUser write + testuser13, // Shared Via NamespaceUser admin + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotUsers, err := ListUsersFromList(tt.args.l, tt.args.search) + if (err != nil) != tt.wantErr { + t.Errorf("ListUsersFromList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if diff, equal := messagediff.PrettyDiff(tt.wantUsers, gotUsers); !equal { + t.Errorf("Test %s, LabelTask.ReadAll() = %v, want %v, \ndiff: %v", tt.name, gotUsers, tt.wantUsers, diff) + } + }) + } +} diff --git a/pkg/routes/api/v1/user_list.go b/pkg/routes/api/v1/user_list.go index 0389d3f407..6aa1f1534d 100644 --- a/pkg/routes/api/v1/user_list.go +++ b/pkg/routes/api/v1/user_list.go @@ -21,6 +21,7 @@ import ( "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" + "strconv" ) // UserList gets all information about a user @@ -49,3 +50,45 @@ func UserList(c echo.Context) error { return c.JSON(http.StatusOK, users) } + +// ListUsersForList returns a list with all users who have access to a list, regardless of the method the list was shared with them. +// @Summary Get users +// @Description Lists all users (without emailadresses). Also possible to search for a specific user. +// @tags list +// @Accept json +// @Produce json +// @Param s query string false "Search for a user by its name." +// @Security JWTKeyAuth +// @Param id path int true "List ID" +// @Success 200 {array} models.User "All (found) users." +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Something's invalid." +// @Failure 401 {object} code.vikunja.io/web.HTTPError "The user does not have the right to see the list." +// @Failure 500 {object} models.Message "Internal server error." +// @Router /lists/{id}/listusers [get] +func ListUsersForList(c echo.Context) error { + listID, err := strconv.ParseInt(c.Param("list"), 10, 64) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + list := models.List{ID: listID} + currentUser, err := models.GetCurrentUser(c) + if err != nil { + return handler.HandleHTTPError(err, c) + } + canRead, err := list.CanRead(currentUser) + if err != nil { + return handler.HandleHTTPError(err, c) + } + if !canRead { + return echo.ErrForbidden + } + + s := c.QueryParam("s") + users, err := models.ListUsersFromList(&list, s) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + return c.JSON(http.StatusOK, users) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 953dda5866..b10618fa26 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -254,6 +254,7 @@ func registerAPIRoutes(a *echo.Group) { a.POST("/lists/:list", listHandler.UpdateWeb) a.DELETE("/lists/:list", listHandler.DeleteWeb) a.PUT("/namespaces/:namespace/lists", listHandler.CreateWeb) + a.GET("/lists/:list/listusers", apiv1.ListUsersForList) taskHandler := &handler.WebHandler{ EmptyStruct: func() handler.CObject { diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 58be2dbb5d..5352d1cbfd 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2019-07-16 00:32:48.008049583 +0200 CEST m=+0.169009519 +// 2019-07-18 18:18:32.365544639 +0200 CEST m=+0.166364676 package swagger @@ -58,7 +58,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns an array with all assignees for this task.", + "description": "Returns all labels which are either created by the user or associated with a task the user has at least read-access to.", "consumes": [ "application/json" ], @@ -66,9 +66,9 @@ var doc = `{ "application/json" ], "tags": [ - "assignees" + "labels" ], - "summary": "Get all assignees for a task", + "summary": "Get all labels a user has access to", "parameters": [ { "type": "integer", @@ -78,18 +78,18 @@ var doc = `{ }, { "type": "string", - "description": "Search assignees by their username.", + "description": "Search labels by label text.", "name": "s", "in": "query" } ], "responses": { "200": { - "description": "The assignees", + "description": "The labels", "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/models.Label" } } }, @@ -412,7 +412,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns a list by its ID.", + "description": "Returns a team by its ID.", "consumes": [ "application/json" ], @@ -420,13 +420,13 @@ var doc = `{ "application/json" ], "tags": [ - "list" + "team" ], - "summary": "Gets one list", + "summary": "Gets one team", "parameters": [ { "type": "integer", - "description": "List ID", + "description": "Team ID", "name": "id", "in": "path", "required": true @@ -434,14 +434,14 @@ var doc = `{ ], "responses": { "200": { - "description": "The list", + "description": "The team", "schema": { "type": "object", - "$ref": "#/definitions/models.List" + "$ref": "#/definitions/models.Team" } }, "403": { - "description": "The user does not have access to the list", + "description": "The user does not have access to the team", "schema": { "type": "object", "$ref": "#/definitions/code.vikunja.io.web.HTTPError" @@ -645,6 +645,73 @@ var doc = `{ } } }, + "/lists/{id}/listusers": { + "get": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Lists all users (without emailadresses). Also possible to search for a specific user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "list" + ], + "summary": "Get users", + "parameters": [ + { + "type": "string", + "description": "Search for a user by its name.", + "name": "s", + "in": "query" + }, + { + "type": "integer", + "description": "List ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "All (found) users.", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } + } + }, + "400": { + "description": "Something's invalid.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io.web.HTTPError" + } + }, + "401": { + "description": "The user does not have the right to see the list.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io.web.HTTPError" + } + }, + "500": { + "description": "Internal server error.", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/lists/{id}/teams": { "get": { "security": [ diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index 15bca1d581..8ebc7b218c 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -45,7 +45,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns an array with all assignees for this task.", + "description": "Returns all labels which are either created by the user or associated with a task the user has at least read-access to.", "consumes": [ "application/json" ], @@ -53,9 +53,9 @@ "application/json" ], "tags": [ - "assignees" + "labels" ], - "summary": "Get all assignees for a task", + "summary": "Get all labels a user has access to", "parameters": [ { "type": "integer", @@ -65,18 +65,18 @@ }, { "type": "string", - "description": "Search assignees by their username.", + "description": "Search labels by label text.", "name": "s", "in": "query" } ], "responses": { "200": { - "description": "The assignees", + "description": "The labels", "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/models.Label" } } }, @@ -399,7 +399,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns a list by its ID.", + "description": "Returns a team by its ID.", "consumes": [ "application/json" ], @@ -407,13 +407,13 @@ "application/json" ], "tags": [ - "list" + "team" ], - "summary": "Gets one list", + "summary": "Gets one team", "parameters": [ { "type": "integer", - "description": "List ID", + "description": "Team ID", "name": "id", "in": "path", "required": true @@ -421,14 +421,14 @@ ], "responses": { "200": { - "description": "The list", + "description": "The team", "schema": { "type": "object", - "$ref": "#/definitions/models.List" + "$ref": "#/definitions/models.Team" } }, "403": { - "description": "The user does not have access to the list", + "description": "The user does not have access to the team", "schema": { "type": "object", "$ref": "#/definitions/code.vikunja.io/web.HTTPError" @@ -632,6 +632,73 @@ } } }, + "/lists/{id}/listusers": { + "get": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Lists all users (without emailadresses). Also possible to search for a specific user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "list" + ], + "summary": "Get users", + "parameters": [ + { + "type": "string", + "description": "Search for a user by its name.", + "name": "s", + "in": "query" + }, + { + "type": "integer", + "description": "List ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "All (found) users.", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } + } + }, + "400": { + "description": "Something's invalid.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io/web.HTTPError" + } + }, + "401": { + "description": "The user does not have the right to see the list.", + "schema": { + "type": "object", + "$ref": "#/definitions/code.vikunja.io/web.HTTPError" + } + }, + "500": { + "description": "Internal server error.", + "schema": { + "type": "object", + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/lists/{id}/teams": { "get": { "security": [ diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index f8c2c000ac..3027626fe3 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -704,14 +704,15 @@ paths: get: consumes: - application/json - description: Returns an array with all assignees for this task. + description: Returns all labels which are either created by the user or associated + with a task the user has at least read-access to. parameters: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query name: p type: integer - - description: Search assignees by their username. + - description: Search labels by label text. in: query name: s type: string @@ -719,10 +720,10 @@ paths: - application/json responses: "200": - description: The assignees + description: The labels schema: items: - $ref: '#/definitions/models.User' + $ref: '#/definitions/models.Label' type: array "500": description: Internal error @@ -731,9 +732,9 @@ paths: type: object security: - JWTKeyAuth: [] - summary: Get all assignees for a task + summary: Get all labels a user has access to tags: - - assignees + - labels put: consumes: - application/json @@ -977,9 +978,9 @@ paths: get: consumes: - application/json - description: Returns a list by its ID. + description: Returns a team by its ID. parameters: - - description: List ID + - description: Team ID in: path name: id required: true @@ -988,12 +989,12 @@ paths: - application/json responses: "200": - description: The list + description: The team schema: - $ref: '#/definitions/models.List' + $ref: '#/definitions/models.Team' type: object "403": - description: The user does not have access to the list + description: The user does not have access to the team schema: $ref: '#/definitions/code.vikunja.io/web.HTTPError' type: object @@ -1004,9 +1005,9 @@ paths: type: object security: - JWTKeyAuth: [] - summary: Gets one list + summary: Gets one team tags: - - list + - team post: consumes: - application/json @@ -1097,6 +1098,51 @@ paths: summary: Create a task tags: - task + /lists/{id}/listusers: + get: + consumes: + - application/json + description: Lists all users (without emailadresses). Also possible to search + for a specific user. + parameters: + - description: Search for a user by its name. + in: query + name: s + type: string + - description: List ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: All (found) users. + schema: + items: + $ref: '#/definitions/models.User' + type: array + "400": + description: Something's invalid. + schema: + $ref: '#/definitions/code.vikunja.io/web.HTTPError' + type: object + "401": + description: The user does not have the right to see the list. + schema: + $ref: '#/definitions/code.vikunja.io/web.HTTPError' + type: object + "500": + description: Internal server error. + schema: + $ref: '#/definitions/models.Message' + type: object + security: + - JWTKeyAuth: [] + summary: Get users + tags: + - list /lists/{id}/teams: get: consumes: