From d0f8b48869d8fe5a96cae242505da74735655942 Mon Sep 17 00:00:00 2001 From: Grendgi Date: Fri, 19 Jun 2026 14:26:51 +0300 Subject: [PATCH] feat: add monitoring tg view-all scope --- cmd/server/main.go | 98 +++++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index cd44c82..b0aa77d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -71,11 +71,13 @@ type app struct { } type accessScope struct { - IsAdmin bool - CanManage bool - CanAuth bool - DeptID string - DeptIDs []string + IsAdmin bool + CanManage bool + CanManageAll bool + CanAuth bool + CanViewAll bool + DeptID string + DeptIDs []string } type sectionOut struct { @@ -638,7 +640,9 @@ func (a *app) handleAccessMe(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{ "is_admin": scope.IsAdmin, "can_manage_department": scope.CanManage, + "can_manage_all": scope.CanManageAll, "can_auth_telegram": scope.CanAuth, + "can_view_all": scope.CanViewAll, "department_id": nullableString(scope.DeptID), "department_ids": scope.departmentIDs(), }) @@ -667,7 +671,7 @@ func (a *app) listSections(ctx context.Context, w http.ResponseWriter, r *http.R args := []any{vertical} deptFilter := "" - if !scope.IsAdmin { + if !scope.canReadAll() { args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id") } @@ -730,11 +734,12 @@ func (a *app) createSection(ctx context.Context, w http.ResponseWriter, r *http. return } var payload struct { - Vertical string `json:"vertical"` - Slug string `json:"slug"` - Title string `json:"title"` - Emoji *string `json:"emoji"` - Description *string `json:"description"` + Vertical string `json:"vertical"` + DepartmentID *string `json:"department_id"` + Slug string `json:"slug"` + Title string `json:"title"` + Emoji *string `json:"emoji"` + Description *string `json:"description"` } if !readBody(w, r, &payload) { return @@ -746,7 +751,11 @@ func (a *app) createSection(ctx context.Context, w http.ResponseWriter, r *http. writeError(w, http.StatusBadRequest, "vertical, slug and title are required") return } - dept := nullableString(scope.primaryDepartmentID()) + deptID := scope.primaryDepartmentID() + if scope.CanManageAll && payload.DepartmentID != nil { + deptID = strings.TrimSpace(*payload.DepartmentID) + } + dept := nullableString(deptID) row := a.db.QueryRow(ctx, ` INSERT INTO sections (vertical, department_id, slug, title, emoji, description) VALUES ($1, $2, $3, $4, $5, $6) @@ -837,7 +846,7 @@ func (a *app) updateSection(ctx context.Context, w http.ResponseWriter, r *http. } args = append(args, vertical, slug) where := fmt.Sprintf("vertical = $%d AND slug = $%d", len(args)-1, len(args)) - if !scope.IsAdmin { + if !scope.CanManageAll { var deptFilter string args, deptFilter = appendDepartmentFilter(args, scope, "department_id") where += deptFilter @@ -865,7 +874,7 @@ func (a *app) deleteSection(ctx context.Context, w http.ResponseWriter, r *http. if !ok { return } - section, err := a.findSection(ctx, vertical, slug, scope) + section, err := a.findSection(ctx, vertical, slug, scope.forManageLookup()) if err != nil { writeDBError(w, err) return @@ -889,7 +898,7 @@ func (a *app) deleteSection(ctx context.Context, w http.ResponseWriter, r *http. func (a *app) findSection(ctx context.Context, vertical, slug string, scope accessScope) (sectionOut, error) { args := []any{vertical, slug} where := "s.vertical = $1 AND s.slug = $2" - if !scope.IsAdmin { + if !scope.canReadAll() { var deptFilter string args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id") where += deptFilter @@ -933,7 +942,7 @@ func (a *app) listChannels(ctx context.Context, w http.ResponseWriter, r *http.R args = append(args, section) where += fmt.Sprintf(" AND s.slug = $%d", len(args)) } - if !scope.IsAdmin { + if !scope.canReadAll() { var deptFilter string args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id") where += deptFilter @@ -990,7 +999,7 @@ func (a *app) createChannel(ctx context.Context, w http.ResponseWriter, r *http. writeError(w, http.StatusBadRequest, "identifier, vertical and section are required") return } - section, err := a.findSection(ctx, payload.Vertical, payload.Section, scope) + section, err := a.findSection(ctx, payload.Vertical, payload.Section, scope.forManageLookup()) if err != nil { writeDBError(w, err) return @@ -1083,7 +1092,7 @@ func (a *app) updateChannel(ctx context.Context, w http.ResponseWriter, r *http. if !ok { return } - if _, err := a.findChannel(ctx, id, scope, r.URL.Query().Get("vertical"), r.URL.Query().Get("section")); err != nil { + if _, err := a.findChannel(ctx, id, scope.forManageLookup(), r.URL.Query().Get("vertical"), r.URL.Query().Get("section")); err != nil { writeDBError(w, err) return } @@ -1110,7 +1119,7 @@ func (a *app) updateChannel(ctx context.Context, w http.ResponseWriter, r *http. if payload.Vertical != nil && strings.TrimSpace(*payload.Vertical) != "" { vertical = strings.TrimSpace(*payload.Vertical) } - section, err := a.findSection(ctx, vertical, strings.TrimSpace(*payload.Section), scope) + section, err := a.findSection(ctx, vertical, strings.TrimSpace(*payload.Section), scope.forManageLookup()) if err != nil { writeDBError(w, err) return @@ -1144,7 +1153,7 @@ func (a *app) deleteChannel(ctx context.Context, w http.ResponseWriter, r *http. if !ok { return } - if _, err := a.findChannel(ctx, id, scope, r.URL.Query().Get("vertical"), r.URL.Query().Get("section")); err != nil { + if _, err := a.findChannel(ctx, id, scope.forManageLookup(), r.URL.Query().Get("vertical"), r.URL.Query().Get("section")); err != nil { writeDBError(w, err) return } @@ -1166,7 +1175,7 @@ func (a *app) findChannel(ctx context.Context, id int64, scope accessScope, vert args = append(args, strings.TrimSpace(section)) where += fmt.Sprintf(" AND s.slug = $%d", len(args)) } - if !scope.IsAdmin { + if !scope.canReadAll() { var deptFilter string args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id") where += deptFilter @@ -1234,7 +1243,7 @@ func (a *app) reanalyzeChannel(ctx context.Context, w http.ResponseWriter, r *ht if !ok { return } - ch, err := a.findChannel(ctx, id, scope, r.URL.Query().Get("vertical"), r.URL.Query().Get("section")) + ch, err := a.findChannel(ctx, id, scope.forManageLookup(), r.URL.Query().Get("vertical"), r.URL.Query().Get("section")) if err != nil { writeDBError(w, err) return @@ -1316,7 +1325,7 @@ func (a *app) handleMessages(ctx context.Context, w http.ResponseWriter, r *http args = append(args, key, field) where += fmt.Sprintf(" AND COALESCE(mc.verdict ->> $%d, m.extracted -> $%d ->> $%d) = 'true'", len(args), len(args)-1, len(args)) } - if !scope.IsAdmin { + if !scope.canReadAll() { var deptFilter string args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id") where += deptFilter @@ -1385,7 +1394,7 @@ func (a *app) handleMessageItem(ctx context.Context, w http.ResponseWriter, r *h } args := []any{id} where := "m.id = $1" - if !scope.IsAdmin { + if !scope.canReadAll() { var deptFilter string args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id") where += deptFilter @@ -1508,7 +1517,7 @@ func (a *app) canReadChannelMedia(ctx context.Context, scope accessScope, channe FROM channels c JOIN sections s ON s.id = c.section_id WHERE c.id = $1 OR c.source_channel_id = $1 - `, channelID, scope.departmentIDs(), scope.IsAdmin).Scan(&allowed) + `, channelID, scope.departmentIDs(), scope.canReadAll()).Scan(&allowed) if errors.Is(err, pgx.ErrNoRows) { return false, nil } @@ -1538,7 +1547,7 @@ func (a *app) handleStats(ctx context.Context, w http.ResponseWriter, r *http.Re args = append(args, section) where += fmt.Sprintf(" AND s.slug = $%d", len(args)) } - if !scope.IsAdmin { + if !scope.canReadAll() { var deptFilter string args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id") where += deptFilter @@ -1657,7 +1666,7 @@ func (a *app) pendingLLM(ctx context.Context, scope accessScope, vertical, secti args = append(args, section) where += fmt.Sprintf(" AND s.slug = $%d", len(args)) } - if !scope.IsAdmin { + if !scope.canReadAll() { var deptFilter string args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id") where += deptFilter @@ -1748,7 +1757,7 @@ func (a *app) savePrompt(ctx context.Context, w http.ResponseWriter, r *http.Req writeError(w, http.StatusBadRequest, "prompt is too long (max 30000 chars)") return } - deptID, err := a.promptDepartmentID(ctx, scope, vertical, section) + deptID, err := a.promptDepartmentID(ctx, scope.forManageLookup(), vertical, section) if err != nil { writeDBError(w, err) return @@ -1776,7 +1785,7 @@ func (a *app) resetPrompt(ctx context.Context, w http.ResponseWriter, r *http.Re return } section := strings.TrimSpace(r.URL.Query().Get("section")) - deptID, err := a.promptDepartmentID(ctx, scope, vertical, section) + deptID, err := a.promptDepartmentID(ctx, scope.forManageLookup(), vertical, section) if err != nil { writeDBError(w, err) return @@ -1881,11 +1890,11 @@ func (a *app) readScope(w http.ResponseWriter, r *http.Request, manage bool) (ac writeError(w, http.StatusNotFound, "not found") return scope, false } - } else if !scope.IsAdmin && len(scope.departmentIDs()) == 0 { + } else if !scope.canReadAll() && len(scope.departmentIDs()) == 0 { writeError(w, http.StatusForbidden, "department is required") return scope, false } - if manage && !scope.IsAdmin && len(scope.departmentIDs()) == 0 { + if manage && !scope.CanManageAll && len(scope.departmentIDs()) == 0 { writeError(w, http.StatusForbidden, "department is required") return scope, false } @@ -1895,22 +1904,37 @@ func (a *app) readScope(w http.ResponseWriter, r *http.Request, manage bool) (ac func readAccess(r *http.Request) accessScope { admin := commonmw.HeaderBool(r, "X-User-Is-Admin") deptHead := commonmw.HeaderBool(r, "X-User-Is-Department-Head") - canManage := commonmw.HeaderBool(r, "X-Monitoring-TG-Can-Manage") + canManagePermission := commonmw.HeaderBool(r, "X-Monitoring-TG-Can-Manage") canAuth := commonmw.HeaderBool(r, "X-Monitoring-TG-Can-Auth") + canViewAll := commonmw.HeaderBool(r, "X-Monitoring-TG-Can-View-All") deptID := strings.TrimSpace(r.Header.Get("X-User-Department-Id")) deptIDs := commonmw.HeaderCSV(r, "X-User-Department-Ids") if deptID != "" { deptIDs = appendUniqueString(deptIDs, deptID) } return accessScope{ - IsAdmin: admin, - CanManage: admin || deptHead || canManage, - CanAuth: admin || canAuth, - DeptID: deptID, - DeptIDs: deptIDs, + IsAdmin: admin, + CanManage: admin || deptHead || canManagePermission, + CanManageAll: admin || (canManagePermission && canViewAll), + CanAuth: admin || canAuth, + CanViewAll: admin || canViewAll, + DeptID: deptID, + DeptIDs: deptIDs, } } +func (s accessScope) canReadAll() bool { + return s.IsAdmin || s.CanViewAll +} + +func (s accessScope) forManageLookup() accessScope { + if s.CanManageAll { + return s + } + s.CanViewAll = false + return s +} + func appendUniqueString(items []string, value string) []string { value = strings.TrimSpace(value) if value == "" {