Compare commits
1 Commits
cdbdea250d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0f8b48869 |
@@ -73,7 +73,9 @@ type app struct {
|
|||||||
type accessScope struct {
|
type accessScope struct {
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
CanManage bool
|
CanManage bool
|
||||||
|
CanManageAll bool
|
||||||
CanAuth bool
|
CanAuth bool
|
||||||
|
CanViewAll bool
|
||||||
DeptID string
|
DeptID string
|
||||||
DeptIDs []string
|
DeptIDs []string
|
||||||
}
|
}
|
||||||
@@ -638,7 +640,9 @@ func (a *app) handleAccessMe(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeJSON(w, http.StatusOK, map[string]any{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"is_admin": scope.IsAdmin,
|
"is_admin": scope.IsAdmin,
|
||||||
"can_manage_department": scope.CanManage,
|
"can_manage_department": scope.CanManage,
|
||||||
|
"can_manage_all": scope.CanManageAll,
|
||||||
"can_auth_telegram": scope.CanAuth,
|
"can_auth_telegram": scope.CanAuth,
|
||||||
|
"can_view_all": scope.CanViewAll,
|
||||||
"department_id": nullableString(scope.DeptID),
|
"department_id": nullableString(scope.DeptID),
|
||||||
"department_ids": scope.departmentIDs(),
|
"department_ids": scope.departmentIDs(),
|
||||||
})
|
})
|
||||||
@@ -667,7 +671,7 @@ func (a *app) listSections(ctx context.Context, w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
args := []any{vertical}
|
args := []any{vertical}
|
||||||
deptFilter := ""
|
deptFilter := ""
|
||||||
if !scope.IsAdmin {
|
if !scope.canReadAll() {
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
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 {
|
var payload struct {
|
||||||
Vertical string `json:"vertical"`
|
Vertical string `json:"vertical"`
|
||||||
|
DepartmentID *string `json:"department_id"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Emoji *string `json:"emoji"`
|
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")
|
writeError(w, http.StatusBadRequest, "vertical, slug and title are required")
|
||||||
return
|
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, `
|
row := a.db.QueryRow(ctx, `
|
||||||
INSERT INTO sections (vertical, department_id, slug, title, emoji, description)
|
INSERT INTO sections (vertical, department_id, slug, title, emoji, description)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
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)
|
args = append(args, vertical, slug)
|
||||||
where := fmt.Sprintf("vertical = $%d AND slug = $%d", len(args)-1, len(args))
|
where := fmt.Sprintf("vertical = $%d AND slug = $%d", len(args)-1, len(args))
|
||||||
if !scope.IsAdmin {
|
if !scope.CanManageAll {
|
||||||
var deptFilter string
|
var deptFilter string
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "department_id")
|
args, deptFilter = appendDepartmentFilter(args, scope, "department_id")
|
||||||
where += deptFilter
|
where += deptFilter
|
||||||
@@ -865,7 +874,7 @@ func (a *app) deleteSection(ctx context.Context, w http.ResponseWriter, r *http.
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
section, err := a.findSection(ctx, vertical, slug, scope)
|
section, err := a.findSection(ctx, vertical, slug, scope.forManageLookup())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeDBError(w, err)
|
writeDBError(w, err)
|
||||||
return
|
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) {
|
func (a *app) findSection(ctx context.Context, vertical, slug string, scope accessScope) (sectionOut, error) {
|
||||||
args := []any{vertical, slug}
|
args := []any{vertical, slug}
|
||||||
where := "s.vertical = $1 AND s.slug = $2"
|
where := "s.vertical = $1 AND s.slug = $2"
|
||||||
if !scope.IsAdmin {
|
if !scope.canReadAll() {
|
||||||
var deptFilter string
|
var deptFilter string
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
||||||
where += deptFilter
|
where += deptFilter
|
||||||
@@ -933,7 +942,7 @@ func (a *app) listChannels(ctx context.Context, w http.ResponseWriter, r *http.R
|
|||||||
args = append(args, section)
|
args = append(args, section)
|
||||||
where += fmt.Sprintf(" AND s.slug = $%d", len(args))
|
where += fmt.Sprintf(" AND s.slug = $%d", len(args))
|
||||||
}
|
}
|
||||||
if !scope.IsAdmin {
|
if !scope.canReadAll() {
|
||||||
var deptFilter string
|
var deptFilter string
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
||||||
where += deptFilter
|
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")
|
writeError(w, http.StatusBadRequest, "identifier, vertical and section are required")
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
writeDBError(w, err)
|
writeDBError(w, err)
|
||||||
return
|
return
|
||||||
@@ -1083,7 +1092,7 @@ func (a *app) updateChannel(ctx context.Context, w http.ResponseWriter, r *http.
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
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)
|
writeDBError(w, err)
|
||||||
return
|
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) != "" {
|
if payload.Vertical != nil && strings.TrimSpace(*payload.Vertical) != "" {
|
||||||
vertical = 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 {
|
if err != nil {
|
||||||
writeDBError(w, err)
|
writeDBError(w, err)
|
||||||
return
|
return
|
||||||
@@ -1144,7 +1153,7 @@ func (a *app) deleteChannel(ctx context.Context, w http.ResponseWriter, r *http.
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
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)
|
writeDBError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1166,7 +1175,7 @@ func (a *app) findChannel(ctx context.Context, id int64, scope accessScope, vert
|
|||||||
args = append(args, strings.TrimSpace(section))
|
args = append(args, strings.TrimSpace(section))
|
||||||
where += fmt.Sprintf(" AND s.slug = $%d", len(args))
|
where += fmt.Sprintf(" AND s.slug = $%d", len(args))
|
||||||
}
|
}
|
||||||
if !scope.IsAdmin {
|
if !scope.canReadAll() {
|
||||||
var deptFilter string
|
var deptFilter string
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
||||||
where += deptFilter
|
where += deptFilter
|
||||||
@@ -1234,7 +1243,7 @@ func (a *app) reanalyzeChannel(ctx context.Context, w http.ResponseWriter, r *ht
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
writeDBError(w, err)
|
writeDBError(w, err)
|
||||||
return
|
return
|
||||||
@@ -1316,7 +1325,7 @@ func (a *app) handleMessages(ctx context.Context, w http.ResponseWriter, r *http
|
|||||||
args = append(args, key, field)
|
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))
|
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
|
var deptFilter string
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
||||||
where += deptFilter
|
where += deptFilter
|
||||||
@@ -1385,7 +1394,7 @@ func (a *app) handleMessageItem(ctx context.Context, w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
args := []any{id}
|
args := []any{id}
|
||||||
where := "m.id = $1"
|
where := "m.id = $1"
|
||||||
if !scope.IsAdmin {
|
if !scope.canReadAll() {
|
||||||
var deptFilter string
|
var deptFilter string
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
||||||
where += deptFilter
|
where += deptFilter
|
||||||
@@ -1508,7 +1517,7 @@ func (a *app) canReadChannelMedia(ctx context.Context, scope accessScope, channe
|
|||||||
FROM channels c
|
FROM channels c
|
||||||
JOIN sections s ON s.id = c.section_id
|
JOIN sections s ON s.id = c.section_id
|
||||||
WHERE c.id = $1 OR c.source_channel_id = $1
|
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) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -1538,7 +1547,7 @@ func (a *app) handleStats(ctx context.Context, w http.ResponseWriter, r *http.Re
|
|||||||
args = append(args, section)
|
args = append(args, section)
|
||||||
where += fmt.Sprintf(" AND s.slug = $%d", len(args))
|
where += fmt.Sprintf(" AND s.slug = $%d", len(args))
|
||||||
}
|
}
|
||||||
if !scope.IsAdmin {
|
if !scope.canReadAll() {
|
||||||
var deptFilter string
|
var deptFilter string
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
||||||
where += deptFilter
|
where += deptFilter
|
||||||
@@ -1657,7 +1666,7 @@ func (a *app) pendingLLM(ctx context.Context, scope accessScope, vertical, secti
|
|||||||
args = append(args, section)
|
args = append(args, section)
|
||||||
where += fmt.Sprintf(" AND s.slug = $%d", len(args))
|
where += fmt.Sprintf(" AND s.slug = $%d", len(args))
|
||||||
}
|
}
|
||||||
if !scope.IsAdmin {
|
if !scope.canReadAll() {
|
||||||
var deptFilter string
|
var deptFilter string
|
||||||
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
args, deptFilter = appendDepartmentFilter(args, scope, "s.department_id")
|
||||||
where += deptFilter
|
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)")
|
writeError(w, http.StatusBadRequest, "prompt is too long (max 30000 chars)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
deptID, err := a.promptDepartmentID(ctx, scope, vertical, section)
|
deptID, err := a.promptDepartmentID(ctx, scope.forManageLookup(), vertical, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeDBError(w, err)
|
writeDBError(w, err)
|
||||||
return
|
return
|
||||||
@@ -1776,7 +1785,7 @@ func (a *app) resetPrompt(ctx context.Context, w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
section := strings.TrimSpace(r.URL.Query().Get("section"))
|
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 {
|
if err != nil {
|
||||||
writeDBError(w, err)
|
writeDBError(w, err)
|
||||||
return
|
return
|
||||||
@@ -1881,11 +1890,11 @@ func (a *app) readScope(w http.ResponseWriter, r *http.Request, manage bool) (ac
|
|||||||
writeError(w, http.StatusNotFound, "not found")
|
writeError(w, http.StatusNotFound, "not found")
|
||||||
return scope, false
|
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")
|
writeError(w, http.StatusForbidden, "department is required")
|
||||||
return scope, false
|
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")
|
writeError(w, http.StatusForbidden, "department is required")
|
||||||
return scope, false
|
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 {
|
func readAccess(r *http.Request) accessScope {
|
||||||
admin := commonmw.HeaderBool(r, "X-User-Is-Admin")
|
admin := commonmw.HeaderBool(r, "X-User-Is-Admin")
|
||||||
deptHead := commonmw.HeaderBool(r, "X-User-Is-Department-Head")
|
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")
|
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"))
|
deptID := strings.TrimSpace(r.Header.Get("X-User-Department-Id"))
|
||||||
deptIDs := commonmw.HeaderCSV(r, "X-User-Department-Ids")
|
deptIDs := commonmw.HeaderCSV(r, "X-User-Department-Ids")
|
||||||
if deptID != "" {
|
if deptID != "" {
|
||||||
@@ -1904,13 +1914,27 @@ func readAccess(r *http.Request) accessScope {
|
|||||||
}
|
}
|
||||||
return accessScope{
|
return accessScope{
|
||||||
IsAdmin: admin,
|
IsAdmin: admin,
|
||||||
CanManage: admin || deptHead || canManage,
|
CanManage: admin || deptHead || canManagePermission,
|
||||||
|
CanManageAll: admin || (canManagePermission && canViewAll),
|
||||||
CanAuth: admin || canAuth,
|
CanAuth: admin || canAuth,
|
||||||
|
CanViewAll: admin || canViewAll,
|
||||||
DeptID: deptID,
|
DeptID: deptID,
|
||||||
DeptIDs: deptIDs,
|
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 {
|
func appendUniqueString(items []string, value string) []string {
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user