NOISSUE - Add missing role fields to re and reports (#3435)

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
Steve Munene
2026-04-08 11:40:27 +03:00
committed by GitHub
parent c5fc0b64d4
commit c9bf5beba2
12 changed files with 289 additions and 167 deletions
+3
View File
@@ -1 +1,4 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
optional bind-mount placeholder
@@ -1 +1,4 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
optional bind-mount placeholder
+3
View File
@@ -1 +1,4 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
optional bind-mount placeholder
+3
View File
@@ -1 +1,4 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
optional bind-mount placeholder
+16 -14
View File
@@ -12,6 +12,7 @@ import (
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/pkg/roles"
)
const (
@@ -21,20 +22,21 @@ const (
// ReportConfig represents a report configuration.
type ReportConfig struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Schedule any `json:"schedule,omitempty"`
Config any `json:"config,omitempty"`
Email any `json:"email,omitempty"`
Metrics any `json:"metrics,omitempty"`
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Schedule any `json:"schedule,omitempty"`
Config any `json:"config,omitempty"`
Email any `json:"email,omitempty"`
Metrics any `json:"metrics,omitempty"`
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
Roles []roles.MemberRoleActions `json:"roles,omitempty"`
}
type ReportTemplate any
+17 -15
View File
@@ -10,27 +10,29 @@ import (
"net/http"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/pkg/roles"
)
const rulesEndpoint = "rules"
// Rule represents a rule configuration.
type Rule struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
DomainID string `json:"domain,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Tags []string `json:"tags,omitempty"`
InputChannel string `json:"input_channel,omitempty"`
InputTopic string `json:"input_topic,omitempty"`
Logic any `json:"logic,omitempty"`
Outputs any `json:"outputs,omitempty"`
Schedule any `json:"schedule,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
DomainID string `json:"domain,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Tags []string `json:"tags,omitempty"`
InputChannel string `json:"input_channel,omitempty"`
InputTopic string `json:"input_topic,omitempty"`
Logic any `json:"logic,omitempty"`
Outputs any `json:"outputs,omitempty"`
Schedule any `json:"schedule,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
Roles []roles.MemberRoleActions `json:"roles,omitempty"`
}
type Page struct {
+53 -26
View File
@@ -404,37 +404,64 @@ func (repo *PostgresRepository) ListAllRules(ctx context.Context, pm re.PageMeta
func (repo *PostgresRepository) ListUserRules(ctx context.Context, userID string, pm re.PageMeta) (re.Page, error) {
pm.UserID = userID
clauses := []string{
`(
EXISTS (
SELECT 1
FROM rules_roles rr
JOIN rules_role_members rrm ON rrm.role_id = rr.id
WHERE rr.entity_id = r.id AND rrm.member_id = :user_id
)
OR EXISTS (
SELECT 1
FROM domains_roles dr
JOIN domains_role_members drm ON drm.role_id = dr.id
JOIN domains_role_actions dra ON dra.role_id = dr.id
WHERE dr.entity_id = r.domain_id
AND drm.member_id = :user_id
AND dra.action LIKE 'rule%'
)
)`,
additionalConditions := pageRulesQueryConditions(pm)
additionalWhereClause := ""
if len(additionalConditions) > 0 {
additionalWhereClause = "AND " + strings.Join(additionalConditions, " AND ")
}
clauses = append(clauses, pageRulesQueryConditions(pm)...)
orderClause := rulesOrderClause(pm)
pgData := rulesPageData(pm)
whereClause := fmt.Sprintf("WHERE %s", strings.Join(clauses, " AND "))
innerQ := fmt.Sprintf(`
SELECT DISTINCT r.id, r.name, r.domain_id, r.tags, r.input_channel, r.input_topic, r.logic_type, r.logic_value, r.outputs,
r.start_datetime, r.time, r.recurring, r.recurring_period, r.created_at, r.created_by, r.updated_at, r.updated_by, r.status
FROM rules r
%s
`, whereClause)
WITH direct_rules AS (
SELECT r.id, r.name, r.domain_id, r.tags, r.metadata, r.input_channel, r.input_topic,
r.logic_type, r.logic_value, r.outputs, r.start_datetime, r.time,
r.recurring, r.recurring_period, r.created_at, r.created_by, r.updated_at, r.updated_by, r.status,
rr.id AS role_id,
rr."name" AS role_name,
array_remove(array_agg(DISTINCT rra."action"), NULL) AS actions,
'direct' AS access_type,
'' AS access_provider_id,
'' AS access_provider_role_id,
'' AS access_provider_role_name,
CAST(array[] AS text[]) AS access_provider_role_actions
FROM rules_role_members rrm
JOIN rules_roles rr ON rr.id = rrm.role_id
JOIN rules r ON r.id = rr.entity_id
LEFT JOIN rules_role_actions rra ON rra.role_id = rrm.role_id
WHERE rrm.member_id = :user_id
%s
GROUP BY r.id, rr.id, rr."name"
),
domain_rules AS (
SELECT r.id, r.name, r.domain_id, r.tags, r.metadata, r.input_channel, r.input_topic,
r.logic_type, r.logic_value, r.outputs, r.start_datetime, r.time,
r.recurring, r.recurring_period, r.created_at, r.created_by, r.updated_at, r.updated_by, r.status,
'' AS role_id,
'' AS role_name,
CAST(array[] AS text[]) AS actions,
'domain' AS access_type,
d.id AS access_provider_id,
dr.id AS access_provider_role_id,
dr."name" AS access_provider_role_name,
array_agg(DISTINCT dra."action") AS access_provider_role_actions
FROM domains_role_members drm
JOIN domains_role_actions dra ON dra.role_id = drm.role_id
JOIN domains_roles dr ON dr.id = drm.role_id
JOIN domains d ON d.id = dr.entity_id
JOIN rules r ON r.domain_id = d.id
WHERE drm.member_id = :user_id
AND dra.action LIKE 'rule%%'
AND NOT EXISTS (SELECT 1 FROM direct_rules tmp WHERE tmp.id = r.id)
%s
GROUP BY r.id, d.id, dr.id, dr."name"
)
SELECT * FROM direct_rules
UNION ALL
SELECT * FROM domain_rules
`, additionalWhereClause, additionalWhereClause)
q := fmt.Sprintf(`
SELECT * FROM (%s) AS sub %s %s;
+44 -27
View File
@@ -13,31 +13,40 @@ import (
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/re"
"github.com/jackc/pgtype"
"github.com/lib/pq"
)
// dbRule represents the database structure for a Rule.
type dbRule struct {
ID string `db:"id"`
Name string `db:"name"`
DomainID string `db:"domain_id"`
Tags pgtype.TextArray `db:"tags,omitempty"`
Metadata []byte `db:"metadata,omitempty"`
InputChannel string `db:"input_channel"`
InputTopic sql.NullString `db:"input_topic"`
LogicType re.ScriptType `db:"logic_type"`
LogicValue string `db:"logic_value"`
Outputs []byte `db:"outputs"`
StartDateTime sql.NullTime `db:"start_datetime"`
Time sql.NullTime `db:"time"`
Recurring schedule.Recurring `db:"recurring"`
RecurringPeriod uint `db:"recurring_period"`
Status re.Status `db:"status"`
CreatedAt time.Time `db:"created_at"`
CreatedBy string `db:"created_by"`
UpdatedAt time.Time `db:"updated_at"`
UpdatedBy string `db:"updated_by"`
MemberID string `db:"member_id,omitempty"`
Roles json.RawMessage `db:"roles,omitempty"`
ID string `db:"id"`
Name string `db:"name"`
DomainID string `db:"domain_id"`
Tags pgtype.TextArray `db:"tags,omitempty"`
Metadata []byte `db:"metadata,omitempty"`
InputChannel string `db:"input_channel"`
InputTopic sql.NullString `db:"input_topic"`
LogicType re.ScriptType `db:"logic_type"`
LogicValue string `db:"logic_value"`
Outputs []byte `db:"outputs"`
StartDateTime sql.NullTime `db:"start_datetime"`
Time sql.NullTime `db:"time"`
Recurring schedule.Recurring `db:"recurring"`
RecurringPeriod uint `db:"recurring_period"`
Status re.Status `db:"status"`
CreatedAt time.Time `db:"created_at"`
CreatedBy string `db:"created_by"`
UpdatedAt time.Time `db:"updated_at"`
UpdatedBy string `db:"updated_by"`
MemberID string `db:"member_id,omitempty"`
RoleID string `db:"role_id,omitempty"`
RoleName string `db:"role_name,omitempty"`
Actions pq.StringArray `db:"actions,omitempty"`
AccessType string `db:"access_type,omitempty"`
AccessProviderId string `db:"access_provider_id,omitempty"`
AccessProviderRoleId string `db:"access_provider_role_id,omitempty"`
AccessProviderRoleName string `db:"access_provider_role_name,omitempty"`
AccessProviderRoleActions pq.StringArray `db:"access_provider_role_actions,omitempty"`
Roles json.RawMessage `db:"roles,omitempty"`
}
func ruleToDb(r re.Rule) (dbRule, error) {
@@ -137,12 +146,20 @@ func dbToRule(dto dbRule) (re.Rule, error) {
Recurring: dto.Recurring,
RecurringPeriod: dto.RecurringPeriod,
},
Status: dto.Status,
CreatedAt: dto.CreatedAt,
CreatedBy: dto.CreatedBy,
UpdatedAt: dto.UpdatedAt,
UpdatedBy: dto.UpdatedBy,
Roles: roles,
Status: dto.Status,
CreatedAt: dto.CreatedAt,
CreatedBy: dto.CreatedBy,
UpdatedAt: dto.UpdatedAt,
UpdatedBy: dto.UpdatedBy,
RoleID: dto.RoleID,
RoleName: dto.RoleName,
Actions: []string(dto.Actions),
AccessType: dto.AccessType,
AccessProviderId: dto.AccessProviderId,
AccessProviderRoleId: dto.AccessProviderRoleId,
AccessProviderRoleName: dto.AccessProviderRoleName,
AccessProviderRoleActions: []string(dto.AccessProviderRoleActions),
Roles: roles,
}, nil
}
+25 -16
View File
@@ -45,22 +45,31 @@ var outputRegistry = map[outputs.OutputType]func() Runnable{
}
type Rule struct {
ID string `json:"id"`
Name string `json:"name"`
DomainID string `json:"domain"`
Metadata Metadata `json:"metadata,omitempty"`
Tags []string `json:"tags,omitempty"`
InputChannel string `json:"input_channel"`
InputTopic string `json:"input_topic"`
Logic Script `json:"logic"`
Outputs Outputs `json:"outputs,omitempty"`
Schedule schedule.Schedule `json:"schedule,omitempty"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by"`
UpdatedAt time.Time `json:"updated_at"`
UpdatedBy string `json:"updated_by"`
Roles []roles.MemberRoleActions `json:"roles,omitempty"`
ID string `json:"id"`
Name string `json:"name"`
DomainID string `json:"domain"`
Metadata Metadata `json:"metadata,omitempty"`
Tags []string `json:"tags,omitempty"`
InputChannel string `json:"input_channel"`
InputTopic string `json:"input_topic"`
Logic Script `json:"logic"`
Outputs Outputs `json:"outputs,omitempty"`
Schedule schedule.Schedule `json:"schedule,omitempty"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by"`
UpdatedAt time.Time `json:"updated_at"`
UpdatedBy string `json:"updated_by"`
// Extended
RoleID string `json:"role_id,omitempty"`
RoleName string `json:"role_name,omitempty"`
Actions []string `json:"actions,omitempty"`
AccessType string `json:"access_type,omitempty"`
AccessProviderId string `json:"access_provider_id,omitempty"`
AccessProviderRoleId string `json:"access_provider_role_id,omitempty"`
AccessProviderRoleName string `json:"access_provider_role_name,omitempty"`
AccessProviderRoleActions []string `json:"access_provider_role_actions,omitempty"`
Roles []roles.MemberRoleActions `json:"roles,omitempty"`
}
// EventEncode converts a Rule struct to map[string]any at event producer.
+44 -27
View File
@@ -12,29 +12,38 @@ import (
"github.com/absmach/magistrala/pkg/roles"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/reports"
"github.com/lib/pq"
)
// dbReport represents the database structure for a Report.
type dbReport struct {
ID string `db:"id"`
Name string `db:"name"`
Description string `db:"description"`
DomainID string `db:"domain_id"`
StartDateTime sql.NullTime `db:"start_datetime"`
Due sql.NullTime `db:"due"`
Recurring schedule.Recurring `db:"recurring"`
RecurringPeriod uint `db:"recurring_period"`
Status reports.Status `db:"status"`
CreatedAt time.Time `db:"created_at"`
CreatedBy string `db:"created_by"`
UpdatedAt time.Time `db:"updated_at"`
UpdatedBy string `db:"updated_by"`
Config []byte `db:"config,omitempty"`
Metrics []byte `db:"metrics"`
Email []byte `db:"email"`
ReportTemplate reports.ReportTemplate `db:"report_template"`
MemberID string `db:"member_id,omitempty"`
Roles json.RawMessage `db:"roles,omitempty"`
ID string `db:"id"`
Name string `db:"name"`
Description string `db:"description"`
DomainID string `db:"domain_id"`
StartDateTime sql.NullTime `db:"start_datetime"`
Due sql.NullTime `db:"due"`
Recurring schedule.Recurring `db:"recurring"`
RecurringPeriod uint `db:"recurring_period"`
Status reports.Status `db:"status"`
CreatedAt time.Time `db:"created_at"`
CreatedBy string `db:"created_by"`
UpdatedAt time.Time `db:"updated_at"`
UpdatedBy string `db:"updated_by"`
Config []byte `db:"config,omitempty"`
Metrics []byte `db:"metrics"`
Email []byte `db:"email"`
ReportTemplate reports.ReportTemplate `db:"report_template"`
MemberID string `db:"member_id,omitempty"`
RoleID string `db:"role_id,omitempty"`
RoleName string `db:"role_name,omitempty"`
Actions pq.StringArray `db:"actions,omitempty"`
AccessType string `db:"access_type,omitempty"`
AccessProviderId string `db:"access_provider_id,omitempty"`
AccessProviderRoleId string `db:"access_provider_role_id,omitempty"`
AccessProviderRoleName string `db:"access_provider_role_name,omitempty"`
AccessProviderRoleActions pq.StringArray `db:"access_provider_role_actions,omitempty"`
Roles json.RawMessage `db:"roles,omitempty"`
}
func reportToDb(r reports.ReportConfig) (dbReport, error) {
@@ -136,14 +145,22 @@ func dbToReport(dto dbReport) (reports.ReportConfig, error) {
Recurring: dto.Recurring,
RecurringPeriod: dto.RecurringPeriod,
},
Email: &email,
Status: dto.Status,
CreatedAt: dto.CreatedAt,
CreatedBy: dto.CreatedBy,
UpdatedAt: dto.UpdatedAt,
UpdatedBy: dto.UpdatedBy,
ReportTemplate: dto.ReportTemplate,
Roles: roles,
Email: &email,
Status: dto.Status,
CreatedAt: dto.CreatedAt,
CreatedBy: dto.CreatedBy,
UpdatedAt: dto.UpdatedAt,
UpdatedBy: dto.UpdatedBy,
ReportTemplate: dto.ReportTemplate,
RoleID: dto.RoleID,
RoleName: dto.RoleName,
Actions: []string(dto.Actions),
AccessType: dto.AccessType,
AccessProviderId: dto.AccessProviderId,
AccessProviderRoleId: dto.AccessProviderRoleId,
AccessProviderRoleName: dto.AccessProviderRoleName,
AccessProviderRoleActions: []string(dto.AccessProviderRoleActions),
Roles: roles,
}
return rpt, nil
+54 -27
View File
@@ -452,38 +452,65 @@ func (repo *PostgresRepository) ListAllReportsConfig(ctx context.Context, pm rep
}
func (repo *PostgresRepository) ListUserReportsConfig(ctx context.Context, userID string, pm reports.PageMeta) (reports.ReportConfigPage, error) {
clauses := []string{
`(
EXISTS (
SELECT 1
FROM reports_roles rr
JOIN reports_role_members rrm ON rrm.role_id = rr.id
WHERE rr.entity_id = rc.id AND rrm.member_id = :user_id
)
OR EXISTS (
SELECT 1
FROM domains_roles dr
JOIN domains_role_members drm ON drm.role_id = dr.id
JOIN domains_role_actions dra ON dra.role_id = dr.id
WHERE dr.entity_id = rc.domain_id
AND drm.member_id = :user_id
AND dra.action LIKE 'report%'
)
)`,
pm.UserID = userID
additionalConditions := pageReportQueryConditions(pm)
additionalWhereClause := ""
if len(additionalConditions) > 0 {
additionalWhereClause = "AND " + strings.Join(additionalConditions, " AND ")
}
clauses = append(clauses, pageReportQueryConditions(pm)...)
orderClause := reportsOrderClause(pm)
pgData := reportsPageData(pm)
pm.UserID = userID
whereClause := fmt.Sprintf("WHERE %s", strings.Join(clauses, " AND "))
innerQ := fmt.Sprintf(`
SELECT DISTINCT rc.id, rc.name, rc.description, rc.domain_id, rc.metrics, rc.email, rc.config,
rc.start_datetime, rc.due, rc.recurring, rc.recurring_period, rc.created_at, rc.created_by, rc.updated_at, rc.updated_by, rc.status
FROM report_config rc
%s
`, whereClause)
WITH direct_reports AS (
SELECT rc.id, rc.name, rc.description, rc.domain_id, rc.metrics, rc.email, rc.config,
rc.start_datetime, rc.due, rc.recurring, rc.recurring_period,
rc.created_at, rc.created_by, rc.updated_at, rc.updated_by, rc.status,
rr.id AS role_id,
rr."name" AS role_name,
array_remove(array_agg(DISTINCT rra."action"), NULL) AS actions,
'direct' AS access_type,
'' AS access_provider_id,
'' AS access_provider_role_id,
'' AS access_provider_role_name,
CAST(array[] AS text[]) AS access_provider_role_actions
FROM reports_role_members rrm
JOIN reports_roles rr ON rr.id = rrm.role_id
JOIN report_config rc ON rc.id = rr.entity_id
LEFT JOIN reports_role_actions rra ON rra.role_id = rrm.role_id
WHERE rrm.member_id = :user_id
%s
GROUP BY rc.id, rr.id, rr."name"
),
domain_reports AS (
SELECT rc.id, rc.name, rc.description, rc.domain_id, rc.metrics, rc.email, rc.config,
rc.start_datetime, rc.due, rc.recurring, rc.recurring_period,
rc.created_at, rc.created_by, rc.updated_at, rc.updated_by, rc.status,
'' AS role_id,
'' AS role_name,
CAST(array[] AS text[]) AS actions,
'domain' AS access_type,
d.id AS access_provider_id,
dr.id AS access_provider_role_id,
dr."name" AS access_provider_role_name,
array_agg(DISTINCT dra."action") AS access_provider_role_actions
FROM domains_role_members drm
JOIN domains_role_actions dra ON dra.role_id = drm.role_id
JOIN domains_roles dr ON dr.id = drm.role_id
JOIN domains d ON d.id = dr.entity_id
JOIN report_config rc ON rc.domain_id = d.id
WHERE drm.member_id = :user_id
AND dra.action LIKE 'report%%'
AND NOT EXISTS (SELECT 1 FROM direct_reports tmp WHERE tmp.id = rc.id)
%s
GROUP BY rc.id, d.id, dr.id, dr."name"
)
SELECT * FROM direct_reports
UNION ALL
SELECT * FROM domain_reports
`, additionalWhereClause, additionalWhereClause)
q := fmt.Sprintf(`
SELECT * FROM (%s) AS sub %s %s;
+24 -15
View File
@@ -153,21 +153,30 @@ func (rm ReqMetric) Validate() error {
}
type ReportConfig struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
DomainID string `json:"domain_id"`
Schedule schedule.Schedule `json:"schedule,omitempty"`
Config *MetricConfig `json:"config,omitempty"`
Email *EmailSetting `json:"email,omitempty"`
Metrics []ReqMetric `json:"metrics,omitempty"`
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
UpdatedBy string `json:"updated_by,omitempty"`
Roles []roles.MemberRoleActions `json:"roles,omitempty"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
DomainID string `json:"domain_id"`
Schedule schedule.Schedule `json:"schedule,omitempty"`
Config *MetricConfig `json:"config,omitempty"`
Email *EmailSetting `json:"email,omitempty"`
Metrics []ReqMetric `json:"metrics,omitempty"`
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
UpdatedBy string `json:"updated_by,omitempty"`
// Extended
RoleID string `json:"role_id,omitempty"`
RoleName string `json:"role_name,omitempty"`
Actions []string `json:"actions,omitempty"`
AccessType string `json:"access_type,omitempty"`
AccessProviderId string `json:"access_provider_id,omitempty"`
AccessProviderRoleId string `json:"access_provider_role_id,omitempty"`
AccessProviderRoleName string `json:"access_provider_role_name,omitempty"`
AccessProviderRoleActions []string `json:"access_provider_role_actions,omitempty"`
Roles []roles.MemberRoleActions `json:"roles,omitempty"`
}
type ReportConfigPage struct {