feat: add monitoring tg view-all scope
All checks were successful
CI / hygiene (push) Successful in 1s
Build and Deploy / build-and-deploy (push) Successful in 28s
CI / go (push) Successful in 21s
CI / python (push) Successful in 1s

This commit is contained in:
Grendgi
2026-06-19 14:26:51 +03:00
parent cdbdea250d
commit d0f8b48869

View File

@@ -73,7 +73,9 @@ type app struct {
type accessScope struct {
IsAdmin bool
CanManage bool
CanManageAll bool
CanAuth bool
CanViewAll bool
DeptID string
DeptIDs []string
}
@@ -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")
}
@@ -731,6 +735,7 @@ func (a *app) createSection(ctx context.Context, w http.ResponseWriter, r *http.
}
var payload struct {
Vertical string `json:"vertical"`
DepartmentID *string `json:"department_id"`
Slug string `json:"slug"`
Title string `json:"title"`
Emoji *string `json:"emoji"`
@@ -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,8 +1904,9 @@ 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 != "" {
@@ -1904,13 +1914,27 @@ func readAccess(r *http.Request) accessScope {
}
return accessScope{
IsAdmin: admin,
CanManage: admin || deptHead || canManage,
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 == "" {