From 703f544cdf5d14b2f0383e094a05d4f4356d7164 Mon Sep 17 00:00:00 2001 From: Grendgi Date: Tue, 16 Jun 2026 09:34:17 +0300 Subject: [PATCH] Allow managers to view subordinate PF projects --- internal/pf/http.go | 62 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/internal/pf/http.go b/internal/pf/http.go index 68868a9..78699f5 100644 --- a/internal/pf/http.go +++ b/internal/pf/http.go @@ -1,6 +1,7 @@ package pf import ( + "database/sql" "encoding/json" "errors" "net/http" @@ -111,13 +112,23 @@ func (s Server) accessMe(w http.ResponseWriter, r *http.Request) { } func (s Server) summary(w http.ResponseWriter, r *http.Request) { - emp, err := s.App.CurrentEmployee(r.Context(), portalUserID(r), false) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - if emp != nil && emp.TGChatID == nil { - emp = nil + var emp *Employee + var err error + if requested := ownerPortalIDFromQuery(r); requested != nil { + var ok bool + emp, ok = s.resolveProjectOwnerForRead(w, r, requested) + if !ok { + return + } + } else { + emp, err = s.App.CurrentEmployee(r.Context(), portalUserID(r), false) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + if emp != nil && emp.TGChatID == nil { + emp = nil + } } out, err := s.App.Summary(r.Context(), emp) if err != nil { @@ -226,7 +237,13 @@ func (s Server) employeeItem(w http.ResponseWriter, r *http.Request, path string func (s Server) projects(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - emp, ok := s.requireEmployee(w, r) + var emp *Employee + var ok bool + if requested := ownerPortalIDFromQuery(r); requested != nil { + emp, ok = s.resolveProjectOwnerForRead(w, r, requested) + } else { + emp, ok = s.requireEmployee(w, r) + } if !ok { return } @@ -439,6 +456,27 @@ func (s Server) resolveProjectOwner(w http.ResponseWriter, r *http.Request, requ return owner, true } +func (s Server) resolveProjectOwnerForRead(w http.ResponseWriter, r *http.Request, requested *string) (*Employee, bool) { + if requested == nil || strings.TrimSpace(*requested) == "" { + return s.requireEmployee(w, r) + } + targetPortalID := strings.TrimSpace(*requested) + if !canManagePortalUser(r, targetPortalID) { + writeError(w, http.StatusForbidden, "Нет прав на просмотр объектов этого сотрудника") + return nil, false + } + owner, err := s.App.EmployeeByPortalUserID(r.Context(), targetPortalID) + if errors.Is(err, ErrNotFound) || errors.Is(err, sql.ErrNoRows) { + writeError(w, http.StatusNotFound, "employee not found") + return nil, false + } + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return nil, false + } + return owner, true +} + func (s Server) projectOwnerIDForAccess(w http.ResponseWriter, r *http.Request, projectID int64) (int64, bool) { owner, err := s.App.ProjectOwner(r.Context(), projectID) if errors.Is(err, ErrNotFound) { @@ -511,6 +549,14 @@ func subordinatePortalIDs(r *http.Request) []string { return out } +func ownerPortalIDFromQuery(r *http.Request) *string { + value := strings.TrimSpace(r.URL.Query().Get("owner_portal_user_id")) + if value == "" { + return nil + } + return &value +} + func nullablePlain(value string) *string { if strings.TrimSpace(value) == "" { return nil