From 73e985c45c21f676821a14125e70c2b575be511b Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Mon, 29 Jul 2024 17:26:14 +0100 Subject: [PATCH] jellyseerr: add user modification methods addded permissions get/set before realizing it already comes as part of the User object. Split User attributes that will be templated into UserTemplate struct, which User inherits. ApplyTemplateToUser takes a UserTemplate, while ModifyUser takes a plain map with some typed fields (display name and email, for now). --- jellyseerr/jellyseerr.go | 95 +++++++++++++++++++++++++++++++++-- jellyseerr/jellyseerr_test.go | 24 ++++++++- jellyseerr/models.go | 29 ++++++++--- 3 files changed, 137 insertions(+), 11 deletions(-) diff --git a/jellyseerr/jellyseerr.go b/jellyseerr/jellyseerr.go index ed41863..82659fc 100644 --- a/jellyseerr/jellyseerr.go +++ b/jellyseerr/jellyseerr.go @@ -91,7 +91,7 @@ func (js *Jellyseerr) getJSON(url string, params map[string]string, queryParams } // does a POST and optionally returns response as string. Returns a string instead of an io.reader bcs i couldn't get it working otherwise. -func (js *Jellyseerr) send(mode string, url string, data interface{}, response bool, headers map[string]string) (string, int, error) { +func (js *Jellyseerr) send(mode string, url string, data any, response bool, headers map[string]string) (string, int, error) { responseText := "" params, _ := json.Marshal(data) req, _ := http.NewRequest(mode, url, bytes.NewBuffer(params)) @@ -129,11 +129,11 @@ func (js *Jellyseerr) send(mode string, url string, data interface{}, response b return responseText, resp.StatusCode, nil } -func (js *Jellyseerr) post(url string, data map[string]interface{}, response bool) (string, int, error) { +func (js *Jellyseerr) post(url string, data any, response bool) (string, int, error) { return js.send("POST", url, data, response, nil) } -func (js *Jellyseerr) put(url string, data map[string]interface{}, response bool) (string, int, error) { +func (js *Jellyseerr) put(url string, data any, response bool) (string, int, error) { return js.send("PUT", url, data, response, nil) } @@ -236,3 +236,92 @@ func (js *Jellyseerr) Me() (User, error) { err = json.Unmarshal([]byte(resp), &data) return data, err } + +func (js *Jellyseerr) GetPermissions(jfID string) (Permissions, error) { + data := permissionsDTO{Permissions: -1} + u, err := js.MustGetUser(jfID) + if err != nil { + return data.Permissions, err + } + + resp, status, err := js.getJSON(fmt.Sprintf(js.server+"/user/%d/settings/permissions", u.ID), nil, url.Values{}) + if err != nil { + return data.Permissions, err + } + if status != 200 { + return data.Permissions, fmt.Errorf("failed (error %d)", status) + } + err = json.Unmarshal([]byte(resp), &data) + return data.Permissions, err +} + +func (js *Jellyseerr) SetPermissions(jfID string, perm Permissions) error { + u, err := js.MustGetUser(jfID) + if err != nil { + return err + } + + _, status, err := js.post(fmt.Sprintf(js.server+"/user/%d/settings/permissions", u.ID), permissionsDTO{Permissions: perm}, false) + if err != nil { + return err + } + if status != 200 && status != 201 { + return fmt.Errorf("failed (error %d)", status) + } + u.Permissions = perm + js.userCache[jfID] = u + return nil +} + +func (js *Jellyseerr) ApplyTemplateToUser(jfID string, tmpl UserTemplate) error { + u, err := js.MustGetUser(jfID) + if err != nil { + return err + } + + _, status, err := js.put(fmt.Sprintf(js.server+"/user/%d", u.ID), tmpl, false) + if err != nil { + return err + } + if status != 200 && status != 201 { + return fmt.Errorf("failed (error %d)", status) + } + u.UserTemplate = tmpl + js.userCache[jfID] = u + return nil +} + +func (js *Jellyseerr) ModifyUser(jfID string, conf map[UserField]string) error { + u, err := js.MustGetUser(jfID) + if err != nil { + return err + } + + _, status, err := js.put(fmt.Sprintf(js.server+"/user/%d", u.ID), conf, false) + if err != nil { + return err + } + if status != 200 && status != 201 { + return fmt.Errorf("failed (error %d)", status) + } + // Lazily just invalidate the cache. + js.cacheExpiry = time.Now() + return nil +} + +func (js *Jellyseerr) DeleteUser(jfID string) error { + u, err := js.MustGetUser(jfID) + if err != nil { + return err + } + + _, status, err := js.send("DELETE", fmt.Sprintf(js.server+"/user/%d", u.ID), nil, false, nil) + if status != 200 && status != 201 { + return fmt.Errorf("failed (error %d)", status) + } + if err != nil { + return err + } + delete(js.userCache, jfID) + return err +} diff --git a/jellyseerr/jellyseerr_test.go b/jellyseerr/jellyseerr_test.go index 6159ad7..1180758 100644 --- a/jellyseerr/jellyseerr_test.go +++ b/jellyseerr/jellyseerr_test.go @@ -9,6 +9,7 @@ import ( const ( API_KEY = "MTcyMjI2MDM2MTYyMzMxNDZkZmYyLTE4MzMtNDUyNy1hODJlLTI0MTZkZGUyMDg2Ng==" URI = "http://localhost:5055" + PERM = 2097184 ) func client() *Jellyseerr { @@ -26,7 +27,7 @@ func TestMe(t *testing.T) { } } -func TestImportFromJellyfin(t *testing.T) { +/* func TestImportFromJellyfin(t *testing.T) { js := client() list, err := js.ImportFromJellyfin("6b75e189efb744f583aa2e8e9cee41d3") if err != nil { @@ -35,7 +36,7 @@ func TestImportFromJellyfin(t *testing.T) { if len(list) == 0 { t.Fatalf("returned no users") } -} +} */ func TestMustGetUser(t *testing.T) { js := client() @@ -47,3 +48,22 @@ func TestMustGetUser(t *testing.T) { t.Fatalf("returned no users") } } + +func TestSetPermissions(t *testing.T) { + js := client() + err := js.SetPermissions("6b75e189efb744f583aa2e8e9cee41d3", PERM) + if err != nil { + t.Fatalf("returned error %+v", err) + } +} + +func TestGetPermissions(t *testing.T) { + js := client() + perm, err := js.GetPermissions("6b75e189efb744f583aa2e8e9cee41d3") + if err != nil { + t.Fatalf("returned error %+v", err) + } + if perm != PERM { + t.Fatalf("got unexpected perm code %d", perm) + } +} diff --git a/jellyseerr/models.go b/jellyseerr/models.go index 438c1dc..4cc3c02 100644 --- a/jellyseerr/models.go +++ b/jellyseerr/models.go @@ -2,8 +2,15 @@ package jellyseerr import "time" +type UserField string + +const ( + FieldDisplayName UserField = "displayName" + FieldEmail UserField = "email" +) + type User struct { - Permissions int `json:"permissions"` + UserTemplate // Note: You can set this with User.UserTemplate = value. Warnings []any `json:"warnings"` ID int `json:"id"` Email string `json:"email"` @@ -11,23 +18,27 @@ type User struct { JellyfinUsername string `json:"jellyfinUsername"` Username string `json:"username"` RecoveryLinkExpirationDate any `json:"recoveryLinkExpirationDate"` - UserType int `json:"userType"` PlexID string `json:"plexId"` JellyfinUserID string `json:"jellyfinUserId"` JellyfinDeviceID string `json:"jellyfinDeviceId"` JellyfinAuthToken string `json:"jellyfinAuthToken"` PlexToken string `json:"plexToken"` Avatar string `json:"avatar"` - MovieQuotaLimit any `json:"movieQuotaLimit"` - MovieQuotaDays any `json:"movieQuotaDays"` - TvQuotaLimit any `json:"tvQuotaLimit"` - TvQuotaDays any `json:"tvQuotaDays"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` RequestCount int `json:"requestCount"` DisplayName string `json:"displayName"` } +type UserTemplate struct { + Permissions Permissions `json:"permissions"` + UserType int `json:"userType"` + MovieQuotaLimit any `json:"movieQuotaLimit"` + MovieQuotaDays any `json:"movieQuotaDays"` + TvQuotaLimit any `json:"tvQuotaLimit"` + TvQuotaDays any `json:"tvQuotaDays"` +} + type PageInfo struct { Pages int `json:"pages"` PageSize int `json:"pageSize"` @@ -39,3 +50,9 @@ type GetUsersDTO struct { Page PageInfo `json:"pageInfo"` Results []User `json:"results"` } + +type permissionsDTO struct { + Permissions Permissions `json:"permissions"` +} + +type Permissions int