MG-136 - Move reports to a separate service (#152)

* initial implementation

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* initial implementation

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add remove report from nats handler

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add license header

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* remove unused code

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update docker compose

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* move runinfo to pkg

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update report handler

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update reports handler

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update handler in reports

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update repo method from time to due

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix validation methods

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* address comments

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update reports port to 9017

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update nginx to support reports

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix reports location in nginx

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* update env variable

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
Steve Munene
2025-06-16 13:10:50 +03:00
committed by GitHub
parent e57db52b34
commit dcd5ff914d
52 changed files with 5789 additions and 4152 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
MG_DOCKER_IMAGE_NAME_PREFIX ?= ghcr.io/absmach/magistrala
BUILD_DIR = build
SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-writer timescale-reader cli alarms
SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-writer timescale-reader cli alarms reports
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
+556
View File
@@ -0,0 +1,556 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
openapi: 3.0.1
info:
title: Magistrala Reports Service API
description: |
HTTP API for managing reports service.
version: 0.15.1
servers:
- url: http://localhost:9017
tags:
- name: reports
description: Operations related to report configurations and generation
paths:
/{domainID}/reports:
post:
operationId: generateReport
summary: Generate a report
description: Generates a report based on the provided configuration or an existing config. The action determines the response format.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateReportRequest'
responses:
'200':
description: Report generated successfully (content varies by action)
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateReportResponse'
application/octet-stream:
schema:
type: string
format: binary
'400':
description: Invalid request parameters
'401':
description: Missing or invalid access token
'500':
$ref: '#/components/responses/ServiceError'
/{domainID}/reports/configs:
post:
operationId: addReportConfig
summary: Create a report configuration
description: Creates a new report configuration.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AddReportConfigRequest'
responses:
'201':
description: Report configuration created
headers:
Location:
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/ReportConfig'
'400':
description: Invalid request body
'401':
description: Missing or invalid access token
'500':
$ref: '#/components/responses/ServiceError'
get:
operationId: listReportConfigs
summary: List report configurations
description: Retrieves a paginated list of report configurations.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/Offset'
- $ref: '#/components/parameters/Limit'
security:
- bearerAuth: []
responses:
'200':
description: List of report configurations
content:
application/json:
schema:
$ref: '#/components/schemas/ListReportsConfigResponse'
'400':
description: Invalid query parameters
'401':
description: Missing or invalid access token
'500':
$ref: '#/components/responses/ServiceError'
/{domainID}/reports/configs/{reportID}:
get:
operationId: viewReportConfig
summary: View a report configuration
description: Retrieves details of a specific report configuration.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/ReportID'
security:
- bearerAuth: []
responses:
'200':
description: Report configuration details
content:
application/json:
schema:
$ref: '#/components/schemas/ReportConfig'
'404':
description: Report configuration not found
'401':
description: Missing or invalid access token
'500':
$ref: '#/components/responses/ServiceError'
patch:
operationId: updateReportConfig
summary: Update a report configuration
description: Updates specified fields of a report configuration.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/ReportID'
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateReportConfigRequest'
responses:
'200':
description: Report configuration updated
content:
application/json:
schema:
$ref: '#/components/schemas/ReportConfig'
'400':
description: Invalid request body
'401':
description: Missing or invalid access token
'404':
description: Report configuration not found
'500':
$ref: '#/components/responses/ServiceError'
delete:
operationId: deleteReportConfig
summary: Delete a report configuration
description: Permanently deletes a report configuration.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/ReportID'
security:
- bearerAuth: []
responses:
'204':
description: Report configuration deleted
'401':
description: Missing or invalid access token
'404':
description: Report configuration not found
'500':
$ref: '#/components/responses/ServiceError'
/{domainID}/reports/configs/{reportID}/schedule:
patch:
operationId: updateReportSchedule
summary: Update report schedule
description: Updates the schedule of a report configuration.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/ReportID'
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Schedule'
responses:
'200':
description: Schedule updated
content:
application/json:
schema:
$ref: '#/components/schemas/ReportConfig'
'400':
description: Invalid schedule
'401':
description: Missing or invalid access token
'404':
description: Report configuration not found
'500':
$ref: '#/components/responses/ServiceError'
/{domainID}/reports/configs/{reportID}/enable:
post:
operationId: enableReportConfig
summary: Enable a report configuration
description: Enables a report configuration to generate scheduled reports.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/ReportID'
security:
- bearerAuth: []
responses:
'200':
description: Report configuration enabled
content:
application/json:
schema:
$ref: '#/components/schemas/ReportConfig'
'401':
description: Missing or invalid access token
'404':
description: Report configuration not found
'500':
$ref: '#/components/responses/ServiceError'
/{domainID}/reports/configs/{reportID}/disable:
post:
operationId: disableReportConfig
summary: Disable a report configuration
description: Disables a report configuration, stopping scheduled reports.
tags:
- reports
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/ReportID'
security:
- bearerAuth: []
responses:
'200':
description: Report configuration disabled
content:
application/json:
schema:
$ref: '#/components/schemas/ReportConfig'
'401':
description: Missing or invalid access token
'404':
description: Report configuration not found
'500':
$ref: '#/components/responses/ServiceError'
/health:
get:
summary: Service health check
tags:
- health
responses:
'200':
$ref: '#/components/responses/HealthRes'
components:
schemas:
ReportConfig:
type: object
properties:
id:
type: string
readOnly: true
name:
type: string
description:
type: string
domain_id:
type: string
readOnly: true
schedule:
$ref: '#/components/schemas/Schedule'
config:
$ref: '#/components/schemas/MetricConfig'
email:
$ref: '#/components/schemas/EmailSetting'
metrics:
type: array
items:
$ref: '#/components/schemas/ReqMetric'
status:
$ref: '#/components/schemas/Status'
created_at:
type: string
format: date-time
readOnly: true
created_by:
type: string
readOnly: true
updated_at:
type: string
format: date-time
readOnly: true
updated_by:
type: string
readOnly: true
required:
- name
- metrics
- config
Schedule:
type: object
properties:
recurring:
type: string
enum: [None, Daily, Weekly, Monthly]
recurring_period:
type: integer
minimum: 1
start_time:
type: string
format: date-time
next_run:
type: string
format: date-time
readOnly: true
MetricConfig:
type: object
properties:
title:
type: string
maxLength: 100
format:
type: string
enum: [pdf, csv, html]
aggregation:
$ref: '#/components/schemas/AggConfig'
AggConfig:
type: object
properties:
window:
type: string
function:
type: string
enum: [sum, average, max, min]
EmailSetting:
type: object
properties:
recipients:
type: array
items:
type: string
format: email
subject:
type: string
body_template:
type: string
required:
- recipients
- subject
ReqMetric:
type: object
properties:
name:
type: string
type:
type: string
enum: [gauge, counter, histogram]
parameters:
type: object
required:
- name
- type
Status:
type: string
enum: [enabled, disabled]
GenerateReportRequest:
type: object
properties:
action:
type: string
enum: [view, download, email]
config_id:
type: string
name:
type: string
description:
type: string
schedule:
$ref: '#/components/schemas/Schedule'
config:
$ref: '#/components/schemas/MetricConfig'
email:
$ref: '#/components/schemas/EmailSetting'
metrics:
type: array
items:
$ref: '#/components/schemas/ReqMetric'
required:
- action
GenerateReportResponse:
type: object
properties:
total:
type: integer
from:
type: string
format: date-time
to:
type: string
format: date-time
aggregation:
$ref: '#/components/schemas/AggConfig'
reports:
type: array
items:
$ref: '#/components/schemas/Report'
Report:
type: object
properties:
timestamp:
type: string
format: date-time
value:
type: number
metric_name:
type: string
AddReportConfigRequest:
type: object
properties:
name:
type: string
description:
type: string
schedule:
$ref: '#/components/schemas/Schedule'
config:
$ref: '#/components/schemas/MetricConfig'
email:
$ref: '#/components/schemas/EmailSetting'
metrics:
type: array
items:
$ref: '#/components/schemas/ReqMetric'
status:
$ref: '#/components/schemas/Status'
required:
- name
- metrics
- config
UpdateReportConfigRequest:
type: object
properties:
name:
type: string
description:
type: string
schedule:
$ref: '#/components/schemas/Schedule'
config:
$ref: '#/components/schemas/MetricConfig'
email:
$ref: '#/components/schemas/EmailSetting'
metrics:
type: array
items:
$ref: '#/components/schemas/ReqMetric'
status:
$ref: '#/components/schemas/Status'
ListReportsConfigResponse:
type: object
properties:
total:
type: integer
offset:
type: integer
limit:
type: integer
report_configs:
type: array
items:
$ref: '#/components/schemas/ReportConfig'
parameters:
DomainID:
name: domainID
in: path
required: true
schema:
type: string
ReportID:
name: reportID
in: path
required: true
schema:
type: string
Offset:
name: offset
in: query
schema:
type: integer
default: 0
minimum: 0
Limit:
name: limit
in: query
schema:
type: integer
default: 10
minimum: 1
maximum: 100
responses:
ServiceError:
description: Unexpected server error
HealthRes:
description: Service health status
content:
application/json:
schema:
type: object
properties:
status:
type: string
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
+6 -4
View File
@@ -18,9 +18,11 @@ import (
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
"github.com/absmach/magistrala/consumers/writers/brokers"
"github.com/absmach/magistrala/internal/email"
"github.com/absmach/magistrala/pkg/emailer"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/ticker"
"github.com/absmach/magistrala/re"
httpapi "github.com/absmach/magistrala/re/api"
"github.com/absmach/magistrala/re/emailer"
"github.com/absmach/magistrala/re/middleware"
repg "github.com/absmach/magistrala/re/postgres"
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
@@ -190,7 +192,7 @@ func main() {
}
defer authnClient.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
runInfo := make(chan re.RunInfo, channBuffer)
runInfo := make(chan pkglog.RunInfo, channBuffer)
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
@@ -285,7 +287,7 @@ func main() {
}
}
func newService(db pgclient.Database, runInfo chan re.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient) (re.Service, error) {
func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient) (re.Service, error) {
repo := repg.NewRepository(db)
idp := uuid.New()
@@ -294,7 +296,7 @@ func newService(db pgclient.Database, runInfo chan re.RunInfo, rePubSub messagin
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
}
csvc := re.NewService(repo, runInfo, idp, rePubSub, writersPub, alarmsPub, re.NewTicker(time.Second*30), emailerClient, readersClient)
csvc := re.NewService(repo, runInfo, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient)
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
if err != nil {
return nil, err
+258
View File
@@ -0,0 +1,258 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains rule engine main function to start the service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
"time"
chclient "github.com/absmach/callhome/pkg/client"
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
"github.com/absmach/magistrala/internal/email"
"github.com/absmach/magistrala/pkg/emailer"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/ticker"
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
"github.com/absmach/magistrala/reports"
httpapi "github.com/absmach/magistrala/reports/api"
"github.com/absmach/magistrala/reports/middleware"
repg "github.com/absmach/magistrala/reports/postgres"
"github.com/absmach/supermq"
smqlog "github.com/absmach/supermq/logger"
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
mgauthz "github.com/absmach/supermq/pkg/authz"
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
pgclient "github.com/absmach/supermq/pkg/postgres"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
"github.com/absmach/supermq/pkg/uuid"
"github.com/caarlos0/env/v11"
"github.com/go-chi/chi/v5"
"golang.org/x/sync/errgroup"
)
const (
svcName = "reports"
envPrefixDB = "MG_REPORTS_DB_"
envPrefixHTTP = "MG_REPORTS_HTTP_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
defDB = "repo"
defSvcHTTPPort = "9017"
envPrefixGrpc = "MG_TIMESCALE_READER_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
)
// We use a buffered channel to prevent blocking, as logging is an expensive operation.
const channBuffer = 256
type config struct {
LogLevel string `env:"MG_REPORTS_LOG_LEVEL" envDefault:"info"`
InstanceID string `env:"MG_REPORTS_INSTANCE_ID" envDefault:""`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
var logger *slog.Logger
logger, err := smqlog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer smqlog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
ec := email.Config{}
if err := env.Parse(&ec); err != nil {
logger.Error(fmt.Sprintf("failed to load email configuration : %s", err))
exitCode = 1
return
}
dbConfig := pgclient.Config{Name: defDB}
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
db, err := pgclient.Setup(dbConfig, *repg.Migration())
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer db.Close()
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
grpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err))
exitCode = 1
return
}
authn, authnClient, err := authnsvc.NewAuthentication(ctx, grpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer authnClient.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
domsGrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
exitCode = 1
return
}
domAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer domainsHandler.Close()
authz, authzClient, err := authzsvc.NewAuthorization(ctx, grpcCfg, domAuthz)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer authzClient.Close()
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
database := pgclient.NewDatabase(db, dbConfig, tracer)
regrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&regrpcCfg, env.Options{Prefix: envPrefixGrpc}); err != nil {
logger.Error(fmt.Sprintf("failed to load clients gRPC client configuration : %s", err))
exitCode = 1
return
}
client, err := grpcclient.NewHandler(regrpcCfg)
if err != nil {
exitCode = 1
return
}
defer client.Close()
readersClient := grpcClient.NewReadersClient(client.Connection(), regrpcCfg.Timeout)
logger.Info("Readers gRPC client successfully connected to readers gRPC server " + client.Secure())
runInfo := make(chan pkglog.RunInfo, channBuffer)
svc, err := newService(database, runInfo, authz, ec, logger, readersClient)
if err != nil {
logger.Error(fmt.Sprintf("failed to create services: %s", err))
exitCode = 1
return
}
go func() {
for info := range runInfo {
logger.LogAttrs(context.Background(), info.Level, info.Message, info.Details...)
}
}()
mux := chi.NewRouter()
httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, mux, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, supermq.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return svc.StartScheduler(ctx)
})
g.Go(func() error {
return httpSvc.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSvc)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
}
}
func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient) (reports.Service, error) {
repo := repg.NewRepository(db)
idp := uuid.New()
emailerClient, err := emailer.New(&ec)
if err != nil {
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
}
csvc := reports.NewService(repo, runInfo, idp, ticker.NewTicker(time.Second*30), emailerClient, readersClient)
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
if err != nil {
return nil, err
}
csvc = middleware.LoggingMiddleware(csvc, logger)
return csvc, nil
}
+18
View File
@@ -136,6 +136,24 @@ MG_ALARMS_DB_SSL_KEY=
MG_ALARMS_DB_SSL_ROOT_CERT=
MG_ALARMS_INSTANCE_ID=
### REPORTS
MG_REPORTS_LOG_LEVEL=debug
MG_REPORTS_HTTP_HOST=reports
MG_REPORTS_HTTP_PORT=9017
MG_REPORTS_HTTP_SERVER_CERT=
MG_REPORTS_HTTP_SERVER_KEY=
MG_REPORTS_DB_HOST=reports-db
MG_REPORTS_DB_PORT=5432
MG_REPORTS_DB_USER=magistrala
MG_REPORTS_DB_PASS=magistrala
MG_REPORTS_DB_NAME=reports
MG_REPORTS_DB_SSL_MODE=disable
MG_REPORTS_DB_SSL_CERT=
MG_REPORTS_DB_SSL_KEY=
MG_REPORTS_DB_SSL_ROOT_CERT=
MG_REPORTS_INSTANCE_ID=
MG_REPORTS_EMAIL_TEMPLATE=reports.tmpl
### Certs
SMQ_ADDONS_CERTS_PATH_PREFIX=./
+91
View File
@@ -23,6 +23,7 @@ volumes:
magistrala-re-db-volume:
magistrala-auth-redis-volume:
magistrala-alarms-db-volume:
magistrala-reports-db-volume:
services:
ui:
@@ -353,3 +354,93 @@ services:
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
reports-db:
image: postgres:16.2-alpine
container_name: magistrala-reports-db
restart: on-failure
command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}"
environment:
POSTGRES_USER: ${MG_REPORTS_DB_USER}
POSTGRES_PASSWORD: ${MG_REPORTS_DB_PASS}
POSTGRES_DB: ${MG_REPORTS_DB_NAME}
ports:
- 6020:5432
networks:
- magistrala-base-net
volumes:
- magistrala-reports-db-volume:/var/lib/postgresql/data
reports:
image: ghcr.io/absmach/magistrala/reports:${MG_RELEASE_TAG}
container_name: magistrala-reports
depends_on:
- reports-db
restart: on-failure
environment:
MG_REPORTS_LOG_LEVEL: ${MG_REPORTS_LOG_LEVEL}
MG_REPORTS_HTTP_PORT: ${MG_REPORTS_HTTP_PORT}
MG_REPORTS_HTTP_HOST: ${MG_REPORTS_HTTP_HOST}
MG_REPORTS_HTTP_SERVER_CERT: ${MG_REPORTS_HTTP_SERVER_CERT}
MG_REPORTS_HTTP_SERVER_KEY: ${MG_REPORTS_HTTP_SERVER_KEY}
MG_REPORTS_DB_HOST: ${MG_REPORTS_DB_HOST}
MG_REPORTS_DB_PORT: ${MG_REPORTS_DB_PORT}
MG_REPORTS_DB_USER: ${MG_REPORTS_DB_USER}
MG_REPORTS_DB_PASS: ${MG_REPORTS_DB_PASS}
MG_REPORTS_DB_NAME: ${MG_REPORTS_DB_NAME}
MG_REPORTS_DB_SSL_MODE: ${MG_REPORTS_DB_SSL_MODE}
MG_REPORTS_DB_SSL_CERT: ${MG_REPORTS_DB_SSL_CERT}
MG_REPORTS_DB_SSL_KEY: ${MG_REPORTS_DB_SSL_KEY}
MG_REPORTS_DB_SSL_ROOT_CERT: ${MG_REPORTS_DB_SSL_ROOT_CERT}
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL}
SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT}
SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
MG_REPORTS_INSTANCE_ID: ${MG_RE_INSTANCE_ID}
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
MG_EMAIL_USERNAME: ${MG_EMAIL_USERNAME}
MG_EMAIL_PASSWORD: ${MG_EMAIL_PASSWORD}
MG_EMAIL_FROM_ADDRESS: ${MG_EMAIL_FROM_ADDRESS}
MG_EMAIL_FROM_NAME: ${MG_EMAIL_FROM_NAME}
MG_EMAIL_TEMPLATE: ${MG_EMAIL_TEMPLATE}
MG_TIMESCALE_READER_GRPC_URL: ${MG_TIMESCALE_READER_GRPC_URL}
MG_TIMESCALE_READER_GRPC_TIMEOUT: ${MG_TIMESCALE_READER_GRPC_TIMEOUT}
MG_TIMESCALE_READER_GRPC_CLIENT_CERT: ${MG_TIMESCALE_READER_GRPC_CLIENT_CERT}
MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS: ${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS}
MG_TIMESCALE_READER_GRPC_CLIENT_KEY: ${MG_TIMESCALE_READER_GRPC_CLIENT_KEY}
SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL}
SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT}
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
ports:
- ${MG_REPORTS_HTTP_PORT}:${MG_REPORTS_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./templates/${MG_REPORTS_EMAIL_TEMPLATE}:/email.tmpl
# Auth gRPC client certificates
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
+1
View File
@@ -23,6 +23,7 @@ envsubst '
${SMQ_NGINX_MQTTS_PORT}
${MG_RE_HTTP_PORT}
${MG_ALARMS_HTTP_PORT}
${MG_REPORTS_HTTP_PORT}
${SMQ_WS_ADAPTER_HTTP_PORT}' </etc/nginx/nginx.conf.template >/etc/nginx/nginx.conf
exec nginx -g "daemon off;"
+8 -1
View File
@@ -100,13 +100,20 @@ http {
proxy_pass http://re:${MG_RE_HTTP_PORT};
}
# Proxy pass to rule engine service
# Proxy pass to alarm service
location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(alarms)" {
include snippets/proxy-headers.conf;
add_header Access-Control-Expose-Headers Location;
proxy_pass http://alarms:${MG_ALARMS_HTTP_PORT};
}
# Proxy pass to reports service
location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(reports)" {
include snippets/proxy-headers.conf;
add_header Access-Control-Expose-Headers Location;
proxy_pass http://reports:${MG_REPORTS_HTTP_PORT};
}
location /health {
include snippets/proxy-headers.conf;
proxy_pass http://clients:${SMQ_CLIENTS_HTTP_PORT};
+8 -1
View File
@@ -109,13 +109,20 @@ http {
proxy_pass http://re:${MG_RE_HTTP_PORT};
}
# Proxy pass to rule engine service
# Proxy pass to alarms service
location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(alarms)" {
include snippets/proxy-headers.conf;
add_header Access-Control-Expose-Headers Location;
proxy_pass http://alarms:${MG_ALARMS_HTTP_PORT};
}
# Proxy pass to reports service
location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(reports)" {
include snippets/proxy-headers.conf;
add_header Access-Control-Expose-Headers Location;
proxy_pass http://reports:${MG_REPORTS_HTTP_PORT};
}
location /health {
include snippets/proxy-headers.conf;
proxy_pass http://clients:${SMQ_CLIENTS_HTTP_PORT};
+3
View File
@@ -0,0 +1,3 @@
{{.Header}}
{{.Content}}
{{.Footer}}
@@ -5,16 +5,20 @@ package emailer
import (
"github.com/absmach/magistrala/internal/email"
"github.com/absmach/magistrala/re"
)
var _ re.Emailer = (*emailer)(nil)
var _ Emailer = (*emailer)(nil)
type Emailer interface {
// SendEmailNotification sends an email to the recipients based on a trigger.
SendEmailNotification(to []string, from, subject, header, user, content, footer string, attachments map[string][]byte) error
}
type emailer struct {
agent *email.Agent
}
func New(a *email.Config) (re.Emailer, error) {
func New(a *email.Config) (Emailer, error) {
e, err := email.New(a)
return &emailer{agent: e}, err
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package logger
import "log/slog"
type RunInfo struct {
Level slog.Level
Details []slog.Attr
Message string
}
+4 -1
View File
@@ -1,13 +1,16 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package re
package schedule
import (
"encoding/json"
"errors"
"time"
)
var ErrInvalidRecurringType = errors.New("invalid recurring type")
// Type can be daily, weekly or monthly.
type Recurring uint
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package re
package ticker
import "time"
-224
View File
@@ -177,227 +177,3 @@ func disableRuleEndpoint(s re.Service) endpoint.Endpoint {
return updateRuleStatusRes{Rule: rule}, err
}
}
func generateReportEndpoint(svc re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(generateReportReq)
if err := req.validate(); err != nil {
return generateReportResp{}, err
}
res, err := svc.GenerateReport(ctx, session, re.ReportConfig{
Name: req.Name,
DomainID: req.DomainID,
Config: req.Config,
Metrics: req.Metrics,
Email: req.Email,
}, req.action)
if err != nil {
return generateReportResp{}, err
}
switch req.action {
case re.DownloadReport:
return downloadReportResp{
File: res.File,
}, nil
case re.EmailReport:
return emailReportResp{}, nil
default:
return generateReportResp{
Total: res.Total,
From: res.From,
To: res.To,
Aggregation: res.Aggregation,
Reports: res.Reports,
}, nil
}
}
}
func listReportsConfigEndpoint(svc re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(listReportsConfigReq)
if err := req.validate(); err != nil {
return listReportsConfigRes{}, err
}
page, err := svc.ListReportsConfig(ctx, session, req.PageMeta)
if err != nil {
return listReportsConfigRes{}, err
}
return listReportsConfigRes{
pageRes: pageRes{
Limit: page.Limit,
Offset: page.Offset,
Total: page.Total,
},
ReportConfigs: page.ReportConfigs,
}, nil
}
}
func deleteReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(deleteReportConfigReq)
if err := req.validate(); err != nil {
return deleteReportConfigRes{}, err
}
err := svc.RemoveReportConfig(ctx, session, req.ID)
if err != nil {
return deleteReportConfigRes{false}, err
}
return deleteReportConfigRes{true}, nil
}
}
func updateReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(updateReportConfigReq)
if err := req.validate(); err != nil {
return updateReportConfigRes{}, err
}
cfg, err := svc.UpdateReportConfig(ctx, session, req.ReportConfig)
if err != nil {
return updateReportConfigRes{}, err
}
return updateReportConfigRes{ReportConfig: cfg}, nil
}
}
func updateReportScheduleEndpoint(s re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(updateReportScheduleReq)
if err := req.validate(); err != nil {
return updateReportConfigRes{}, err
}
rpt := re.ReportConfig{
ID: req.id,
Schedule: req.Schedule,
}
updatedReport, err := s.UpdateReportSchedule(ctx, session, rpt)
if err != nil {
return updateReportConfigRes{}, err
}
return updateReportConfigRes{ReportConfig: updatedReport}, nil
}
}
func viewReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(viewReportConfigReq)
if err := req.validate(); err != nil {
return viewReportConfigRes{}, err
}
cfg, err := svc.ViewReportConfig(ctx, session, req.ID)
if err != nil {
return viewReportConfigRes{}, err
}
return viewReportConfigRes{ReportConfig: cfg}, nil
}
}
func addReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(addReportConfigReq)
if err := req.validate(); err != nil {
return addReportConfigRes{}, err
}
cfg, err := svc.AddReportConfig(ctx, session, req.ReportConfig)
if err != nil {
return addReportConfigRes{}, err
}
return addReportConfigRes{
ReportConfig: cfg,
created: true,
}, nil
}
}
func enableReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(updateReportStatusReq)
if err := req.validate(); err != nil {
return updateReportConfigRes{}, err
}
cfg, err := svc.EnableReportConfig(ctx, session, req.id)
if err != nil {
return updateReportConfigRes{}, err
}
return updateReportConfigRes{ReportConfig: cfg}, nil
}
}
func disableReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(updateReportStatusReq)
if err := req.validate(); err != nil {
return updateReportConfigRes{}, err
}
cfg, err := svc.DisableReportConfig(ctx, session, req.id)
if err != nil {
return updateReportConfigRes{}, err
}
return updateReportConfigRes{ReportConfig: cfg}, nil
}
}
+4 -711
View File
@@ -15,7 +15,7 @@ import (
"github.com/0x6flab/namegenerator"
"github.com/absmach/magistrala/internal/testsutil"
"github.com/absmach/magistrala/pkg/errors"
pkgSch "github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/re"
"github.com/absmach/magistrala/re/api"
"github.com/absmach/magistrala/re/mocks"
@@ -24,6 +24,7 @@ import (
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
@@ -40,9 +41,9 @@ var (
validToken = "valid"
invalidToken = "invalid"
now = time.Now().UTC().Truncate(time.Minute)
schedule = re.Schedule{
schedule = pkgSch.Schedule{
StartDateTime: now.Add(-1 * time.Hour),
Recurring: re.Daily,
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
}
@@ -55,31 +56,6 @@ var (
"name": "test",
},
}
reportConfig = re.ReportConfig{
ID: validID,
Name: namegen.Generate(),
DomainID: domainID,
Schedule: schedule,
Status: re.EnabledStatus,
Metrics: []re.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
},
Config: &re.MetricConfig{
From: "now()-1h",
To: "now()",
Title: title,
Aggregation: re.AggConfig{AggType: re.AggregationAVG, Interval: "1h"},
},
Email: &re.EmailSetting{
To: []string{"test@example.com"},
Subject: "Test Report",
},
}
title = "test_title"
)
type testRequest struct {
@@ -991,686 +967,3 @@ type respBody struct {
ID string `json:"id"`
Status re.Status `json:"status"`
}
func TestAddReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newRuleEngineServer()
defer ts.Close()
cases := []struct {
desc string
cfg re.ReportConfig
domainID string
token string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcRes re.ReportConfig
svcErr error
err error
}{
{
desc: "add report config successfully",
cfg: reportConfig,
token: validToken,
contentType: contentType,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
status: http.StatusCreated,
svcRes: reportConfig,
},
{
desc: "add report config with invalid token",
cfg: reportConfig,
token: invalidToken,
authnRes: smqauthn.Session{},
domainID: domainID,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "add report config with empty token",
token: "",
authnRes: smqauthn.Session{},
domainID: domainID,
cfg: reportConfig,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "add report config with empty domainID",
token: validToken,
cfg: reportConfig,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "add report config with invalid content type",
token: validToken,
domainID: domainID,
cfg: reportConfig,
contentType: "application/xml",
status: http.StatusUnsupportedMediaType,
err: apiutil.ErrUnsupportedContentType,
},
{
desc: "add report config with service error",
token: validToken,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
cfg: reportConfig,
contentType: contentType,
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
data := toJSON(tc.cfg)
req := testRequest{
client: ts.Client(),
method: http.MethodPost,
url: fmt.Sprintf("%s/%s/reports/configs", ts.URL, tc.domainID),
contentType: tc.contentType,
token: tc.token,
body: strings.NewReader(data),
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("AddReportConfig", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcRes, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newRuleEngineServer()
defer ts.Close()
cases := []struct {
desc string
id string
domainID string
token string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcRes re.ReportConfig
svcErr error
err error
}{
{
desc: "view report config successfully",
id: validID,
token: validToken,
contentType: contentType,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
status: http.StatusOK,
svcRes: reportConfig,
},
{
desc: "view report config with invalid token",
id: validID,
token: invalidToken,
authnRes: smqauthn.Session{},
domainID: domainID,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "view report config with empty token",
token: "",
authnRes: smqauthn.Session{},
domainID: domainID,
id: validID,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "view report config with empty domainID",
token: validToken,
id: validID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "view report config with service error",
token: validToken,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
id: validID,
contentType: contentType,
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodGet,
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
contentType: tc.contentType,
token: tc.token,
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("ViewReportConfig", mock.Anything, tc.authnRes, tc.id).Return(tc.svcRes, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestListReportsConfigEndpoint(t *testing.T) {
ts, svc, authn := newRuleEngineServer()
defer ts.Close()
cases := []struct {
desc string
query string
domainID string
token string
session smqauthn.Session
listReportsResponse re.ReportConfigPage
status int
authnErr error
err error
}{
{
desc: "list reports config successfully",
domainID: domainID,
token: validToken,
status: http.StatusOK,
listReportsResponse: re.ReportConfigPage{
ReportConfigs: []re.ReportConfig{reportConfig},
PageMeta: re.PageMeta{Total: 1},
},
err: nil,
},
{
desc: "list reports config with empty token",
domainID: domainID,
token: "",
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "list reports config with invalid token",
domainID: domainID,
token: invalidToken,
status: http.StatusUnauthorized,
authnErr: svcerr.ErrAuthentication,
err: svcerr.ErrAuthentication,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodGet,
url: ts.URL + "/" + tc.domainID + "/reports/configs?" + tc.query,
contentType: contentType,
token: tc.token,
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("ListReportsConfig", mock.Anything, tc.session, mock.Anything).Return(tc.listReportsResponse, tc.err)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var bodyRes respBody
err = json.NewDecoder(res.Body).Decode(&bodyRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if bodyRes.Err != "" || bodyRes.Message != "" {
err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newRuleEngineServer()
defer ts.Close()
cases := []struct {
desc string
token string
id string
domainID string
updateReq re.ReportConfig
contentType string
session smqauthn.Session
svcResp re.ReportConfig
svcErr error
status int
authnErr error
err error
}{
{
desc: "update report config successfully",
token: validToken,
domainID: domainID,
id: validID,
updateReq: reportConfig,
contentType: contentType,
svcResp: reportConfig,
status: http.StatusOK,
err: nil,
},
{
desc: "update report config with invalid token",
token: invalidToken,
session: smqauthn.Session{},
domainID: domainID,
id: validID,
updateReq: reportConfig,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "update report config with empty token",
token: "",
session: smqauthn.Session{},
domainID: domainID,
id: validID,
updateReq: reportConfig,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "update report config with empty domainID",
token: validToken,
id: validID,
updateReq: reportConfig,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "update report config with invalid content type",
token: validToken,
id: validID,
domainID: domainID,
updateReq: reportConfig,
contentType: "application/xml",
svcResp: reportConfig,
status: http.StatusUnsupportedMediaType,
err: apiutil.ErrUnsupportedContentType,
},
{
desc: "update report config with service error",
token: validToken,
id: validID,
domainID: domainID,
updateReq: reportConfig,
contentType: contentType,
svcResp: re.ReportConfig{},
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
data := toJSON(tc.updateReq)
req := testRequest{
client: ts.Client(),
method: http.MethodPatch,
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
contentType: tc.contentType,
token: tc.token,
body: strings.NewReader(data),
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("UpdateReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcResp, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDeleteReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newRuleEngineServer()
defer ts.Close()
cases := []struct {
desc string
token string
id string
domainID string
session smqauthn.Session
svcErr error
status int
authnErr error
err error
}{
{
desc: "delete report config successfully",
token: validToken,
domainID: domainID,
id: validID,
svcErr: nil,
status: http.StatusNoContent,
err: nil,
},
{
desc: "delete report config with invalid token",
token: invalidToken,
session: smqauthn.Session{},
domainID: domainID,
id: validID,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "delete report config with empty token",
token: "",
session: smqauthn.Session{},
domainID: domainID,
id: validID,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "delete report config with empty domainID",
token: validToken,
id: validID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "delete report config with service error",
token: validToken,
id: validID,
domainID: domainID,
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodDelete,
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
token: tc.token,
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("RemoveReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestEnableReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newRuleEngineServer()
defer ts.Close()
cases := []struct {
desc string
token string
id string
domainID string
session smqauthn.Session
svcResp re.ReportConfig
svcErr error
status int
authnErr error
err error
}{
{
desc: "enable report config successfully",
token: validToken,
domainID: domainID,
id: validID,
svcResp: reportConfig,
svcErr: nil,
status: http.StatusOK,
err: nil,
},
{
desc: "enable report config with invalid token",
token: invalidToken,
session: smqauthn.Session{},
domainID: domainID,
id: validID,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "enable report config with empty token",
token: "",
session: smqauthn.Session{},
domainID: domainID,
id: validID,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "enable report config with empty domainID",
token: validToken,
id: validID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "enable report config with service error",
token: validToken,
id: validID,
domainID: domainID,
svcResp: re.ReportConfig{},
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
{
desc: "enable report config with empty id",
token: validToken,
id: "",
domainID: domainID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodPost,
url: fmt.Sprintf("%s/%s/reports/configs/%s/enable", ts.URL, tc.domainID, tc.id),
token: tc.token,
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("EnableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDisableReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newRuleEngineServer()
defer ts.Close()
cases := []struct {
desc string
token string
id string
domainID string
session smqauthn.Session
svcResp re.ReportConfig
svcErr error
status int
authnErr error
err error
}{
{
desc: "disable report config successfully",
token: validToken,
domainID: domainID,
id: validID,
svcResp: reportConfig,
svcErr: nil,
status: http.StatusOK,
err: nil,
},
{
desc: "disable report config with invalid token",
token: invalidToken,
session: smqauthn.Session{},
domainID: domainID,
id: validID,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "disable report config with empty token",
token: "",
session: smqauthn.Session{},
domainID: domainID,
id: validID,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "disable report config with empty domainID",
token: validToken,
id: validID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "disable report config with service error",
token: validToken,
id: validID,
domainID: domainID,
svcResp: re.ReportConfig{},
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
{
desc: "disable report config with empty id",
token: validToken,
id: "",
domainID: domainID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodPost,
url: fmt.Sprintf("%s/%s/reports/configs/%s/disable", ts.URL, tc.domainID, tc.id),
token: tc.token,
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("DisableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
+2 -156
View File
@@ -4,30 +4,16 @@
package api
import (
"fmt"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/re"
api "github.com/absmach/supermq/api/http"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
)
var (
errInvalidReportAction = errors.New("invalid report action")
errMetricsNotProvided = errors.New("metrics not provided")
errMissingReportConfig = errors.New("missing report config")
errMissingReportEmailConfig = errors.New("missing report email config")
errInvalidRecurringPeriod = errors.New("invalid recurring period")
errTitleSize = errors.New("invalid title size")
)
const (
maxLimitSize = 1000
MaxNameSize = 1024
MaxTitleSize = 37
errInvalidMetric = "invalid metric[%d]: %w"
)
type addRuleReq struct {
@@ -85,7 +71,7 @@ func (req updateRuleReq) validate() error {
type updateRuleScheduleReq struct {
id string
Schedule re.Schedule `json:"schedule,omitempty"`
Schedule schedule.Schedule `json:"schedule,omitempty"`
}
func (req updateRuleScheduleReq) validate() error {
@@ -119,143 +105,3 @@ func (req deleteRuleReq) validate() error {
return nil
}
type updateReportConfigReq struct {
re.ReportConfig `json:",inline"`
}
func (req updateReportConfigReq) validate() error {
if req.ID == "" {
return apiutil.ErrMissingID
}
return validateReportConfig(req.ReportConfig, false, false)
}
type updateReportScheduleReq struct {
id string
Schedule re.Schedule `json:"schedule,omitempty"`
}
func (req updateReportScheduleReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
return nil
}
type addReportConfigReq struct {
re.ReportConfig `json:",inline"`
}
func (req addReportConfigReq) validate() error {
if req.Name == "" {
return apiutil.ErrMissingName
}
return validateReportConfig(req.ReportConfig, false, false)
}
type viewReportConfigReq struct {
ID string `json:"id"`
}
func (req viewReportConfigReq) validate() error {
if req.ID == "" {
return apiutil.ErrMissingID
}
return nil
}
type listReportsConfigReq struct {
re.PageMeta `json:",inline"`
}
func (req listReportsConfigReq) validate() error {
if req.Limit > maxLimitSize {
return svcerr.ErrMalformedEntity
}
return nil
}
type deleteReportConfigReq struct {
ID string `json:"id"`
}
func (req deleteReportConfigReq) validate() error {
if req.ID == "" {
return apiutil.ErrMissingID
}
return nil
}
type generateReportReq struct {
re.ReportConfig
action re.ReportAction
}
func (req generateReportReq) validate() error {
if len(req.Config.Title) > MaxTitleSize {
return errors.Wrap(apiutil.ErrValidation, errTitleSize)
}
switch req.action {
case re.ViewReport, re.DownloadReport:
return validateReportConfig(req.ReportConfig, true, true)
case re.EmailReport:
return validateReportConfig(req.ReportConfig, false, true)
default:
return errors.Wrap(apiutil.ErrValidation, errInvalidReportAction)
}
}
type updateReportStatusReq struct {
id string
}
func (req updateReportStatusReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
return nil
}
func validateReportConfig(req re.ReportConfig, skipEmailValidation bool, skipSchedularValidation bool) error {
if len(req.Metrics) == 0 {
return errors.Wrap(apiutil.ErrValidation, errMetricsNotProvided)
}
for i, metric := range req.Metrics {
if err := metric.Validate(); err != nil {
return errors.Wrap(apiutil.ErrValidation, fmt.Errorf(errInvalidMetric, i+1, err))
}
}
if req.Config == nil {
return errMissingReportConfig
}
if err := req.Config.Validate(); err != nil {
return errors.Wrap(apiutil.ErrValidation, err)
}
if skipEmailValidation {
return nil
}
if req.Email == nil {
return errMissingReportEmailConfig
}
if err := req.Email.Validate(); err != nil {
return errors.Wrap(apiutil.ErrValidation, err)
}
if skipSchedularValidation {
return nil
}
return validateScheduler(req.Schedule)
}
func validateScheduler(sch re.Schedule) error {
if sch.Recurring != re.None && sch.RecurringPeriod < 1 {
return errInvalidRecurringPeriod
}
return nil
}
-147
View File
@@ -6,7 +6,6 @@ package api
import (
"fmt"
"net/http"
"time"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq"
@@ -19,11 +18,6 @@ var (
_ supermq.Response = (*rulesPageRes)(nil)
_ supermq.Response = (*updateRuleRes)(nil)
_ supermq.Response = (*deleteRuleRes)(nil)
_ supermq.Response = (*addReportConfigRes)(nil)
_ supermq.Response = (*viewReportConfigRes)(nil)
_ supermq.Response = (*updateReportConfigRes)(nil)
_ supermq.Response = (*deleteReportConfigRes)(nil)
_ supermq.Response = (*listReportsConfigRes)(nil)
)
type pageRes struct {
@@ -142,144 +136,3 @@ func (res deleteRuleRes) Headers() map[string]string {
func (res deleteRuleRes) Empty() bool {
return true
}
type generateReportResp struct {
Total uint64 `json:"total"`
From time.Time `json:"from,omitempty"`
To time.Time `json:"to,omitempty"`
Aggregation re.AggConfig `json:"aggregation,omitempty"`
Reports []re.Report `json:"reports,omitempty"`
}
func (res generateReportResp) Code() int {
return http.StatusCreated
}
func (res generateReportResp) Headers() map[string]string {
return map[string]string{}
}
func (res generateReportResp) Empty() bool {
return false
}
type addReportConfigRes struct {
re.ReportConfig `json:",inline"`
created bool
}
func (res addReportConfigRes) Code() int {
if res.created {
return http.StatusCreated
}
return http.StatusOK
}
func (res addReportConfigRes) Headers() map[string]string {
if res.created {
return map[string]string{}
}
return map[string]string{}
}
func (res addReportConfigRes) Empty() bool {
return false
}
type viewReportConfigRes struct {
re.ReportConfig `json:",inline"`
}
func (res viewReportConfigRes) Code() int {
return http.StatusOK
}
func (res viewReportConfigRes) Headers() map[string]string {
return map[string]string{}
}
func (res viewReportConfigRes) Empty() bool {
return false
}
type updateReportConfigRes struct {
re.ReportConfig `json:",inline"`
}
func (res updateReportConfigRes) Code() int {
return http.StatusOK
}
func (res updateReportConfigRes) Headers() map[string]string {
return map[string]string{}
}
func (res updateReportConfigRes) Empty() bool {
return false
}
type deleteReportConfigRes struct {
deleted bool
}
func (res deleteReportConfigRes) Code() int {
if res.deleted {
return http.StatusNoContent
}
return http.StatusOK
}
func (res deleteReportConfigRes) Headers() map[string]string {
return map[string]string{}
}
func (res deleteReportConfigRes) Empty() bool {
return true
}
type listReportsConfigRes struct {
pageRes
ReportConfigs []re.ReportConfig `json:"report_configs"`
}
func (res listReportsConfigRes) Code() int {
return http.StatusOK
}
func (res listReportsConfigRes) Headers() map[string]string {
return map[string]string{}
}
func (res listReportsConfigRes) Empty() bool {
return false
}
type downloadReportResp struct {
File re.ReportFile
}
func (res downloadReportResp) Code() int {
return http.StatusOK
}
func (res downloadReportResp) Headers() map[string]string {
return map[string]string{}
}
func (res downloadReportResp) Empty() bool {
return false
}
type emailReportResp struct{}
func (res emailReportResp) Code() int {
return http.StatusOK
}
func (res emailReportResp) Headers() map[string]string {
return map[string]string{}
}
func (res emailReportResp) Empty() bool {
return true
}
-200
View File
@@ -6,7 +6,6 @@ package api
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strings"
@@ -100,72 +99,6 @@ func MakeHandler(svc re.Service, authn mgauthn.Authentication, mux *chi.Mux, log
), "disable_rule").ServeHTTP)
})
})
r.Route("/reports", func(r chi.Router) {
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
generateReportEndpoint(svc),
decodeGenerateReportRequest,
encodeFileDownloadResponse,
opts...,
), "generate_report").ServeHTTP)
r.Route("/configs", func(r chi.Router) {
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
addReportConfigEndpoint(svc),
decodeAddReportConfigRequest,
api.EncodeResponse,
opts...,
), "add_report_config").ServeHTTP)
r.Get("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
viewReportConfigEndpoint(svc),
decodeViewReportConfigRequest,
api.EncodeResponse,
opts...,
), "view_report_config").ServeHTTP)
r.Patch("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
updateReportConfigEndpoint(svc),
decodeUpdateReportConfigRequest,
api.EncodeResponse,
opts...,
), "update_report_config").ServeHTTP)
r.Patch("/{reportID}/schedule", otelhttp.NewHandler(kithttp.NewServer(
updateReportScheduleEndpoint(svc),
decodeUpdateReportScheduleRequest,
api.EncodeResponse,
opts...,
), "update_report_scheduler").ServeHTTP)
r.Delete("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
deleteReportConfigEndpoint(svc),
decodeDeleteReportConfigRequest,
api.EncodeResponse,
opts...,
), "delete_report_config").ServeHTTP)
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
listReportsConfigEndpoint(svc),
decodeListReportsConfigRequest,
api.EncodeResponse,
opts...,
), "list_reports_config").ServeHTTP)
r.Post("/{reportID}/enable", otelhttp.NewHandler(kithttp.NewServer(
enableReportConfigEndpoint(svc),
decodeUpdateReportStatusRequest,
api.EncodeResponse,
opts...,
), "enable_report_config").ServeHTTP)
r.Post("/{reportID}/disable", otelhttp.NewHandler(kithttp.NewServer(
disableReportConfigEndpoint(svc),
decodeUpdateReportStatusRequest,
api.EncodeResponse,
opts...,
), "disable_report_config").ServeHTTP)
})
})
})
})
@@ -276,136 +209,3 @@ func decodeDeleteRuleRequest(_ context.Context, r *http.Request) (interface{}, e
return deleteRuleReq{id: id}, nil
}
func decodeGenerateReportRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
a, err := apiutil.ReadStringQuery(r, actionKey, defAction)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
action, err := re.ToReportAction(a)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
req := generateReportReq{
action: action,
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(err, apiutil.ErrValidation)
}
return req, nil
}
func decodeAddReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
var config re.ReportConfig
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
return nil, errors.Wrap(err, apiutil.ErrValidation)
}
return addReportConfigReq{ReportConfig: config}, nil
}
func decodeViewReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
id := chi.URLParam(r, reportIdKey)
return viewReportConfigReq{ID: id}, nil
}
func decodeUpdateReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
var config re.ReportConfig
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
return nil, errors.Wrap(err, apiutil.ErrValidation)
}
config.ID = chi.URLParam(r, reportIdKey)
return updateReportConfigReq{ReportConfig: config}, nil
}
func decodeUpdateReportScheduleRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateReportScheduleReq{
id: chi.URLParam(r, reportIdKey),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err))
}
return req, nil
}
func decodeUpdateReportStatusRequest(_ context.Context, r *http.Request) (interface{}, error) {
req := updateReportStatusReq{
id: chi.URLParam(r, reportIdKey),
}
return req, nil
}
func decodeDeleteReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
id := chi.URLParam(r, reportIdKey)
return deleteReportConfigReq{ID: id}, nil
}
func decodeListReportsConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
status, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefStatus)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
st, err := re.ToStatus(status)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
name, err := apiutil.ReadStringQuery(r, api.NameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
return listReportsConfigReq{
PageMeta: re.PageMeta{
Offset: offset,
Limit: limit,
Status: st,
Name: name,
},
}, nil
}
func encodeFileDownloadResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
switch resp := response.(type) {
case downloadReportResp:
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", resp.File.Name))
w.Header().Set("Content-Type", resp.File.Format.ContentType())
_, err := w.Write(resp.File.Data)
return err
default:
if ar, ok := response.(supermq.Response); ok {
for k, v := range ar.Headers() {
w.Header().Set(k, v)
}
w.Header().Set("Content-Type", api.ContentType)
w.WriteHeader(ar.Code())
if ar.Empty() {
return nil
}
}
return json.NewEncoder(w).Encode(response)
}
}
-9
View File
@@ -1,9 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package re
type Emailer interface {
// SendEmailNotification sends an email to the recipients based on a trigger.
SendEmailNotification(to []string, from, subject, header, user, content, footer string, attachments map[string][]byte) error
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package emailer contains the domain concept definitions needed to support
// Magistrala re email service functionality.
package emailer
+9 -41
View File
@@ -11,6 +11,7 @@ import (
"strings"
"time"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/messaging"
lua "github.com/yuin/gopher-lua"
@@ -77,7 +78,7 @@ func matchSubject(published, subscribed string) bool {
return len(s) == n
}
func (re *re) process(ctx context.Context, r Rule, msg *messaging.Message) RunInfo {
func (re *re) process(ctx context.Context, r Rule, msg *messaging.Message) pkglog.RunInfo {
l := lua.NewState()
defer l.Close()
preload(l)
@@ -99,30 +100,30 @@ func (re *re) process(ctx context.Context, r Rule, msg *messaging.Message) RunIn
slog.Time("exec_time", time.Now().UTC()),
}
if err := l.DoString(r.Logic.Value); err != nil {
return RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to run rule logic: %s", err), Details: details}
return pkglog.RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to run rule logic: %s", err), Details: details}
}
// Get the last result.
result := l.Get(-1)
if result == lua.LNil {
return RunInfo{Level: slog.LevelWarn, Message: "rule with nil script result", Details: details}
return pkglog.RunInfo{Level: slog.LevelWarn, Message: "rule with nil script result", Details: details}
}
// Converting Lua is an expensive operation, so
// don't do it if there are no outputs.
if len(r.Logic.Outputs) == 0 {
return RunInfo{Level: slog.LevelWarn, Message: "rule with no output channels", Details: details}
return pkglog.RunInfo{Level: slog.LevelWarn, Message: "rule with no output channels", Details: details}
}
var err error
res := convertLua(result)
for _, o := range r.Logic.Outputs {
// If value is false, don't run the follow-up.
if v, ok := res.(bool); ok && !v {
return RunInfo{Level: slog.LevelInfo, Message: "logic returned false", Details: details}
return pkglog.RunInfo{Level: slog.LevelInfo, Message: "logic returned false", Details: details}
}
if e := re.handleOutput(ctx, o, r, msg, res); e != nil {
err = errors.Wrap(e, err)
}
}
ret := RunInfo{Level: slog.LevelInfo, Message: "rule processed successfully", Details: details}
ret := pkglog.RunInfo{Level: slog.LevelInfo, Message: "rule processed successfully", Details: details}
if err != nil {
ret.Level = slog.LevelError
ret.Message = fmt.Sprintf("failed to handle rule output: %s", err)
@@ -161,7 +162,7 @@ func (re *re) StartScheduler(ctx context.Context) error {
page, err := re.repo.ListRules(ctx, pm)
if err != nil {
re.runInfo <- RunInfo{
re.runInfo <- pkglog.RunInfo{
Level: slog.LevelError,
Message: fmt.Sprintf("failed to list rules: %s", err),
Details: []slog.Attr{slog.Time("due", due)},
@@ -173,7 +174,7 @@ func (re *re) StartScheduler(ctx context.Context) error {
for _, r := range page.Rules {
go func(rule Rule) {
if _, err := re.repo.UpdateRuleDue(ctx, rule.ID, rule.Schedule.NextDue()); err != nil {
re.runInfo <- RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to update rule: %s", err), Details: []slog.Attr{slog.Time("time", time.Now().UTC())}}
re.runInfo <- pkglog.RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to update rule: %s", err), Details: []slog.Attr{slog.Time("time", time.Now().UTC())}}
return
}
@@ -188,39 +189,6 @@ func (re *re) StartScheduler(ctx context.Context) error {
}
// Reset due, it will reset in the page meta as well.
due = time.Now().UTC()
reportConfigs, err := re.repo.ListReportsConfig(ctx, pm)
if err != nil {
re.runInfo <- RunInfo{
Level: slog.LevelError,
Message: fmt.Sprintf("failed to list reports : %s", err),
Details: []slog.Attr{slog.Time("due", due)},
}
continue
}
for _, c := range reportConfigs.ReportConfigs {
go func(cfg ReportConfig) {
if _, err := re.repo.UpdateReportDue(ctx, cfg.ID, cfg.Schedule.NextDue()); err != nil {
re.runInfo <- RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to update report: %s", err), Details: []slog.Attr{slog.Time("time", time.Now().UTC())}}
return
}
_, err := re.generateReport(ctx, cfg, EmailReport)
ret := RunInfo{
Details: []slog.Attr{
slog.String("domain_id", cfg.DomainID),
slog.String("report_id", cfg.ID),
slog.String("report_name", cfg.Name),
slog.Time("exec_time", time.Now().UTC()),
},
}
if err != nil {
ret.Level = slog.LevelError
ret.Message = fmt.Sprintf("failed to generate report: %s", err)
}
re.runInfo <- ret
}(c)
}
}
}
}
+4 -153
View File
@@ -15,15 +15,10 @@ import (
)
var (
errDomainCreateConfigs = errors.New("not authorized to create report configs in domain")
errDomainViewConfigs = errors.New("not authorized to view report configs in domain")
errDomainUpdateConfigs = errors.New("not authorized to update report configs in domain")
errDomainDeleteConfigs = errors.New("not authorized to delete report configs in domain")
errDomainCreateRules = errors.New("not authorized to create rules in domain")
errDomainViewRules = errors.New("not authorized to view rules in domain")
errDomainUpdateRules = errors.New("not authorized to update rules in domain")
errDomainDeleteRules = errors.New("not authorized to delete rules in domain")
errDomainGenerateReports = errors.New("not authorized to generate reports in domain")
errDomainCreateRules = errors.New("not authorized to create rules in domain")
errDomainViewRules = errors.New("not authorized to view rules in domain")
errDomainUpdateRules = errors.New("not authorized to update rules in domain")
errDomainDeleteRules = errors.New("not authorized to delete rules in domain")
)
type authorizationMiddleware struct {
@@ -167,150 +162,6 @@ func (am *authorizationMiddleware) DisableRule(ctx context.Context, session auth
return am.svc.DisableRule(ctx, session, id)
}
func (am *authorizationMiddleware) AddReportConfig(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return re.ReportConfig{}, errors.Wrap(errDomainCreateConfigs, err)
}
return am.svc.AddReportConfig(ctx, session, cfg)
}
func (am *authorizationMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return re.ReportConfig{}, errors.Wrap(errDomainViewConfigs, err)
}
return am.svc.ViewReportConfig(ctx, session, id)
}
func (am *authorizationMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return re.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
}
return am.svc.UpdateReportConfig(ctx, session, cfg)
}
func (am *authorizationMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return re.ReportConfig{}, errors.Wrap(errDomainDeleteConfigs, err)
}
return am.svc.UpdateReportSchedule(ctx, session, cfg)
}
func (am *authorizationMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return errors.Wrap(errDomainDeleteConfigs, err)
}
return am.svc.RemoveReportConfig(ctx, session, id)
}
func (am *authorizationMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm re.PageMeta) (re.ReportConfigPage, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return re.ReportConfigPage{}, errors.Wrap(errDomainViewConfigs, err)
}
return am.svc.ListReportsConfig(ctx, session, pm)
}
func (am *authorizationMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return re.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
}
return am.svc.EnableReportConfig(ctx, session, id)
}
func (am *authorizationMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return re.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
}
return am.svc.DisableReportConfig(ctx, session, id)
}
func (am *authorizationMiddleware) GenerateReport(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction) (re.ReportPage, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return re.ReportPage{}, errors.Wrap(errDomainGenerateReports, err)
}
return am.svc.GenerateReport(ctx, session, config, action)
}
func (am *authorizationMiddleware) StartScheduler(ctx context.Context) error {
return am.svc.StartScheduler(ctx)
}
-171
View File
@@ -220,174 +220,3 @@ func (lm *loggingMiddleware) Handle(msg *messaging.Message) (err error) {
func (lm *loggingMiddleware) Cancel() error {
return lm.Cancel()
}
func (lm *loggingMiddleware) GenerateReport(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction) (page re.ReportPage, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Generate report failed", args...)
return
}
lm.logger.Info("Generate report completed", args...)
}(time.Now())
return lm.svc.GenerateReport(ctx, session, config, action)
}
func (lm *loggingMiddleware) AddReportConfig(ctx context.Context, session authn.Session, config re.ReportConfig) (res re.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.String("report_name", config.Name),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Add report config failed", args...)
return
}
lm.logger.Info("Add report config completed successfully", args...)
}(time.Now())
return lm.svc.AddReportConfig(ctx, session, config)
}
func (lm *loggingMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (res re.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report_config",
slog.String("id", res.ID),
slog.String("name", res.Name),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("View report config failed", args...)
return
}
lm.logger.Info("View report config completed successfully", args...)
}(time.Now())
return lm.svc.ViewReportConfig(ctx, session, id)
}
func (lm *loggingMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, config re.ReportConfig) (res re.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report_config",
slog.String("id", config.ID),
slog.String("name", config.Name),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Update report config failed", args...)
return
}
lm.logger.Info("Update report config completed successfully", args...)
}(time.Now())
return lm.svc.UpdateReportConfig(ctx, session, config)
}
func (lm *loggingMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg re.ReportConfig) (res re.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report",
slog.String("id", cfg.ID),
slog.Any("schedule", cfg.Schedule),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Update report schedule failed", args...)
return
}
lm.logger.Info("Update report schedule completed successfully", args...)
}(time.Now())
return lm.svc.UpdateReportSchedule(ctx, session, cfg)
}
func (lm *loggingMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm re.PageMeta) (pg re.ReportConfigPage, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("page",
slog.Uint64("offset", pm.Offset),
slog.Uint64("limit", pm.Limit),
slog.Uint64("total", pg.Total),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("List reports config failed", args...)
return
}
lm.logger.Info("List reports config completed successfully", args...)
}(time.Now())
return lm.svc.ListReportsConfig(ctx, session, pm)
}
func (lm *loggingMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (res re.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report_config",
slog.String("id", res.ID),
slog.String("name", res.Name),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Disable report config failed", args...)
return
}
lm.logger.Info("Disable report config completed successfully", args...)
}(time.Now())
return lm.svc.DisableReportConfig(ctx, session, id)
}
func (lm *loggingMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (res re.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report_config",
slog.String("id", res.ID),
slog.String("name", res.Name),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Enable report config failed", args...)
return
}
lm.logger.Info("Enable report config completed successfully", args...)
}(time.Now())
return lm.svc.EnableReportConfig(ctx, session, id)
}
func (lm *loggingMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.String("report_config_id", id),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Remove report config failed", args...)
return
}
lm.logger.Info("Remove report config completed successfully", args...)
}(time.Now())
return lm.svc.RemoveReportConfig(ctx, session, id)
}
-432
View File
@@ -42,61 +42,6 @@ func (_m *Repository) EXPECT() *Repository_Expecter {
return &Repository_Expecter{mock: &_m.Mock}
}
// AddReportConfig provides a mock function for the type Repository
func (_mock *Repository) AddReportConfig(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
ret := _mock.Called(ctx, cfg)
if len(ret) == 0 {
panic("no return value specified for AddReportConfig")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) (re.ReportConfig, error)); ok {
return returnFunc(ctx, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) re.ReportConfig); ok {
r0 = returnFunc(ctx, cfg)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, re.ReportConfig) error); ok {
r1 = returnFunc(ctx, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_AddReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReportConfig'
type Repository_AddReportConfig_Call struct {
*mock.Call
}
// AddReportConfig is a helper method to define mock.On call
// - ctx
// - cfg
func (_e *Repository_Expecter) AddReportConfig(ctx interface{}, cfg interface{}) *Repository_AddReportConfig_Call {
return &Repository_AddReportConfig_Call{Call: _e.mock.On("AddReportConfig", ctx, cfg)}
}
func (_c *Repository_AddReportConfig_Call) Run(run func(ctx context.Context, cfg re.ReportConfig)) *Repository_AddReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(re.ReportConfig))
})
return _c
}
func (_c *Repository_AddReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Repository_AddReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_AddReportConfig_Call) RunAndReturn(run func(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error)) *Repository_AddReportConfig_Call {
_c.Call.Return(run)
return _c
}
// AddRule provides a mock function for the type Repository
func (_mock *Repository) AddRule(ctx context.Context, r re.Rule) (re.Rule, error) {
ret := _mock.Called(ctx, r)
@@ -152,61 +97,6 @@ func (_c *Repository_AddRule_Call) RunAndReturn(run func(ctx context.Context, r
return _c
}
// ListReportsConfig provides a mock function for the type Repository
func (_mock *Repository) ListReportsConfig(ctx context.Context, pm re.PageMeta) (re.ReportConfigPage, error) {
ret := _mock.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for ListReportsConfig")
}
var r0 re.ReportConfigPage
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, re.PageMeta) (re.ReportConfigPage, error)); ok {
return returnFunc(ctx, pm)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, re.PageMeta) re.ReportConfigPage); ok {
r0 = returnFunc(ctx, pm)
} else {
r0 = ret.Get(0).(re.ReportConfigPage)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, re.PageMeta) error); ok {
r1 = returnFunc(ctx, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_ListReportsConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReportsConfig'
type Repository_ListReportsConfig_Call struct {
*mock.Call
}
// ListReportsConfig is a helper method to define mock.On call
// - ctx
// - pm
func (_e *Repository_Expecter) ListReportsConfig(ctx interface{}, pm interface{}) *Repository_ListReportsConfig_Call {
return &Repository_ListReportsConfig_Call{Call: _e.mock.On("ListReportsConfig", ctx, pm)}
}
func (_c *Repository_ListReportsConfig_Call) Run(run func(ctx context.Context, pm re.PageMeta)) *Repository_ListReportsConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(re.PageMeta))
})
return _c
}
func (_c *Repository_ListReportsConfig_Call) Return(reportConfigPage re.ReportConfigPage, err error) *Repository_ListReportsConfig_Call {
_c.Call.Return(reportConfigPage, err)
return _c
}
func (_c *Repository_ListReportsConfig_Call) RunAndReturn(run func(ctx context.Context, pm re.PageMeta) (re.ReportConfigPage, error)) *Repository_ListReportsConfig_Call {
_c.Call.Return(run)
return _c
}
// ListRules provides a mock function for the type Repository
func (_mock *Repository) ListRules(ctx context.Context, pm re.PageMeta) (re.Page, error) {
ret := _mock.Called(ctx, pm)
@@ -262,52 +152,6 @@ func (_c *Repository_ListRules_Call) RunAndReturn(run func(ctx context.Context,
return _c
}
// RemoveReportConfig provides a mock function for the type Repository
func (_mock *Repository) RemoveReportConfig(ctx context.Context, id string) error {
ret := _mock.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for RemoveReportConfig")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = returnFunc(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Repository_RemoveReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveReportConfig'
type Repository_RemoveReportConfig_Call struct {
*mock.Call
}
// RemoveReportConfig is a helper method to define mock.On call
// - ctx
// - id
func (_e *Repository_Expecter) RemoveReportConfig(ctx interface{}, id interface{}) *Repository_RemoveReportConfig_Call {
return &Repository_RemoveReportConfig_Call{Call: _e.mock.On("RemoveReportConfig", ctx, id)}
}
func (_c *Repository_RemoveReportConfig_Call) Run(run func(ctx context.Context, id string)) *Repository_RemoveReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *Repository_RemoveReportConfig_Call) Return(err error) *Repository_RemoveReportConfig_Call {
_c.Call.Return(err)
return _c
}
func (_c *Repository_RemoveReportConfig_Call) RunAndReturn(run func(ctx context.Context, id string) error) *Repository_RemoveReportConfig_Call {
_c.Call.Return(run)
return _c
}
// RemoveRule provides a mock function for the type Repository
func (_mock *Repository) RemoveRule(ctx context.Context, id string) error {
ret := _mock.Called(ctx, id)
@@ -354,227 +198,6 @@ func (_c *Repository_RemoveRule_Call) RunAndReturn(run func(ctx context.Context,
return _c
}
// UpdateReportConfig provides a mock function for the type Repository
func (_mock *Repository) UpdateReportConfig(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
ret := _mock.Called(ctx, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportConfig")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) (re.ReportConfig, error)); ok {
return returnFunc(ctx, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) re.ReportConfig); ok {
r0 = returnFunc(ctx, cfg)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, re.ReportConfig) error); ok {
r1 = returnFunc(ctx, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_UpdateReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfig'
type Repository_UpdateReportConfig_Call struct {
*mock.Call
}
// UpdateReportConfig is a helper method to define mock.On call
// - ctx
// - cfg
func (_e *Repository_Expecter) UpdateReportConfig(ctx interface{}, cfg interface{}) *Repository_UpdateReportConfig_Call {
return &Repository_UpdateReportConfig_Call{Call: _e.mock.On("UpdateReportConfig", ctx, cfg)}
}
func (_c *Repository_UpdateReportConfig_Call) Run(run func(ctx context.Context, cfg re.ReportConfig)) *Repository_UpdateReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(re.ReportConfig))
})
return _c
}
func (_c *Repository_UpdateReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Repository_UpdateReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_UpdateReportConfig_Call) RunAndReturn(run func(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error)) *Repository_UpdateReportConfig_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportConfigStatus provides a mock function for the type Repository
func (_mock *Repository) UpdateReportConfigStatus(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
ret := _mock.Called(ctx, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportConfigStatus")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) (re.ReportConfig, error)); ok {
return returnFunc(ctx, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) re.ReportConfig); ok {
r0 = returnFunc(ctx, cfg)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, re.ReportConfig) error); ok {
r1 = returnFunc(ctx, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_UpdateReportConfigStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfigStatus'
type Repository_UpdateReportConfigStatus_Call struct {
*mock.Call
}
// UpdateReportConfigStatus is a helper method to define mock.On call
// - ctx
// - cfg
func (_e *Repository_Expecter) UpdateReportConfigStatus(ctx interface{}, cfg interface{}) *Repository_UpdateReportConfigStatus_Call {
return &Repository_UpdateReportConfigStatus_Call{Call: _e.mock.On("UpdateReportConfigStatus", ctx, cfg)}
}
func (_c *Repository_UpdateReportConfigStatus_Call) Run(run func(ctx context.Context, cfg re.ReportConfig)) *Repository_UpdateReportConfigStatus_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(re.ReportConfig))
})
return _c
}
func (_c *Repository_UpdateReportConfigStatus_Call) Return(reportConfig re.ReportConfig, err error) *Repository_UpdateReportConfigStatus_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_UpdateReportConfigStatus_Call) RunAndReturn(run func(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error)) *Repository_UpdateReportConfigStatus_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportDue provides a mock function for the type Repository
func (_mock *Repository) UpdateReportDue(ctx context.Context, id string, due time.Time) (re.ReportConfig, error) {
ret := _mock.Called(ctx, id, due)
if len(ret) == 0 {
panic("no return value specified for UpdateReportDue")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Time) (re.ReportConfig, error)); ok {
return returnFunc(ctx, id, due)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Time) re.ReportConfig); ok {
r0 = returnFunc(ctx, id, due)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string, time.Time) error); ok {
r1 = returnFunc(ctx, id, due)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_UpdateReportDue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportDue'
type Repository_UpdateReportDue_Call struct {
*mock.Call
}
// UpdateReportDue is a helper method to define mock.On call
// - ctx
// - id
// - due
func (_e *Repository_Expecter) UpdateReportDue(ctx interface{}, id interface{}, due interface{}) *Repository_UpdateReportDue_Call {
return &Repository_UpdateReportDue_Call{Call: _e.mock.On("UpdateReportDue", ctx, id, due)}
}
func (_c *Repository_UpdateReportDue_Call) Run(run func(ctx context.Context, id string, due time.Time)) *Repository_UpdateReportDue_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(time.Time))
})
return _c
}
func (_c *Repository_UpdateReportDue_Call) Return(reportConfig re.ReportConfig, err error) *Repository_UpdateReportDue_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_UpdateReportDue_Call) RunAndReturn(run func(ctx context.Context, id string, due time.Time) (re.ReportConfig, error)) *Repository_UpdateReportDue_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportSchedule provides a mock function for the type Repository
func (_mock *Repository) UpdateReportSchedule(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
ret := _mock.Called(ctx, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportSchedule")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) (re.ReportConfig, error)); ok {
return returnFunc(ctx, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) re.ReportConfig); ok {
r0 = returnFunc(ctx, cfg)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, re.ReportConfig) error); ok {
r1 = returnFunc(ctx, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_UpdateReportSchedule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportSchedule'
type Repository_UpdateReportSchedule_Call struct {
*mock.Call
}
// UpdateReportSchedule is a helper method to define mock.On call
// - ctx
// - cfg
func (_e *Repository_Expecter) UpdateReportSchedule(ctx interface{}, cfg interface{}) *Repository_UpdateReportSchedule_Call {
return &Repository_UpdateReportSchedule_Call{Call: _e.mock.On("UpdateReportSchedule", ctx, cfg)}
}
func (_c *Repository_UpdateReportSchedule_Call) Run(run func(ctx context.Context, cfg re.ReportConfig)) *Repository_UpdateReportSchedule_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(re.ReportConfig))
})
return _c
}
func (_c *Repository_UpdateReportSchedule_Call) Return(reportConfig re.ReportConfig, err error) *Repository_UpdateReportSchedule_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_UpdateReportSchedule_Call) RunAndReturn(run func(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error)) *Repository_UpdateReportSchedule_Call {
_c.Call.Return(run)
return _c
}
// UpdateRule provides a mock function for the type Repository
func (_mock *Repository) UpdateRule(ctx context.Context, r re.Rule) (re.Rule, error) {
ret := _mock.Called(ctx, r)
@@ -796,61 +419,6 @@ func (_c *Repository_UpdateRuleStatus_Call) RunAndReturn(run func(ctx context.Co
return _c
}
// ViewReportConfig provides a mock function for the type Repository
func (_mock *Repository) ViewReportConfig(ctx context.Context, id string) (re.ReportConfig, error) {
ret := _mock.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for ViewReportConfig")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (re.ReportConfig, error)); ok {
return returnFunc(ctx, id)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string) re.ReportConfig); ok {
r0 = returnFunc(ctx, id)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = returnFunc(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_ViewReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewReportConfig'
type Repository_ViewReportConfig_Call struct {
*mock.Call
}
// ViewReportConfig is a helper method to define mock.On call
// - ctx
// - id
func (_e *Repository_Expecter) ViewReportConfig(ctx interface{}, id interface{}) *Repository_ViewReportConfig_Call {
return &Repository_ViewReportConfig_Call{Call: _e.mock.On("ViewReportConfig", ctx, id)}
}
func (_c *Repository_ViewReportConfig_Call) Run(run func(ctx context.Context, id string)) *Repository_ViewReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *Repository_ViewReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Repository_ViewReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Context, id string) (re.ReportConfig, error)) *Repository_ViewReportConfig_Call {
_c.Call.Return(run)
return _c
}
// ViewRule provides a mock function for the type Repository
func (_mock *Repository) ViewRule(ctx context.Context, id string) (re.Rule, error) {
ret := _mock.Called(ctx, id)
-496
View File
@@ -43,62 +43,6 @@ func (_m *Service) EXPECT() *Service_Expecter {
return &Service_Expecter{mock: &_m.Mock}
}
// AddReportConfig provides a mock function for the type Service
func (_mock *Service) AddReportConfig(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
ret := _mock.Called(ctx, session, cfg)
if len(ret) == 0 {
panic("no return value specified for AddReportConfig")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) (re.ReportConfig, error)); ok {
return returnFunc(ctx, session, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) re.ReportConfig); ok {
r0 = returnFunc(ctx, session, cfg)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.ReportConfig) error); ok {
r1 = returnFunc(ctx, session, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_AddReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReportConfig'
type Service_AddReportConfig_Call struct {
*mock.Call
}
// AddReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - cfg
func (_e *Service_Expecter) AddReportConfig(ctx interface{}, session interface{}, cfg interface{}) *Service_AddReportConfig_Call {
return &Service_AddReportConfig_Call{Call: _e.mock.On("AddReportConfig", ctx, session, cfg)}
}
func (_c *Service_AddReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig)) *Service_AddReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.ReportConfig))
})
return _c
}
func (_c *Service_AddReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_AddReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_AddReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error)) *Service_AddReportConfig_Call {
_c.Call.Return(run)
return _c
}
// AddRule provides a mock function for the type Service
func (_mock *Service) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
ret := _mock.Called(ctx, session, r)
@@ -199,62 +143,6 @@ func (_c *Service_Cancel_Call) RunAndReturn(run func() error) *Service_Cancel_Ca
return _c
}
// DisableReportConfig provides a mock function for the type Service
func (_mock *Service) DisableReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
ret := _mock.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for DisableReportConfig")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (re.ReportConfig, error)); ok {
return returnFunc(ctx, session, id)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) re.ReportConfig); ok {
r0 = returnFunc(ctx, session, id)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
r1 = returnFunc(ctx, session, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_DisableReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisableReportConfig'
type Service_DisableReportConfig_Call struct {
*mock.Call
}
// DisableReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - id
func (_e *Service_Expecter) DisableReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_DisableReportConfig_Call {
return &Service_DisableReportConfig_Call{Call: _e.mock.On("DisableReportConfig", ctx, session, id)}
}
func (_c *Service_DisableReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_DisableReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
})
return _c
}
func (_c *Service_DisableReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_DisableReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_DisableReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error)) *Service_DisableReportConfig_Call {
_c.Call.Return(run)
return _c
}
// DisableRule provides a mock function for the type Service
func (_mock *Service) DisableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
ret := _mock.Called(ctx, session, id)
@@ -311,62 +199,6 @@ func (_c *Service_DisableRule_Call) RunAndReturn(run func(ctx context.Context, s
return _c
}
// EnableReportConfig provides a mock function for the type Service
func (_mock *Service) EnableReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
ret := _mock.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for EnableReportConfig")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (re.ReportConfig, error)); ok {
return returnFunc(ctx, session, id)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) re.ReportConfig); ok {
r0 = returnFunc(ctx, session, id)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
r1 = returnFunc(ctx, session, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_EnableReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnableReportConfig'
type Service_EnableReportConfig_Call struct {
*mock.Call
}
// EnableReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - id
func (_e *Service_Expecter) EnableReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_EnableReportConfig_Call {
return &Service_EnableReportConfig_Call{Call: _e.mock.On("EnableReportConfig", ctx, session, id)}
}
func (_c *Service_EnableReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_EnableReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
})
return _c
}
func (_c *Service_EnableReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_EnableReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_EnableReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error)) *Service_EnableReportConfig_Call {
_c.Call.Return(run)
return _c
}
// EnableRule provides a mock function for the type Service
func (_mock *Service) EnableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
ret := _mock.Called(ctx, session, id)
@@ -423,63 +255,6 @@ func (_c *Service_EnableRule_Call) RunAndReturn(run func(ctx context.Context, se
return _c
}
// GenerateReport provides a mock function for the type Service
func (_mock *Service) GenerateReport(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction) (re.ReportPage, error) {
ret := _mock.Called(ctx, session, config, action)
if len(ret) == 0 {
panic("no return value specified for GenerateReport")
}
var r0 re.ReportPage
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig, re.ReportAction) (re.ReportPage, error)); ok {
return returnFunc(ctx, session, config, action)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig, re.ReportAction) re.ReportPage); ok {
r0 = returnFunc(ctx, session, config, action)
} else {
r0 = ret.Get(0).(re.ReportPage)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.ReportConfig, re.ReportAction) error); ok {
r1 = returnFunc(ctx, session, config, action)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_GenerateReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateReport'
type Service_GenerateReport_Call struct {
*mock.Call
}
// GenerateReport is a helper method to define mock.On call
// - ctx
// - session
// - config
// - action
func (_e *Service_Expecter) GenerateReport(ctx interface{}, session interface{}, config interface{}, action interface{}) *Service_GenerateReport_Call {
return &Service_GenerateReport_Call{Call: _e.mock.On("GenerateReport", ctx, session, config, action)}
}
func (_c *Service_GenerateReport_Call) Run(run func(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction)) *Service_GenerateReport_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.ReportConfig), args[3].(re.ReportAction))
})
return _c
}
func (_c *Service_GenerateReport_Call) Return(reportPage re.ReportPage, err error) *Service_GenerateReport_Call {
_c.Call.Return(reportPage, err)
return _c
}
func (_c *Service_GenerateReport_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction) (re.ReportPage, error)) *Service_GenerateReport_Call {
_c.Call.Return(run)
return _c
}
// Handle provides a mock function for the type Service
func (_mock *Service) Handle(msg *messaging.Message) error {
ret := _mock.Called(msg)
@@ -525,62 +300,6 @@ func (_c *Service_Handle_Call) RunAndReturn(run func(msg *messaging.Message) err
return _c
}
// ListReportsConfig provides a mock function for the type Service
func (_mock *Service) ListReportsConfig(ctx context.Context, session authn.Session, pm re.PageMeta) (re.ReportConfigPage, error) {
ret := _mock.Called(ctx, session, pm)
if len(ret) == 0 {
panic("no return value specified for ListReportsConfig")
}
var r0 re.ReportConfigPage
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.PageMeta) (re.ReportConfigPage, error)); ok {
return returnFunc(ctx, session, pm)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.PageMeta) re.ReportConfigPage); ok {
r0 = returnFunc(ctx, session, pm)
} else {
r0 = ret.Get(0).(re.ReportConfigPage)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.PageMeta) error); ok {
r1 = returnFunc(ctx, session, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_ListReportsConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReportsConfig'
type Service_ListReportsConfig_Call struct {
*mock.Call
}
// ListReportsConfig is a helper method to define mock.On call
// - ctx
// - session
// - pm
func (_e *Service_Expecter) ListReportsConfig(ctx interface{}, session interface{}, pm interface{}) *Service_ListReportsConfig_Call {
return &Service_ListReportsConfig_Call{Call: _e.mock.On("ListReportsConfig", ctx, session, pm)}
}
func (_c *Service_ListReportsConfig_Call) Run(run func(ctx context.Context, session authn.Session, pm re.PageMeta)) *Service_ListReportsConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.PageMeta))
})
return _c
}
func (_c *Service_ListReportsConfig_Call) Return(reportConfigPage re.ReportConfigPage, err error) *Service_ListReportsConfig_Call {
_c.Call.Return(reportConfigPage, err)
return _c
}
func (_c *Service_ListReportsConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, pm re.PageMeta) (re.ReportConfigPage, error)) *Service_ListReportsConfig_Call {
_c.Call.Return(run)
return _c
}
// ListRules provides a mock function for the type Service
func (_mock *Service) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
ret := _mock.Called(ctx, session, pm)
@@ -637,53 +356,6 @@ func (_c *Service_ListRules_Call) RunAndReturn(run func(ctx context.Context, ses
return _c
}
// RemoveReportConfig provides a mock function for the type Service
func (_mock *Service) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
ret := _mock.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for RemoveReportConfig")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok {
r0 = returnFunc(ctx, session, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Service_RemoveReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveReportConfig'
type Service_RemoveReportConfig_Call struct {
*mock.Call
}
// RemoveReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - id
func (_e *Service_Expecter) RemoveReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_RemoveReportConfig_Call {
return &Service_RemoveReportConfig_Call{Call: _e.mock.On("RemoveReportConfig", ctx, session, id)}
}
func (_c *Service_RemoveReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_RemoveReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
})
return _c
}
func (_c *Service_RemoveReportConfig_Call) Return(err error) *Service_RemoveReportConfig_Call {
_c.Call.Return(err)
return _c
}
func (_c *Service_RemoveReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) error) *Service_RemoveReportConfig_Call {
_c.Call.Return(run)
return _c
}
// RemoveRule provides a mock function for the type Service
func (_mock *Service) RemoveRule(ctx context.Context, session authn.Session, id string) error {
ret := _mock.Called(ctx, session, id)
@@ -776,118 +448,6 @@ func (_c *Service_StartScheduler_Call) RunAndReturn(run func(ctx context.Context
return _c
}
// UpdateReportConfig provides a mock function for the type Service
func (_mock *Service) UpdateReportConfig(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
ret := _mock.Called(ctx, session, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportConfig")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) (re.ReportConfig, error)); ok {
return returnFunc(ctx, session, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) re.ReportConfig); ok {
r0 = returnFunc(ctx, session, cfg)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.ReportConfig) error); ok {
r1 = returnFunc(ctx, session, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_UpdateReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfig'
type Service_UpdateReportConfig_Call struct {
*mock.Call
}
// UpdateReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - cfg
func (_e *Service_Expecter) UpdateReportConfig(ctx interface{}, session interface{}, cfg interface{}) *Service_UpdateReportConfig_Call {
return &Service_UpdateReportConfig_Call{Call: _e.mock.On("UpdateReportConfig", ctx, session, cfg)}
}
func (_c *Service_UpdateReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig)) *Service_UpdateReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.ReportConfig))
})
return _c
}
func (_c *Service_UpdateReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_UpdateReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_UpdateReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error)) *Service_UpdateReportConfig_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportSchedule provides a mock function for the type Service
func (_mock *Service) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
ret := _mock.Called(ctx, session, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportSchedule")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) (re.ReportConfig, error)); ok {
return returnFunc(ctx, session, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) re.ReportConfig); ok {
r0 = returnFunc(ctx, session, cfg)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.ReportConfig) error); ok {
r1 = returnFunc(ctx, session, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_UpdateReportSchedule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportSchedule'
type Service_UpdateReportSchedule_Call struct {
*mock.Call
}
// UpdateReportSchedule is a helper method to define mock.On call
// - ctx
// - session
// - cfg
func (_e *Service_Expecter) UpdateReportSchedule(ctx interface{}, session interface{}, cfg interface{}) *Service_UpdateReportSchedule_Call {
return &Service_UpdateReportSchedule_Call{Call: _e.mock.On("UpdateReportSchedule", ctx, session, cfg)}
}
func (_c *Service_UpdateReportSchedule_Call) Run(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig)) *Service_UpdateReportSchedule_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.ReportConfig))
})
return _c
}
func (_c *Service_UpdateReportSchedule_Call) Return(reportConfig re.ReportConfig, err error) *Service_UpdateReportSchedule_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_UpdateReportSchedule_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error)) *Service_UpdateReportSchedule_Call {
_c.Call.Return(run)
return _c
}
// UpdateRule provides a mock function for the type Service
func (_mock *Service) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
ret := _mock.Called(ctx, session, r)
@@ -1000,62 +560,6 @@ func (_c *Service_UpdateRuleSchedule_Call) RunAndReturn(run func(ctx context.Con
return _c
}
// ViewReportConfig provides a mock function for the type Service
func (_mock *Service) ViewReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
ret := _mock.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for ViewReportConfig")
}
var r0 re.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (re.ReportConfig, error)); ok {
return returnFunc(ctx, session, id)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) re.ReportConfig); ok {
r0 = returnFunc(ctx, session, id)
} else {
r0 = ret.Get(0).(re.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
r1 = returnFunc(ctx, session, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_ViewReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewReportConfig'
type Service_ViewReportConfig_Call struct {
*mock.Call
}
// ViewReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - id
func (_e *Service_Expecter) ViewReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_ViewReportConfig_Call {
return &Service_ViewReportConfig_Call{Call: _e.mock.On("ViewReportConfig", ctx, session, id)}
}
func (_c *Service_ViewReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_ViewReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
})
return _c
}
func (_c *Service_ViewReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_ViewReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error)) *Service_ViewReportConfig_Call {
_c.Call.Return(run)
return _c
}
// ViewRule provides a mock function for the type Service
func (_mock *Service) ViewRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
ret := _mock.Called(ctx, session, id)
-26
View File
@@ -43,32 +43,6 @@ func Migration() *migrate.MemoryMigrationSource {
`DROP TABLE IF EXISTS rules`,
},
},
{
Id: "rules_02",
Up: []string{
`CREATE TABLE IF NOT EXISTS report_config (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(1024),
description TEXT,
domain_id VARCHAR(36) NOT NULL,
status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0),
created_at TIMESTAMP,
created_by VARCHAR(254),
updated_at TIMESTAMP,
updated_by VARCHAR(254),
time TIMESTAMP,
recurring SMALLINT,
recurring_period SMALLINT,
start_datetime TIMESTAMP,
config JSONB,
email JSONB,
metrics JSONB
);`,
},
Down: []string{
`DROP TABLE IF EXISTS report_config;`,
},
},
},
}
}
-315
View File
@@ -353,318 +353,3 @@ func pageRulesQuery(pm re.PageMeta) string {
return q
}
func (repo *PostgresRepository) AddReportConfig(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
q := `
INSERT INTO report_config (id, name, description, domain_id, config, metrics,
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status)
VALUES (:id, :name, :description, :domain_id, :config, :metrics,
:email, :start_datetime, :time, :recurring, :recurring_period, :created_at, :created_by, :updated_at, :updated_by, :status)
RETURNING id, name, description, domain_id, config, metrics,
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
`
dbr, err := reportToDb(cfg)
if err != nil {
return re.ReportConfig{}, err
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
if err != nil {
return re.ReportConfig{}, err
}
defer row.Close()
var dbReport dbReport
if row.Next() {
if err := row.StructScan(&dbReport); err != nil {
return re.ReportConfig{}, err
}
}
report, err := dbToReport(dbReport)
if err != nil {
return re.ReportConfig{}, err
}
return report, nil
}
func (repo *PostgresRepository) ViewReportConfig(ctx context.Context, id string) (re.ReportConfig, error) {
q := `
SELECT id, name, description, domain_id, config, metrics,
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status
FROM report_config
WHERE id = $1;
`
row := repo.DB.QueryRowxContext(ctx, q, id)
if err := row.Err(); err != nil {
return re.ReportConfig{}, err
}
var dbr dbReport
if err := row.StructScan(&dbr); err != nil {
return re.ReportConfig{}, err
}
rpt, err := dbToReport(dbr)
if err != nil {
return re.ReportConfig{}, err
}
return rpt, nil
}
func (repo *PostgresRepository) UpdateReportConfigStatus(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
q := `UPDATE report_config SET status = :status, updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id
RETURNING id, name, description, domain_id, metrics, email, config,
start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;`
dbRpt, err := reportToDb(cfg)
if err != nil {
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbRpt)
if err != nil {
return re.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
}
defer row.Close()
dbr := dbReport{}
if row.Next() {
if err := row.StructScan(&dbr); err != nil {
return re.ReportConfig{}, err
}
res, err := dbToReport(dbr)
if err != nil {
return re.ReportConfig{}, err
}
return res, err
}
return re.ReportConfig{}, repoerr.ErrNotFound
}
func (repo *PostgresRepository) UpdateReportConfig(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
var query []string
if cfg.Name != "" {
query = append(query, "name = :name")
}
if cfg.Description != "" {
query = append(query, "description = :description")
}
if len(cfg.Metrics) > 0 {
query = append(query, "metrics = :metrics")
}
if cfg.Email != nil {
query = append(query, "email = :email")
}
if cfg.Config != nil {
query = append(query, "config = :config")
}
var q string
if len(query) > 0 {
q = fmt.Sprintf("%s", strings.Join(query, ", "))
}
q = fmt.Sprintf(`
UPDATE report_config
SET %s,
updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id
RETURNING id, name, description, domain_id, config, metrics,
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
`, q)
dbr, err := reportToDb(cfg)
if err != nil {
return re.ReportConfig{}, err
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
if err != nil {
return re.ReportConfig{}, err
}
defer row.Close()
var dbReport dbReport
if row.Next() {
if err := row.StructScan(&dbReport); err != nil {
return re.ReportConfig{}, err
}
}
rpt, err := dbToReport(dbReport)
if err != nil {
return re.ReportConfig{}, err
}
return rpt, nil
}
func (repo *PostgresRepository) UpdateReportSchedule(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
q := `
UPDATE report_config
SET start_datetime = :start_datetime, time = :time, recurring = :recurring,
recurring_period = :recurring_period, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id
RETURNING id, name, description, domain_id, config, metrics,
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
`
dbr, err := reportToDb(cfg)
if err != nil {
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
if err != nil {
return re.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
}
defer row.Close()
var dbReport dbReport
if row.Next() {
if err := row.StructScan(&dbReport); err != nil {
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
}
report, err := dbToReport(dbReport)
if err != nil {
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
return report, nil
}
func (repo *PostgresRepository) RemoveReportConfig(ctx context.Context, id string) error {
q := `
DELETE FROM report_config
WHERE id = $1;
`
result, err := repo.DB.ExecContext(ctx, q, id)
if err != nil {
return err
}
if _, err := result.RowsAffected(); err != nil {
return repoerr.ErrNotFound
}
return nil
}
func (repo *PostgresRepository) ListReportsConfig(ctx context.Context, pm re.PageMeta) (re.ReportConfigPage, error) {
listReportsQuery := `
SELECT id, name, description, domain_id, metrics, email, config,
start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status
FROM report_config rc %s %s;
`
pgData := ""
if pm.Limit != 0 {
pgData = "LIMIT :limit"
}
if pm.Offset != 0 {
pgData += " OFFSET :offset"
}
pq := pageReportQuery(pm)
q := fmt.Sprintf(listReportsQuery, pq, pgData)
rows, err := repo.DB.NamedQueryContext(ctx, q, pm)
if err != nil {
return re.ReportConfigPage{}, err
}
defer rows.Close()
cfgs := []re.ReportConfig{}
for rows.Next() {
var r dbReport
if err := rows.StructScan(&r); err != nil {
return re.ReportConfigPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
rpt, err := dbToReport(r)
if err != nil {
return re.ReportConfigPage{}, err
}
cfgs = append(cfgs, rpt)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM report_config rc %s;`, pq)
total, err := postgres.Total(ctx, repo.DB, cq, pm)
if err != nil {
return re.ReportConfigPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
pm.Total = total
ret := re.ReportConfigPage{
PageMeta: pm,
ReportConfigs: cfgs,
}
return ret, nil
}
func (repo *PostgresRepository) UpdateReportDue(ctx context.Context, id string, due time.Time) (re.ReportConfig, error) {
q := `
UPDATE report_config
SET time = :time, updated_at = :updated_at WHERE id = :id
RETURNING id, name, description, domain_id, config, metrics,
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
`
dbr := dbReport{
ID: id,
UpdatedAt: time.Now().UTC(),
Time: sql.NullTime{Time: due},
}
if !due.IsZero() {
dbr.Time.Valid = true
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
if err != nil {
return re.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
}
defer row.Close()
var dbReport dbReport
if row.Next() {
if err := row.StructScan(&dbReport); err != nil {
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
}
report, err := dbToReport(dbReport)
if err != nil {
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
return report, nil
}
func pageReportQuery(pm re.PageMeta) string {
var query []string
if pm.Status != re.AllStatus {
query = append(query, "rc.status = :status")
}
if pm.Domain != "" {
query = append(query, "rc.domain_id = :domain_id")
}
if pm.ScheduledBefore != nil {
query = append(query, "rc.time < :scheduled_before")
}
if pm.ScheduledAfter != nil {
query = append(query, "rc.time > :scheduled_after")
}
if pm.Name != "" {
query = append(query, "rc.name ILIKE '%' || :name || '%'")
}
var q string
if len(query) > 0 {
q = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
}
return q
}
+22 -144
View File
@@ -8,6 +8,7 @@ import (
"encoding/json"
"time"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/re"
"github.com/absmach/supermq/pkg/errors"
"github.com/lib/pq"
@@ -15,46 +16,26 @@ import (
// dbRule represents the database structure for a Rule.
type dbRule struct {
ID string `db:"id"`
Name string `db:"name"`
DomainID string `db:"domain_id"`
Metadata []byte `db:"metadata,omitempty"`
InputChannel string `db:"input_channel"`
InputTopic sql.NullString `db:"input_topic"`
LogicType re.ScriptType `db:"logic_type"`
LogicOutputs pq.Int32Array `db:"logic_output"`
LogicValue string `db:"logic_value"`
OutputChannel sql.NullString `db:"output_channel"`
OutputTopic sql.NullString `db:"output_topic"`
StartDateTime sql.NullTime `db:"start_datetime"`
Time sql.NullTime `db:"time"`
Recurring re.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"`
}
// 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"`
Time sql.NullTime `db:"time"`
Recurring re.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"`
Config []byte `db:"config,omitempty"`
Metrics []byte `db:"metrics"`
Email []byte `db:"email"`
ID string `db:"id"`
Name string `db:"name"`
DomainID string `db:"domain_id"`
Metadata []byte `db:"metadata,omitempty"`
InputChannel string `db:"input_channel"`
InputTopic sql.NullString `db:"input_topic"`
LogicType re.ScriptType `db:"logic_type"`
LogicOutputs pq.Int32Array `db:"logic_output"`
LogicValue string `db:"logic_value"`
OutputChannel sql.NullString `db:"output_channel"`
OutputTopic sql.NullString `db:"output_topic"`
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"`
}
func ruleToDb(r re.Rule) (dbRule, error) {
@@ -127,7 +108,7 @@ func dbToRule(dto dbRule) (re.Rule, error) {
},
OutputChannel: fromNullString(dto.OutputChannel),
OutputTopic: fromNullString(dto.OutputTopic),
Schedule: re.Schedule{
Schedule: schedule.Schedule{
StartDateTime: dto.StartDateTime.Time,
Time: dto.Time.Time,
Recurring: dto.Recurring,
@@ -141,109 +122,6 @@ func dbToRule(dto dbRule) (re.Rule, error) {
}, nil
}
func reportToDb(r re.ReportConfig) (dbReport, error) {
config := []byte("{}")
if r.Config != nil {
b, err := json.Marshal(r.Config)
if err != nil {
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
config = b
}
metrics := []byte("{}")
if r.Metrics != nil {
m, err := json.Marshal(r.Metrics)
if err != nil {
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
metrics = m
}
email := []byte("{}")
if r.Email != nil {
e, err := json.Marshal(r.Email)
if err != nil {
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
email = e
}
start := sql.NullTime{Time: r.Schedule.StartDateTime}
if !r.Schedule.StartDateTime.IsZero() {
start.Valid = true
}
t := sql.NullTime{Time: r.Schedule.Time}
if !r.Schedule.Time.IsZero() {
t.Valid = true
}
return dbReport{
ID: r.ID,
Name: r.Name,
Description: r.Description,
DomainID: r.DomainID,
StartDateTime: start,
Time: t,
Recurring: r.Schedule.Recurring,
RecurringPeriod: r.Schedule.RecurringPeriod,
Status: r.Status,
CreatedAt: r.CreatedAt,
CreatedBy: r.CreatedBy,
UpdatedAt: r.UpdatedAt,
UpdatedBy: r.UpdatedBy,
Config: config,
Metrics: metrics,
Email: email,
}, nil
}
func dbToReport(dto dbReport) (re.ReportConfig, error) {
var config re.MetricConfig
if dto.Config != nil {
if err := json.Unmarshal(dto.Config, &config); err != nil {
return re.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
}
var email re.EmailSetting
if dto.Email != nil {
if err := json.Unmarshal(dto.Email, &email); err != nil {
return re.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
}
var metrics []re.ReqMetric
if dto.Metrics != nil {
if err := json.Unmarshal(dto.Metrics, &metrics); err != nil {
return re.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
}
rpt := re.ReportConfig{
ID: dto.ID,
Name: dto.Name,
Description: dto.Description,
DomainID: dto.DomainID,
Config: &config,
Metrics: metrics,
Schedule: re.Schedule{
StartDateTime: dto.StartDateTime.Time,
Time: dto.Time.Time,
Recurring: dto.Recurring,
RecurringPeriod: dto.RecurringPeriod,
},
Email: &email,
Status: dto.Status,
CreatedAt: dto.CreatedAt,
CreatedBy: dto.CreatedBy,
UpdatedAt: dto.UpdatedAt,
UpdatedBy: dto.UpdatedBy,
}
return rpt, nil
}
func toNullString(value string) sql.NullString {
if value == "" {
return sql.NullString{Valid: false}
+16 -24
View File
@@ -5,15 +5,13 @@ package re
import (
"encoding/json"
"log/slog"
"strings"
"time"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/supermq/pkg/errors"
)
var ErrInvalidRecurringType = errors.New("invalid recurring type")
const protocol = "nats"
// ScriptOutput is the indicator for type of the logic
@@ -80,25 +78,19 @@ type (
)
type Rule struct {
ID string `json:"id"`
Name string `json:"name"`
DomainID string `json:"domain"`
Metadata Metadata `json:"metadata,omitempty"`
InputChannel string `json:"input_channel"`
InputTopic string `json:"input_topic"`
Logic Script `json:"logic"`
OutputChannel string `json:"output_channel,omitempty"`
OutputTopic string `json:"output_topic,omitempty"`
Schedule Schedule `json:"schedule"`
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"`
}
type RunInfo struct {
Level slog.Level
Details []slog.Attr
Message string
ID string `json:"id"`
Name string `json:"name"`
DomainID string `json:"domain"`
Metadata Metadata `json:"metadata,omitempty"`
InputChannel string `json:"input_channel"`
InputTopic string `json:"input_topic"`
Logic Script `json:"logic"`
OutputChannel string `json:"output_channel,omitempty"`
OutputTopic string `json:"output_topic,omitempty"`
Schedule schedule.Schedule `json:"schedule"`
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"`
}
+22 -417
View File
@@ -5,22 +5,20 @@ package re
import (
"context"
"fmt"
"strings"
"time"
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
"github.com/absmach/magistrala/pkg/reltime"
"github.com/absmach/magistrala/pkg/emailer"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/pkg/ticker"
"github.com/absmach/supermq"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/messaging"
"github.com/absmach/supermq/pkg/transformers/senml"
)
const limit = 1000
type Repository interface {
AddRule(ctx context.Context, r Rule) (Rule, error)
ViewRule(ctx context.Context, id string) (Rule, error)
@@ -30,33 +28,24 @@ type Repository interface {
UpdateRuleStatus(ctx context.Context, r Rule) (Rule, error)
ListRules(ctx context.Context, pm PageMeta) (Page, error)
UpdateRuleDue(ctx context.Context, id string, due time.Time) (Rule, error)
AddReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
ViewReportConfig(ctx context.Context, id string) (ReportConfig, error)
UpdateReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
UpdateReportSchedule(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
RemoveReportConfig(ctx context.Context, id string) error
UpdateReportConfigStatus(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
ListReportsConfig(ctx context.Context, pm PageMeta) (ReportConfigPage, error)
UpdateReportDue(ctx context.Context, id string, due time.Time) (ReportConfig, error)
}
// PageMeta contains page metadata that helps navigation.
type PageMeta struct {
Total uint64 `json:"total" db:"total"`
Offset uint64 `json:"offset" db:"offset"`
Limit uint64 `json:"limit" db:"limit"`
Dir string `json:"dir" db:"dir"`
Name string `json:"name" db:"name"`
InputChannel string `json:"input_channel,omitempty" db:"input_channel"`
InputTopic *string `json:"input_topic,omitempty" db:"input_topic"`
Scheduled *bool `json:"scheduled,omitempty"`
OutputChannel string `json:"output_channel,omitempty" db:"output_channel"`
Status Status `json:"status,omitempty" db:"status"`
Domain string `json:"domain_id,omitempty" db:"domain_id"`
ScheduledBefore *time.Time `json:"scheduled_before,omitempty" db:"scheduled_before"` // Filter rules scheduled before this time
ScheduledAfter *time.Time `json:"scheduled_after,omitempty" db:"scheduled_after"` // Filter rules scheduled after this time
Recurring *Recurring `json:"recurring,omitempty" db:"recurring"` // Filter by recurring type
Total uint64 `json:"total" db:"total"`
Offset uint64 `json:"offset" db:"offset"`
Limit uint64 `json:"limit" db:"limit"`
Dir string `json:"dir" db:"dir"`
Name string `json:"name" db:"name"`
InputChannel string `json:"input_channel,omitempty" db:"input_channel"`
InputTopic *string `json:"input_topic,omitempty" db:"input_topic"`
Scheduled *bool `json:"scheduled,omitempty"`
OutputChannel string `json:"output_channel,omitempty" db:"output_channel"`
Status Status `json:"status,omitempty" db:"status"`
Domain string `json:"domain_id,omitempty" db:"domain_id"`
ScheduledBefore *time.Time `json:"scheduled_before,omitempty" db:"scheduled_before"` // Filter rules scheduled before this time
ScheduledAfter *time.Time `json:"scheduled_after,omitempty" db:"scheduled_after"` // Filter rules scheduled after this time
Recurring *schedule.Recurring `json:"recurring,omitempty" db:"recurring"` // Filter by recurring type
}
type Page struct {
@@ -77,32 +66,22 @@ type Service interface {
EnableRule(ctx context.Context, session authn.Session, id string) (Rule, error)
DisableRule(ctx context.Context, session authn.Session, id string) (Rule, error)
AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
RemoveReportConfig(ctx context.Context, session authn.Session, id string) error
ListReportsConfig(ctx context.Context, session authn.Session, pm PageMeta) (ReportConfigPage, error)
EnableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error)
StartScheduler(ctx context.Context) error
}
type re struct {
repo Repository
runInfo chan RunInfo
runInfo chan pkglog.RunInfo
idp supermq.IDProvider
rePubSub messaging.PubSub
writersPub messaging.Publisher
alarmsPub messaging.Publisher
ticker Ticker
email Emailer
ticker ticker.Ticker
email emailer.Emailer
readers grpcReadersV1.ReadersServiceClient
}
func NewService(repo Repository, runInfo chan RunInfo, idp supermq.IDProvider, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, tck Ticker, emailer Emailer, readers grpcReadersV1.ReadersServiceClient) Service {
func NewService(repo Repository, runInfo chan pkglog.RunInfo, idp supermq.IDProvider, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, tck ticker.Ticker, emailer emailer.Emailer, readers grpcReadersV1.ReadersServiceClient) Service {
return &re{
repo: repo,
idp: idp,
@@ -229,377 +208,3 @@ func (re *re) DisableRule(ctx context.Context, session authn.Session, id string)
func (re *re) Cancel() error {
return nil
}
func (re *re) AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
id, err := re.idp.ID()
if err != nil {
return ReportConfig{}, err
}
now := time.Now()
cfg.ID = id
cfg.CreatedAt = now
cfg.CreatedBy = session.UserID
cfg.DomainID = session.DomainID
cfg.Status = EnabledStatus
if cfg.Schedule.StartDateTime.IsZero() {
cfg.Schedule.StartDateTime = now
}
cfg.Schedule.Time = cfg.Schedule.StartDateTime
reportConfig, err := re.repo.AddReportConfig(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrCreateEntity, err)
}
return reportConfig, nil
}
func (re *re) ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
cfg, err := re.repo.ViewReportConfig(ctx, id)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
return cfg, nil
}
func (re *re) UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
cfg.UpdatedAt = time.Now().UTC()
cfg.UpdatedBy = session.UserID
reportConfig, err := re.repo.UpdateReportConfig(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return reportConfig, nil
}
func (re *re) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
cfg.UpdatedAt = time.Now().UTC()
cfg.UpdatedBy = session.UserID
cfg.Schedule.Time = cfg.Schedule.StartDateTime
c, err := re.repo.UpdateReportSchedule(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return c, nil
}
func (re *re) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
if err := re.repo.RemoveReportConfig(ctx, id); err != nil {
return errors.Wrap(svcerr.ErrRemoveEntity, err)
}
return nil
}
func (re *re) ListReportsConfig(ctx context.Context, session authn.Session, pm PageMeta) (ReportConfigPage, error) {
pm.Domain = session.DomainID
page, err := re.repo.ListReportsConfig(ctx, pm)
if err != nil {
return ReportConfigPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
return page, nil
}
func (re *re) EnableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
status, err := ToStatus(Enabled)
if err != nil {
return ReportConfig{}, err
}
cfg := ReportConfig{
ID: id,
UpdatedAt: time.Now().UTC(),
UpdatedBy: session.UserID,
Status: status,
}
cfg, err = re.repo.UpdateReportConfigStatus(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return cfg, nil
}
func (re *re) DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
status, err := ToStatus(Disabled)
if err != nil {
return ReportConfig{}, err
}
cfg := ReportConfig{
ID: id,
UpdatedAt: time.Now().UTC(),
UpdatedBy: session.UserID,
Status: status,
}
cfg, err = re.repo.UpdateReportConfigStatus(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return cfg, nil
}
func (re *re) GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error) {
config.DomainID = session.DomainID
if config.Status != EnabledStatus {
return ReportPage{}, svcerr.ErrInvalidStatus
}
reportPage, err := re.generateReport(ctx, config, action)
if err != nil {
return ReportPage{}, err
}
return reportPage, nil
}
func (re *re) generateReport(ctx context.Context, cfg ReportConfig, action ReportAction) (ReportPage, error) {
genReportFile, err := generateFileFunc(action, cfg.Config.FileFormat)
if err != nil {
return ReportPage{}, err
}
agg := grpcReadersV1.Aggregation_AGGREGATION_UNSPECIFIED
switch cfg.Config.Aggregation.AggType {
case AggregationMAX:
agg = grpcReadersV1.Aggregation_MAX
case AggregationMIN:
agg = grpcReadersV1.Aggregation_MIN
case AggregationCOUNT:
agg = grpcReadersV1.Aggregation_COUNT
case AggregationAVG:
agg = grpcReadersV1.Aggregation_AVG
case AggregationSUM:
agg = grpcReadersV1.Aggregation_SUM
}
from, err := reltime.Parse(cfg.Config.From)
if err != nil {
return ReportPage{}, err
}
to, err := reltime.Parse(cfg.Config.To)
if err != nil {
return ReportPage{}, err
}
pm := &grpcReadersV1.PageMetadata{
Aggregation: agg,
Limit: limit,
From: float64(from.UnixMicro()),
To: float64(to.UnixNano()),
Interval: cfg.Config.Aggregation.Interval,
}
var mets []Metric
var reports []Report
for _, metric := range cfg.Metrics {
switch {
case len(metric.ClientIDs) != 0:
for _, clientID := range metric.ClientIDs {
mets = append(mets, Metric{
ChannelID: metric.ChannelID,
ClientID: clientID,
Name: metric.Name,
Subtopic: metric.Subtopic,
Protocol: metric.Protocol,
Format: metric.Format,
})
}
default:
mets = append(mets, Metric{
ChannelID: metric.ChannelID,
Name: metric.Name,
Subtopic: metric.Subtopic,
Protocol: metric.Protocol,
Format: metric.Format,
})
}
}
for _, metric := range mets {
sMsgs := []senml.Message{}
pm.Offset = uint64(0)
pm.Name = metric.Name
if metric.ClientID != "" {
pm.Publisher = metric.ClientID
}
if metric.Subtopic != "" {
pm.Subtopic = metric.Subtopic
}
if metric.Protocol != "" {
pm.Protocol = metric.Protocol
}
if metric.Format != "" {
pm.Format = metric.Format
}
msgs, err := re.readers.ReadMessages(ctx, &grpcReadersV1.ReadMessagesReq{
ChannelId: metric.ChannelID,
DomainId: cfg.DomainID,
PageMetadata: pm,
})
if err != nil {
return ReportPage{}, err
}
for _, msg := range msgs.Messages {
sMsgs = append(sMsgs, convertToSenml(msg.GetSenml()))
}
for msgs.GetTotal() > (pm.Offset + pm.Limit) {
pm.Offset = pm.Offset + pm.Limit
msgs, err := re.readers.ReadMessages(ctx, &grpcReadersV1.ReadMessagesReq{
ChannelId: metric.ChannelID,
DomainId: cfg.DomainID,
PageMetadata: pm,
})
if err != nil {
return ReportPage{}, err
}
for _, msg := range msgs.Messages {
sMsgs = append(sMsgs, convertToSenml(msg.GetSenml()))
}
}
reports = append(reports, convertToReports(metric, sMsgs)...)
}
switch {
case genReportFile != nil:
data, err := genReportFile(cfg.Config.Title, reports)
if err != nil {
return ReportPage{}, err
}
timeStr := strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")
filePrefix := cfg.Name
if filePrefix == "" {
filePrefix = "report"
}
fileName := fmt.Sprintf("%s_%s.%s", filePrefix, timeStr, cfg.Config.FileFormat.Extension())
file := ReportFile{
Name: fileName,
Data: data,
Format: cfg.Config.FileFormat,
}
switch action {
case EmailReport:
if err := re.emailReports(*cfg.Email, file); err != nil {
return ReportPage{}, errors.Wrap(err, svcerr.ErrCreateEntity)
}
return ReportPage{}, nil
default:
return ReportPage{
File: file,
}, nil
}
default:
return ReportPage{
From: from,
To: to,
Aggregation: cfg.Config.Aggregation,
Total: uint64(len(reports)),
Reports: reports,
}, nil
}
}
func generateFileFunc(action ReportAction, format Format) (func(string, []Report) ([]byte, error), error) {
switch action {
case DownloadReport, EmailReport:
switch format {
case PDF:
return generatePDFReport, nil
case CSV:
return generateCSVReport, nil
default:
return nil, errors.New("file format not supported")
}
default:
return nil, nil
}
}
func (re *re) emailReports(es EmailSetting, file ReportFile) error {
if err := es.Validate(); err != nil {
return errors.Wrap(svcerr.ErrMalformedEntity, err)
}
attachments := map[string][]byte{
file.Name: file.Data,
}
if err := re.email.SendEmailNotification(
es.To,
"",
es.Subject,
"",
"",
es.Content,
"",
attachments,
); err != nil {
return err
}
return nil
}
func convertToSenml(g *grpcReadersV1.SenMLMessage) senml.Message {
if g == nil {
return senml.Message{}
}
return senml.Message{
Protocol: g.Base.GetProtocol(),
Subtopic: g.Base.GetSubtopic(),
Publisher: g.Base.GetPublisher(),
Channel: g.Base.GetChannel(),
Name: g.GetName(),
Unit: g.GetUnit(),
Time: g.GetTime(),
UpdateTime: g.GetUpdateTime(),
Value: g.Value,
StringValue: g.StringValue,
DataValue: g.DataValue,
BoolValue: g.BoolValue,
Sum: g.Sum,
}
}
func convertToReports(metric Metric, senmlMsgs []senml.Message) []Report {
if metric.ClientID != "" {
return []Report{
{
Metric: metric,
Messages: senmlMsgs,
},
}
}
return groupReportsByPublisher(metric, senmlMsgs)
}
func groupReportsByPublisher(metric Metric, sMsgs []senml.Message) []Report {
publishers := map[string][]senml.Message{}
for _, msg := range sMsgs {
publishers[msg.Publisher] = append(publishers[msg.Publisher], msg)
}
var groupedReports []Report
for publisher, messages := range publishers {
gMetric := metric
gMetric.ClientID = publisher
groupedReports = append(groupedReports, Report{
Metric: gMetric,
Messages: messages,
})
}
return groupedReports
}
+30 -453
View File
@@ -11,6 +11,8 @@ import (
"github.com/0x6flab/namegenerator"
"github.com/absmach/magistrala/internal/testsutil"
pkglog "github.com/absmach/magistrala/pkg/logger"
pkgSch "github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/re"
"github.com/absmach/magistrala/re/mocks"
readmocks "github.com/absmach/magistrala/readers/mocks"
@@ -32,26 +34,15 @@ var (
ruleName = namegen.Generate()
ruleID = testsutil.GenerateUUID(&testing.T{})
inputChannel = "test.channel"
schedule = re.Schedule{
schedule = pkgSch.Schedule{
StartDateTime: time.Now().Add(-time.Hour),
Recurring: re.Daily,
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: time.Now().Add(-time.Hour),
}
reportName = namegen.Generate()
rptConfig = re.ReportConfig{
ID: testsutil.GenerateUUID(&testing.T{}),
Name: reportName,
DomainID: domainID,
Status: re.EnabledStatus,
Schedule: schedule,
CreatedBy: userID,
UpdatedBy: userID,
UpdatedAt: time.Now(),
}
)
func newService(t *testing.T, runInfo chan re.RunInfo) (re.Service, *mocks.Repository, *pubsubmocks.PubSub, *mocks.Ticker) {
func newService(t *testing.T, runInfo chan pkglog.RunInfo) (re.Service, *mocks.Repository, *pubsubmocks.PubSub, *mocks.Ticker) {
repo := new(mocks.Repository)
mockTicker := new(mocks.Ticker)
idProvider := uuid.NewMock()
@@ -62,7 +53,7 @@ func newService(t *testing.T, runInfo chan re.RunInfo) (re.Service, *mocks.Repos
}
func TestAddRule(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
ruleName := namegen.Generate()
now := time.Now().Add(time.Hour)
cases := []struct {
@@ -81,8 +72,8 @@ func TestAddRule(t *testing.T) {
rule: re.Rule{
Name: ruleName,
InputChannel: inputChannel,
Schedule: re.Schedule{
Recurring: re.Daily,
Schedule: pkgSch.Schedule{
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
},
@@ -91,8 +82,8 @@ func TestAddRule(t *testing.T) {
Name: ruleName,
ID: ruleID,
InputChannel: inputChannel,
Schedule: re.Schedule{
Recurring: re.Daily,
Schedule: pkgSch.Schedule{
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
},
@@ -111,8 +102,8 @@ func TestAddRule(t *testing.T) {
rule: re.Rule{
Name: ruleName,
InputChannel: inputChannel,
Schedule: re.Schedule{
Recurring: re.Daily,
Schedule: pkgSch.Schedule{
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
},
@@ -137,7 +128,7 @@ func TestAddRule(t *testing.T) {
}
func TestViewRule(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
now := time.Now().Add(time.Hour)
cases := []struct {
@@ -158,8 +149,8 @@ func TestViewRule(t *testing.T) {
Name: ruleName,
ID: ruleID,
InputChannel: inputChannel,
Schedule: re.Schedule{
Recurring: re.Daily,
Schedule: pkgSch.Schedule{
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
},
@@ -195,7 +186,7 @@ func TestViewRule(t *testing.T) {
}
func TestUpdateRule(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
newName := namegen.Generate()
now := time.Now().Add(time.Hour)
@@ -216,8 +207,8 @@ func TestUpdateRule(t *testing.T) {
Name: newName,
ID: ruleID,
InputChannel: inputChannel,
Schedule: re.Schedule{
Recurring: re.Daily,
Schedule: pkgSch.Schedule{
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
},
@@ -229,8 +220,8 @@ func TestUpdateRule(t *testing.T) {
Name: newName,
ID: ruleID,
InputChannel: inputChannel,
Schedule: re.Schedule{
Recurring: re.Daily,
Schedule: pkgSch.Schedule{
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
},
@@ -252,8 +243,8 @@ func TestUpdateRule(t *testing.T) {
Name: ruleName,
ID: ruleID,
InputChannel: inputChannel,
Schedule: re.Schedule{
Recurring: re.Daily,
Schedule: pkgSch.Schedule{
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
},
@@ -280,7 +271,7 @@ func TestUpdateRule(t *testing.T) {
}
func TestListRules(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
numRules := 50
now := time.Now().Add(time.Hour)
var rules []re.Rule
@@ -292,8 +283,8 @@ func TestListRules(t *testing.T) {
Status: re.EnabledStatus,
CreatedAt: now,
CreatedBy: userID,
Schedule: re.Schedule{
Recurring: re.Daily,
Schedule: pkgSch.Schedule{
Recurring: pkgSch.Daily,
Time: now.Add(1 * time.Hour),
RecurringPeriod: 1,
StartDateTime: now.Add(-1 * time.Hour),
@@ -385,7 +376,7 @@ func TestListRules(t *testing.T) {
}
func TestRemoveRule(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
cases := []struct {
desc string
@@ -425,7 +416,7 @@ func TestRemoveRule(t *testing.T) {
}
func TestEnableRule(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
now := time.Now()
@@ -484,7 +475,7 @@ func TestEnableRule(t *testing.T) {
}
func TestDisableRule(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
now := time.Now()
@@ -543,7 +534,7 @@ func TestDisableRule(t *testing.T) {
}
func TestHandle(t *testing.T) {
svc, repo, pubmocks, _ := newService(t, make(chan re.RunInfo))
svc, repo, pubmocks, _ := newService(t, make(chan pkglog.RunInfo))
now := time.Now()
scheduled := false
cases := []struct {
@@ -600,7 +591,6 @@ func TestHandle(t *testing.T) {
}
})
repoCall1 := pubmocks.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(tc.publishErr)
repoCall2 := repo.On("ListReportsConfig", mock.Anything, mock.Anything).Return(re.ReportConfigPage{}, nil)
err = svc.Handle(tc.message)
assert.Nil(t, err)
@@ -609,424 +599,13 @@ func TestHandle(t *testing.T) {
repoCall.Unset()
repoCall1.Unset()
repoCall2.Unset()
})
}
}
func TestAddReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
cases := []struct {
desc string
session authn.Session
cfg re.ReportConfig
res re.ReportConfig
err error
}{
{
desc: "Add report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: re.ReportConfig{
Name: reportName,
Schedule: schedule,
},
res: rptConfig,
err: nil,
},
{
desc: "Add report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: re.ReportConfig{
Name: reportName,
Schedule: schedule,
},
err: repoerr.ErrCreateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.AddReportConfig(context.Background(), tc.session, tc.cfg)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.NotEmpty(t, res.ID, "expected non-empty result in ID")
assert.Equal(t, tc.cfg.Name, res.Name)
assert.Equal(t, tc.cfg.Schedule, res.Schedule)
}
defer repoCall.Unset()
})
}
}
func TestViewReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
res re.ReportConfig
err error
}{
{
desc: "view report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
res: rptConfig,
err: nil,
},
{
desc: "view report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("ViewReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.ViewReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestUpdateReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
newName := namegen.Generate()
now := time.Now().Add(time.Hour)
cases := []struct {
desc string
session authn.Session
cfg re.ReportConfig
res re.ReportConfig
err error
}{
{
desc: "update report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: re.ReportConfig{
Name: newName,
ID: rptConfig.ID,
Schedule: schedule,
},
res: re.ReportConfig{
Name: newName,
ID: rptConfig.ID,
DomainID: rptConfig.DomainID,
Status: rptConfig.Status,
Schedule: rptConfig.Schedule,
UpdatedAt: now,
UpdatedBy: userID,
},
err: nil,
},
{
desc: "update report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: re.ReportConfig{
Name: rptConfig.Name,
ID: rptConfig.ID,
Schedule: schedule,
},
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.UpdateReportConfig(context.Background(), tc.session, tc.cfg)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestListReportsConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
numConfigs := 50
now := time.Now().Add(time.Hour)
var configs []re.ReportConfig
for i := 0; i < numConfigs; i++ {
c := re.ReportConfig{
ID: testsutil.GenerateUUID(t),
Name: namegen.Generate(),
DomainID: domainID,
Status: re.EnabledStatus,
CreatedAt: now,
CreatedBy: userID,
Schedule: schedule,
}
configs = append(configs, c)
}
cases := []struct {
desc string
session authn.Session
pageMeta re.PageMeta
res re.ReportConfigPage
err error
}{
{
desc: "list report configs successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: re.PageMeta{},
res: re.ReportConfigPage{
PageMeta: re.PageMeta{
Total: uint64(numConfigs),
Offset: 0,
Limit: 10,
},
ReportConfigs: configs[0:10],
},
err: nil,
},
{
desc: "list report configs successfully with limit",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: re.PageMeta{
Limit: 100,
},
res: re.ReportConfigPage{
PageMeta: re.PageMeta{
Total: uint64(numConfigs),
Offset: 0,
Limit: 100,
},
ReportConfigs: configs[0:numConfigs],
},
err: nil,
},
{
desc: "list report configs successfully with offset",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: re.PageMeta{
Offset: 20,
Limit: 10,
},
res: re.ReportConfigPage{
PageMeta: re.PageMeta{
Total: uint64(numConfigs),
Offset: 20,
Limit: 10,
},
ReportConfigs: configs[20:30],
},
err: nil,
},
{
desc: "list report configs with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: re.PageMeta{},
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("ListReportsConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.ListReportsConfig(context.Background(), tc.session, tc.pageMeta)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestRemoveReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
err error
}{
{
desc: "remove report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: nil,
},
{
desc: "remove report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: svcerr.ErrRemoveEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("RemoveReportConfig", mock.Anything, mock.Anything).Return(tc.err)
err := svc.RemoveReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
defer repoCall.Unset()
})
}
}
func TestEnableReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
status re.Status
res re.ReportConfig
err error
}{
{
desc: "enable report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: re.EnabledStatus,
res: rptConfig,
err: nil,
},
{
desc: "enable report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: re.EnabledStatus,
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfigStatus", context.Background(), mock.Anything).Return(tc.res, tc.err)
res, err := svc.EnableReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestDisableReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
status re.Status
res re.ReportConfig
err error
}{
{
desc: "disable report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: re.DisabledStatus,
res: re.ReportConfig{
ID: rptConfig.ID,
Name: rptConfig.Name,
DomainID: rptConfig.DomainID,
Status: re.DisabledStatus,
Schedule: schedule,
UpdatedBy: userID,
UpdatedAt: time.Now(),
},
err: nil,
},
{
desc: "disable report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: re.DisabledStatus,
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfigStatus", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.DisableReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestStartScheduler(t *testing.T) {
now := time.Now().Truncate(time.Minute)
ri := make(chan re.RunInfo)
ri := make(chan pkglog.RunInfo)
svc, repo, _, ticker := newService(t, ri)
ctxCases := []struct {
@@ -1078,7 +657,6 @@ func TestStartScheduler(t *testing.T) {
for _, tc := range ctxCases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("ListRules", mock.Anything, mock.Anything).Return(tc.page, tc.listErr)
repoCall1 := repo.On("ListReportsConfig", mock.Anything, mock.Anything).Return(re.ReportConfigPage{}, nil)
tickChan := make(chan time.Time)
tickCall := ticker.On("Tick").Return((<-chan time.Time)(tickChan))
tickCall1 := ticker.On("Stop").Return()
@@ -1093,7 +671,6 @@ func TestStartScheduler(t *testing.T) {
err := <-errc
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v but got %v", tc.err, err))
repoCall.Unset()
repoCall1.Unset()
tickCall.Unset()
tickCall1.Unset()
})
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package api contains API-related concerns: endpoint definitions, middlewares
// and all resource representations.
package api
+238
View File
@@ -0,0 +1,238 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"github.com/absmach/magistrala/reports"
api "github.com/absmach/supermq/api/http"
"github.com/absmach/supermq/pkg/authn"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/go-kit/kit/endpoint"
)
func generateReportEndpoint(svc reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(generateReportReq)
if err := req.validate(); err != nil {
return generateReportResp{}, err
}
res, err := svc.GenerateReport(ctx, session, reports.ReportConfig{
Name: req.Name,
DomainID: req.DomainID,
Config: req.Config,
Metrics: req.Metrics,
Email: req.Email,
}, req.action)
if err != nil {
return generateReportResp{}, err
}
switch req.action {
case reports.DownloadReport:
return downloadReportResp{
File: res.File,
}, nil
case reports.EmailReport:
return emailReportResp{}, nil
default:
return generateReportResp{
Total: res.Total,
From: res.From,
To: res.To,
Aggregation: res.Aggregation,
Reports: res.Reports,
}, nil
}
}
}
func listReportsConfigEndpoint(svc reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(listReportsConfigReq)
if err := req.validate(); err != nil {
return listReportsConfigRes{}, err
}
page, err := svc.ListReportsConfig(ctx, session, req.PageMeta)
if err != nil {
return listReportsConfigRes{}, err
}
return listReportsConfigRes{
pageRes: pageRes{
Limit: page.Limit,
Offset: page.Offset,
Total: page.Total,
},
ReportConfigs: page.ReportConfigs,
}, nil
}
}
func deleteReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(deleteReportConfigReq)
if err := req.validate(); err != nil {
return deleteReportConfigRes{}, err
}
err := svc.RemoveReportConfig(ctx, session, req.ID)
if err != nil {
return deleteReportConfigRes{false}, err
}
return deleteReportConfigRes{true}, nil
}
}
func updateReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(updateReportConfigReq)
if err := req.validate(); err != nil {
return updateReportConfigRes{}, err
}
cfg, err := svc.UpdateReportConfig(ctx, session, req.ReportConfig)
if err != nil {
return updateReportConfigRes{}, err
}
return updateReportConfigRes{ReportConfig: cfg}, nil
}
}
func updateReportScheduleEndpoint(s reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(updateReportScheduleReq)
if err := req.validate(); err != nil {
return updateReportConfigRes{}, err
}
rpt := reports.ReportConfig{
ID: req.id,
Schedule: req.Schedule,
}
updatedReport, err := s.UpdateReportSchedule(ctx, session, rpt)
if err != nil {
return updateReportConfigRes{}, err
}
return updateReportConfigRes{ReportConfig: updatedReport}, nil
}
}
func viewReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(viewReportConfigReq)
if err := req.validate(); err != nil {
return viewReportConfigRes{}, err
}
cfg, err := svc.ViewReportConfig(ctx, session, req.ID)
if err != nil {
return viewReportConfigRes{}, err
}
return viewReportConfigRes{ReportConfig: cfg}, nil
}
}
func addReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(addReportConfigReq)
if err := req.validate(); err != nil {
return addReportConfigRes{}, err
}
cfg, err := svc.AddReportConfig(ctx, session, req.ReportConfig)
if err != nil {
return addReportConfigRes{}, err
}
return addReportConfigRes{
ReportConfig: cfg,
created: true,
}, nil
}
}
func enableReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(updateReportStatusReq)
if err := req.validate(); err != nil {
return updateReportConfigRes{}, err
}
cfg, err := svc.EnableReportConfig(ctx, session, req.id)
if err != nil {
return updateReportConfigRes{}, err
}
return updateReportConfigRes{ReportConfig: cfg}, nil
}
}
func disableReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
req := request.(updateReportStatusReq)
if err := req.validate(); err != nil {
return updateReportConfigRes{}, err
}
cfg, err := svc.DisableReportConfig(ctx, session, req.id)
if err != nil {
return updateReportConfigRes{}, err
}
return updateReportConfigRes{ReportConfig: cfg}, nil
}
}
+813
View File
@@ -0,0 +1,813 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api_test
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/0x6flab/namegenerator"
"github.com/absmach/magistrala/internal/testsutil"
pkgSch "github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/reports"
"github.com/absmach/magistrala/reports/api"
"github.com/absmach/magistrala/reports/mocks"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/auth"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const contentType = "application/json"
var (
namegen = namegenerator.NewGenerator()
domainID = testsutil.GenerateUUID(&testing.T{})
userID = testsutil.GenerateUUID(&testing.T{})
validID = testsutil.GenerateUUID(&testing.T{})
validToken = "valid"
invalidToken = "invalid"
now = time.Now().UTC().Truncate(time.Minute)
schedule = pkgSch.Schedule{
StartDateTime: now.Add(-1 * time.Hour),
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: now,
}
reportConfig = reports.ReportConfig{
ID: validID,
Name: namegen.Generate(),
DomainID: domainID,
Schedule: schedule,
Status: reports.EnabledStatus,
Metrics: []reports.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
},
Config: &reports.MetricConfig{
From: "now()-1h",
To: "now()",
Title: title,
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
},
Email: &reports.EmailSetting{
To: []string{"test@example.com"},
Subject: "Test Report",
},
}
title = "test_title"
)
type testRequest struct {
client *http.Client
method string
url string
contentType string
token string
body io.Reader
}
func (tr testRequest) make() (*http.Response, error) {
req, err := http.NewRequest(tr.method, tr.url, tr.body)
if err != nil {
return nil, err
}
if tr.token != "" {
req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token)
}
if tr.contentType != "" {
req.Header.Set("Content-Type", tr.contentType)
}
req.Header.Set("Referer", "http://localhost")
return tr.client.Do(req)
}
func newReportsServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
svc := new(mocks.Service)
authn := new(authnmocks.Authentication)
logger := smqlog.NewMock()
mux := chi.NewRouter()
api.MakeHandler(svc, authn, mux, logger, "")
return httptest.NewServer(mux), svc, authn
}
func toJSON(data any) string {
jsonData, err := json.Marshal(data)
if err != nil {
return ""
}
return string(jsonData)
}
func TestAddReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
cfg reports.ReportConfig
domainID string
token string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcRes reports.ReportConfig
svcErr error
err error
}{
{
desc: "add report config successfully",
cfg: reportConfig,
token: validToken,
contentType: contentType,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
status: http.StatusCreated,
svcRes: reportConfig,
},
{
desc: "add report config with invalid token",
cfg: reportConfig,
token: invalidToken,
authnRes: smqauthn.Session{},
domainID: domainID,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "add report config with empty token",
token: "",
authnRes: smqauthn.Session{},
domainID: domainID,
cfg: reportConfig,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "add report config with empty domainID",
token: validToken,
cfg: reportConfig,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "add report config with invalid content type",
token: validToken,
domainID: domainID,
cfg: reportConfig,
contentType: "application/xml",
status: http.StatusUnsupportedMediaType,
err: apiutil.ErrUnsupportedContentType,
},
{
desc: "add report config with service error",
token: validToken,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
cfg: reportConfig,
contentType: contentType,
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
data := toJSON(tc.cfg)
req := testRequest{
client: ts.Client(),
method: http.MethodPost,
url: fmt.Sprintf("%s/%s/reports/configs", ts.URL, tc.domainID),
contentType: tc.contentType,
token: tc.token,
body: strings.NewReader(data),
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("AddReportConfig", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcRes, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
id string
domainID string
token string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcRes reports.ReportConfig
svcErr error
err error
}{
{
desc: "view report config successfully",
id: validID,
token: validToken,
contentType: contentType,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
status: http.StatusOK,
svcRes: reportConfig,
},
{
desc: "view report config with invalid token",
id: validID,
token: invalidToken,
authnRes: smqauthn.Session{},
domainID: domainID,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "view report config with empty token",
token: "",
authnRes: smqauthn.Session{},
domainID: domainID,
id: validID,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "view report config with empty domainID",
token: validToken,
id: validID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "view report config with service error",
token: validToken,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
id: validID,
contentType: contentType,
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodGet,
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
contentType: tc.contentType,
token: tc.token,
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("ViewReportConfig", mock.Anything, tc.authnRes, tc.id).Return(tc.svcRes, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestListReportsConfigEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
query string
domainID string
token string
session smqauthn.Session
listReportsResponse reports.ReportConfigPage
status int
authnErr error
err error
}{
{
desc: "list reports config successfully",
domainID: domainID,
token: validToken,
status: http.StatusOK,
listReportsResponse: reports.ReportConfigPage{
ReportConfigs: []reports.ReportConfig{reportConfig},
PageMeta: reports.PageMeta{Total: 1},
},
err: nil,
},
{
desc: "list reports config with empty token",
domainID: domainID,
token: "",
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "list reports config with invalid token",
domainID: domainID,
token: invalidToken,
status: http.StatusUnauthorized,
authnErr: svcerr.ErrAuthentication,
err: svcerr.ErrAuthentication,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodGet,
url: ts.URL + "/" + tc.domainID + "/reports/configs?" + tc.query,
contentType: contentType,
token: tc.token,
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("ListReportsConfig", mock.Anything, tc.session, mock.Anything).Return(tc.listReportsResponse, tc.err)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var bodyRes respBody
err = json.NewDecoder(res.Body).Decode(&bodyRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if bodyRes.Err != "" || bodyRes.Message != "" {
err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
token string
id string
domainID string
updateReq reports.ReportConfig
contentType string
session smqauthn.Session
svcResp reports.ReportConfig
svcErr error
status int
authnErr error
err error
}{
{
desc: "update report config successfully",
token: validToken,
domainID: domainID,
id: validID,
updateReq: reportConfig,
contentType: contentType,
svcResp: reportConfig,
status: http.StatusOK,
err: nil,
},
{
desc: "update report config with invalid token",
token: invalidToken,
session: smqauthn.Session{},
domainID: domainID,
id: validID,
updateReq: reportConfig,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "update report config with empty token",
token: "",
session: smqauthn.Session{},
domainID: domainID,
id: validID,
updateReq: reportConfig,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "update report config with empty domainID",
token: validToken,
id: validID,
updateReq: reportConfig,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "update report config with invalid content type",
token: validToken,
id: validID,
domainID: domainID,
updateReq: reportConfig,
contentType: "application/xml",
svcResp: reportConfig,
status: http.StatusUnsupportedMediaType,
err: apiutil.ErrUnsupportedContentType,
},
{
desc: "update report config with service error",
token: validToken,
id: validID,
domainID: domainID,
updateReq: reportConfig,
contentType: contentType,
svcResp: reports.ReportConfig{},
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
data := toJSON(tc.updateReq)
req := testRequest{
client: ts.Client(),
method: http.MethodPatch,
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
contentType: tc.contentType,
token: tc.token,
body: strings.NewReader(data),
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("UpdateReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcResp, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDeleteReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
token string
id string
domainID string
session smqauthn.Session
svcErr error
status int
authnErr error
err error
}{
{
desc: "delete report config successfully",
token: validToken,
domainID: domainID,
id: validID,
svcErr: nil,
status: http.StatusNoContent,
err: nil,
},
{
desc: "delete report config with invalid token",
token: invalidToken,
session: smqauthn.Session{},
domainID: domainID,
id: validID,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "delete report config with empty token",
token: "",
session: smqauthn.Session{},
domainID: domainID,
id: validID,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "delete report config with empty domainID",
token: validToken,
id: validID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "delete report config with service error",
token: validToken,
id: validID,
domainID: domainID,
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodDelete,
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
token: tc.token,
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("RemoveReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestEnableReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
token string
id string
domainID string
session smqauthn.Session
svcResp reports.ReportConfig
svcErr error
status int
authnErr error
err error
}{
{
desc: "enable report config successfully",
token: validToken,
domainID: domainID,
id: validID,
svcResp: reportConfig,
svcErr: nil,
status: http.StatusOK,
err: nil,
},
{
desc: "enable report config with invalid token",
token: invalidToken,
session: smqauthn.Session{},
domainID: domainID,
id: validID,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "enable report config with empty token",
token: "",
session: smqauthn.Session{},
domainID: domainID,
id: validID,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "enable report config with empty domainID",
token: validToken,
id: validID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "enable report config with service error",
token: validToken,
id: validID,
domainID: domainID,
svcResp: reports.ReportConfig{},
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
{
desc: "enable report config with empty id",
token: validToken,
id: "",
domainID: domainID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodPost,
url: fmt.Sprintf("%s/%s/reports/configs/%s/enable", ts.URL, tc.domainID, tc.id),
token: tc.token,
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("EnableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDisableReportConfigEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
token string
id string
domainID string
session smqauthn.Session
svcResp reports.ReportConfig
svcErr error
status int
authnErr error
err error
}{
{
desc: "disable report config successfully",
token: validToken,
domainID: domainID,
id: validID,
svcResp: reportConfig,
svcErr: nil,
status: http.StatusOK,
err: nil,
},
{
desc: "disable report config with invalid token",
token: invalidToken,
session: smqauthn.Session{},
domainID: domainID,
id: validID,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "disable report config with empty token",
token: "",
session: smqauthn.Session{},
domainID: domainID,
id: validID,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "disable report config with empty domainID",
token: validToken,
id: validID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "disable report config with service error",
token: validToken,
id: validID,
domainID: domainID,
svcResp: reports.ReportConfig{},
svcErr: svcerr.ErrAuthorization,
status: http.StatusForbidden,
err: svcerr.ErrAuthorization,
},
{
desc: "disable report config with empty id",
token: validToken,
id: "",
domainID: domainID,
status: http.StatusBadRequest,
err: apiutil.ErrMissingID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodPost,
url: fmt.Sprintf("%s/%s/reports/configs/%s/disable", ts.URL, tc.domainID, tc.id),
token: tc.token,
}
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
svcCall := svc.On("DisableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
type respBody struct {
Err string `json:"error"`
Message string `json:"message"`
Total uint64 `json:"total"`
ID string `json:"id"`
Status reports.Status `json:"status"`
}
+171
View File
@@ -0,0 +1,171 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import (
"fmt"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/reports"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
)
const (
maxLimitSize = 1000
MaxNameSize = 1024
MaxTitleSize = 37
errInvalidMetric = "invalid metric[%d]: %w"
)
var (
errInvalidReportAction = errors.New("invalid report action")
errMetricsNotProvided = errors.New("metrics not provided")
errMissingReportConfig = errors.New("missing report config")
errMissingReportEmailConfig = errors.New("missing report email config")
errInvalidRecurringPeriod = errors.New("invalid recurring period")
errTitleSize = errors.New("invalid title size")
)
type addReportConfigReq struct {
reports.ReportConfig `json:",inline"`
}
func (req addReportConfigReq) validate() error {
if req.Name == "" {
return apiutil.ErrMissingName
}
return validateReportConfig(req.ReportConfig, false, false)
}
type viewReportConfigReq struct {
ID string `json:"id"`
}
func (req viewReportConfigReq) validate() error {
if req.ID == "" {
return apiutil.ErrMissingID
}
return nil
}
type listReportsConfigReq struct {
reports.PageMeta `json:",inline"`
}
func (req listReportsConfigReq) validate() error {
if req.Limit > maxLimitSize {
return svcerr.ErrMalformedEntity
}
return nil
}
type updateReportConfigReq struct {
reports.ReportConfig `json:",inline"`
}
func (req updateReportConfigReq) validate() error {
if req.ID == "" {
return apiutil.ErrMissingID
}
return validateReportConfig(req.ReportConfig, false, false)
}
type updateReportScheduleReq struct {
id string
Schedule schedule.Schedule `json:"schedule,omitempty"`
}
func (req updateReportScheduleReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
return nil
}
type deleteReportConfigReq struct {
ID string `json:"id"`
}
func (req deleteReportConfigReq) validate() error {
if req.ID == "" {
return apiutil.ErrMissingID
}
return nil
}
type generateReportReq struct {
reports.ReportConfig
action reports.ReportAction
}
func (req generateReportReq) validate() error {
if len(req.Config.Title) > MaxTitleSize {
return errors.Wrap(apiutil.ErrValidation, errTitleSize)
}
switch req.action {
case reports.ViewReport, reports.DownloadReport:
return validateReportConfig(req.ReportConfig, true, true)
case reports.EmailReport:
return validateReportConfig(req.ReportConfig, false, true)
default:
return errors.Wrap(apiutil.ErrValidation, errInvalidReportAction)
}
}
type updateReportStatusReq struct {
id string
}
func (req updateReportStatusReq) validate() error {
if req.id == "" {
return apiutil.ErrMissingID
}
return nil
}
func validateReportConfig(req reports.ReportConfig, skipEmailValidation bool, skipSchedularValidation bool) error {
if len(req.Metrics) == 0 {
return errors.Wrap(apiutil.ErrValidation, errMetricsNotProvided)
}
for i, metric := range req.Metrics {
if err := metric.Validate(); err != nil {
return errors.Wrap(apiutil.ErrValidation, fmt.Errorf(errInvalidMetric, i+1, err))
}
}
if req.Config == nil {
return errors.Wrap(errMissingReportConfig, apiutil.ErrValidation)
}
if err := req.Config.Validate(); err != nil {
return errors.Wrap(err, apiutil.ErrValidation)
}
if skipEmailValidation {
return nil
}
if req.Email == nil {
return errors.Wrap(errMissingReportEmailConfig, apiutil.ErrValidation)
}
if err := req.Email.Validate(); err != nil {
return errors.Wrap(apiutil.ErrValidation, err)
}
if skipSchedularValidation {
return nil
}
return validateScheduler(req.Schedule)
}
func validateScheduler(sch schedule.Schedule) error {
if sch.Recurring != schedule.None && sch.RecurringPeriod < 1 {
return errors.Wrap(apiutil.ErrValidation, errInvalidRecurringPeriod)
}
return nil
}
+167
View File
@@ -0,0 +1,167 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import (
"net/http"
"time"
"github.com/absmach/magistrala/reports"
"github.com/absmach/supermq"
)
var (
_ supermq.Response = (*addReportConfigRes)(nil)
_ supermq.Response = (*viewReportConfigRes)(nil)
_ supermq.Response = (*updateReportConfigRes)(nil)
_ supermq.Response = (*deleteReportConfigRes)(nil)
_ supermq.Response = (*listReportsConfigRes)(nil)
)
type pageRes struct {
Limit uint64 `json:"limit,omitempty"`
Offset uint64 `json:"offset"`
Total uint64 `json:"total"`
}
type generateReportResp struct {
Total uint64 `json:"total"`
From time.Time `json:"from,omitempty"`
To time.Time `json:"to,omitempty"`
Aggregation reports.AggConfig `json:"aggregation,omitempty"`
Reports []reports.Report `json:"reports,omitempty"`
}
func (res generateReportResp) Code() int {
return http.StatusCreated
}
func (res generateReportResp) Headers() map[string]string {
return map[string]string{}
}
func (res generateReportResp) Empty() bool {
return false
}
type addReportConfigRes struct {
reports.ReportConfig `json:",inline"`
created bool
}
func (res addReportConfigRes) Code() int {
if res.created {
return http.StatusCreated
}
return http.StatusOK
}
func (res addReportConfigRes) Headers() map[string]string {
if res.created {
return map[string]string{}
}
return map[string]string{}
}
func (res addReportConfigRes) Empty() bool {
return false
}
type viewReportConfigRes struct {
reports.ReportConfig `json:",inline"`
}
func (res viewReportConfigRes) Code() int {
return http.StatusOK
}
func (res viewReportConfigRes) Headers() map[string]string {
return map[string]string{}
}
func (res viewReportConfigRes) Empty() bool {
return false
}
type updateReportConfigRes struct {
reports.ReportConfig `json:",inline"`
}
func (res updateReportConfigRes) Code() int {
return http.StatusOK
}
func (res updateReportConfigRes) Headers() map[string]string {
return map[string]string{}
}
func (res updateReportConfigRes) Empty() bool {
return false
}
type deleteReportConfigRes struct {
deleted bool
}
func (res deleteReportConfigRes) Code() int {
if res.deleted {
return http.StatusNoContent
}
return http.StatusOK
}
func (res deleteReportConfigRes) Headers() map[string]string {
return map[string]string{}
}
func (res deleteReportConfigRes) Empty() bool {
return true
}
type listReportsConfigRes struct {
pageRes
ReportConfigs []reports.ReportConfig `json:"report_configs"`
}
func (res listReportsConfigRes) Code() int {
return http.StatusOK
}
func (res listReportsConfigRes) Headers() map[string]string {
return map[string]string{}
}
func (res listReportsConfigRes) Empty() bool {
return false
}
type downloadReportResp struct {
File reports.ReportFile
}
func (res downloadReportResp) Code() int {
return http.StatusOK
}
func (res downloadReportResp) Headers() map[string]string {
return map[string]string{}
}
func (res downloadReportResp) Empty() bool {
return false
}
type emailReportResp struct{}
func (res emailReportResp) Code() int {
return http.StatusOK
}
func (res emailReportResp) Headers() map[string]string {
return map[string]string{}
}
func (res emailReportResp) Empty() bool {
return true
}
+247
View File
@@ -0,0 +1,247 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strings"
"github.com/absmach/magistrala/reports"
"github.com/absmach/supermq"
api "github.com/absmach/supermq/api/http"
apiutil "github.com/absmach/supermq/api/http/util"
mgauthn "github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
"github.com/go-chi/chi/v5"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
const (
reportIdKey = "reportID"
statusKey = "status"
actionKey = "action"
defAction = "view"
)
// MakeHandler creates an HTTP handler for the service endpoints.
func MakeHandler(svc reports.Service, authn mgauthn.Authentication, mux *chi.Mux, logger *slog.Logger, instanceID string) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
}
mux.Group(func(r chi.Router) {
r.Use(api.AuthenticateMiddleware(authn, true))
r.Route("/{domainID}", func(r chi.Router) {
r.Route("/reports", func(r chi.Router) {
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
generateReportEndpoint(svc),
decodeGenerateReportRequest,
encodeFileDownloadResponse,
opts...,
), "generate_report").ServeHTTP)
r.Route("/configs", func(r chi.Router) {
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
addReportConfigEndpoint(svc),
decodeAddReportConfigRequest,
api.EncodeResponse,
opts...,
), "add_report_config").ServeHTTP)
r.Get("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
viewReportConfigEndpoint(svc),
decodeViewReportConfigRequest,
api.EncodeResponse,
opts...,
), "view_report_config").ServeHTTP)
r.Patch("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
updateReportConfigEndpoint(svc),
decodeUpdateReportConfigRequest,
api.EncodeResponse,
opts...,
), "update_report_config").ServeHTTP)
r.Patch("/{reportID}/schedule", otelhttp.NewHandler(kithttp.NewServer(
updateReportScheduleEndpoint(svc),
decodeUpdateReportScheduleRequest,
api.EncodeResponse,
opts...,
), "update_report_scheduler").ServeHTTP)
r.Delete("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
deleteReportConfigEndpoint(svc),
decodeDeleteReportConfigRequest,
api.EncodeResponse,
opts...,
), "delete_report_config").ServeHTTP)
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
listReportsConfigEndpoint(svc),
decodeListReportsConfigRequest,
api.EncodeResponse,
opts...,
), "list_reports_config").ServeHTTP)
r.Post("/{reportID}/enable", otelhttp.NewHandler(kithttp.NewServer(
enableReportConfigEndpoint(svc),
decodeUpdateReportStatusRequest,
api.EncodeResponse,
opts...,
), "enable_report_config").ServeHTTP)
r.Post("/{reportID}/disable", otelhttp.NewHandler(kithttp.NewServer(
disableReportConfigEndpoint(svc),
decodeUpdateReportStatusRequest,
api.EncodeResponse,
opts...,
), "disable_report_config").ServeHTTP)
})
})
})
})
mux.Get("/health", supermq.Health("rule_engine", instanceID))
mux.Handle("/metrics", promhttp.Handler())
return mux
}
func decodeGenerateReportRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
a, err := apiutil.ReadStringQuery(r, actionKey, defAction)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
action, err := reports.ToReportAction(a)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
req := generateReportReq{
action: action,
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(err, apiutil.ErrValidation)
}
return req, nil
}
func decodeAddReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
var config reports.ReportConfig
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
return nil, errors.Wrap(err, apiutil.ErrValidation)
}
return addReportConfigReq{ReportConfig: config}, nil
}
func decodeViewReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
id := chi.URLParam(r, reportIdKey)
return viewReportConfigReq{ID: id}, nil
}
func decodeUpdateReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
var config reports.ReportConfig
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
return nil, errors.Wrap(err, apiutil.ErrValidation)
}
config.ID = chi.URLParam(r, reportIdKey)
return updateReportConfigReq{ReportConfig: config}, nil
}
func decodeUpdateReportScheduleRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
}
req := updateReportScheduleReq{
id: chi.URLParam(r, reportIdKey),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err))
}
return req, nil
}
func decodeUpdateReportStatusRequest(_ context.Context, r *http.Request) (interface{}, error) {
req := updateReportStatusReq{
id: chi.URLParam(r, reportIdKey),
}
return req, nil
}
func decodeDeleteReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
id := chi.URLParam(r, reportIdKey)
return deleteReportConfigReq{ID: id}, nil
}
func decodeListReportsConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
status, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefStatus)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
st, err := reports.ToStatus(status)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
name, err := apiutil.ReadStringQuery(r, api.NameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
return listReportsConfigReq{
PageMeta: reports.PageMeta{
Offset: offset,
Limit: limit,
Status: st,
Name: name,
},
}, nil
}
func encodeFileDownloadResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
switch resp := response.(type) {
case downloadReportResp:
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", resp.File.Name))
w.Header().Set("Content-Type", resp.File.Format.ContentType())
_, err := w.Write(resp.File.Data)
return err
default:
if ar, ok := response.(supermq.Response); ok {
for k, v := range ar.Headers() {
w.Header().Set(k, v)
}
w.Header().Set("Content-Type", api.ContentType)
w.WriteHeader(ar.Code())
if ar.Empty() {
return nil
}
}
return json.NewEncoder(w).Encode(response)
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package re
package reports
import (
"bytes"
+64
View File
@@ -0,0 +1,64 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package reports
import (
"context"
"fmt"
"log/slog"
"time"
pkglog "github.com/absmach/magistrala/pkg/logger"
)
func (r *report) StartScheduler(ctx context.Context) error {
defer r.ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-r.ticker.Tick():
due := time.Now().UTC()
pm := PageMeta{
Status: EnabledStatus,
ScheduledBefore: &due,
}
reportConfigs, err := r.repo.ListReportsConfig(ctx, pm)
if err != nil {
r.runInfo <- pkglog.RunInfo{
Level: slog.LevelError,
Message: fmt.Sprintf("failed to list reports : %s", err),
Details: []slog.Attr{slog.Time("due", due)},
}
continue
}
for _, c := range reportConfigs.ReportConfigs {
go func(cfg ReportConfig) {
if _, err := r.repo.UpdateReportDue(ctx, cfg.ID, cfg.Schedule.NextDue()); err != nil {
r.runInfo <- pkglog.RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to update report: %s", err), Details: []slog.Attr{slog.Time("time", time.Now().UTC())}}
return
}
_, err := r.generateReport(ctx, cfg, EmailReport)
ret := pkglog.RunInfo{
Details: []slog.Attr{
slog.String("domain_id", cfg.DomainID),
slog.String("report_id", cfg.ID),
slog.String("report_name", cfg.Name),
slog.Time("exec_time", time.Now().UTC()),
},
}
if err != nil {
ret.Level = slog.LevelError
ret.Message = fmt.Sprintf("failed to generate report: %s", err)
}
r.runInfo <- ret
}(c)
}
}
}
}
+190
View File
@@ -0,0 +1,190 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package middleware
import (
"context"
"github.com/absmach/magistrala/reports"
"github.com/absmach/supermq/pkg/authn"
smqauthz "github.com/absmach/supermq/pkg/authz"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/policies"
)
var (
errDomainCreateConfigs = errors.New("not authorized to create report configs in domain")
errDomainViewConfigs = errors.New("not authorized to view report configs in domain")
errDomainUpdateConfigs = errors.New("not authorized to update report configs in domain")
errDomainDeleteConfigs = errors.New("not authorized to delete report configs in domain")
errDomainGenerateReports = errors.New("not authorized to generate reports in domain")
)
type authorizationMiddleware struct {
svc reports.Service
authz smqauthz.Authorization
}
// AuthorizationMiddleware adds authorization to the reports service.
func AuthorizationMiddleware(svc reports.Service, authz smqauthz.Authorization) (reports.Service, error) {
return &authorizationMiddleware{
svc: svc,
authz: authz,
}, nil
}
func (am *authorizationMiddleware) AddReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return reports.ReportConfig{}, errors.Wrap(errDomainCreateConfigs, err)
}
return am.svc.AddReportConfig(ctx, session, cfg)
}
func (am *authorizationMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return reports.ReportConfig{}, errors.Wrap(errDomainViewConfigs, err)
}
return am.svc.ViewReportConfig(ctx, session, id)
}
func (am *authorizationMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
}
return am.svc.UpdateReportConfig(ctx, session, cfg)
}
func (am *authorizationMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return reports.ReportConfig{}, errors.Wrap(errDomainDeleteConfigs, err)
}
return am.svc.UpdateReportSchedule(ctx, session, cfg)
}
func (am *authorizationMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return errors.Wrap(errDomainDeleteConfigs, err)
}
return am.svc.RemoveReportConfig(ctx, session, id)
}
func (am *authorizationMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return reports.ReportConfigPage{}, errors.Wrap(errDomainViewConfigs, err)
}
return am.svc.ListReportsConfig(ctx, session, pm)
}
func (am *authorizationMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
}
return am.svc.EnableReportConfig(ctx, session, id)
}
func (am *authorizationMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
}
return am.svc.DisableReportConfig(ctx, session, id)
}
func (am *authorizationMiddleware) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error) {
if err := am.authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: session.DomainID,
ObjectType: policies.DomainType,
Permission: policies.MembershipPermission,
}); err != nil {
return reports.ReportPage{}, errors.Wrap(errDomainGenerateReports, err)
}
return am.svc.GenerateReport(ctx, session, config, action)
}
func (am *authorizationMiddleware) StartScheduler(ctx context.Context) error {
return am.svc.StartScheduler(ctx)
}
func (am *authorizationMiddleware) authorize(ctx context.Context, pr smqauthz.PolicyReq) error {
if err := am.authz.Authorize(ctx, pr); err != nil {
return err
}
return nil
}
+210
View File
@@ -0,0 +1,210 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package middleware
import (
"context"
"log/slog"
"time"
"github.com/absmach/magistrala/reports"
"github.com/absmach/supermq/pkg/authn"
)
var _ reports.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger *slog.Logger
svc reports.Service
}
func LoggingMiddleware(svc reports.Service, logger *slog.Logger) reports.Service {
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) StartScheduler(ctx context.Context) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Start scheduler failed", args...)
return
}
lm.logger.Info("Start scheduler completed successfully", args...)
}(time.Now())
return lm.svc.StartScheduler(ctx)
}
func (lm *loggingMiddleware) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (page reports.ReportPage, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Generate report failed", args...)
return
}
lm.logger.Info("Generate report completed", args...)
}(time.Now())
return lm.svc.GenerateReport(ctx, session, config, action)
}
func (lm *loggingMiddleware) AddReportConfig(ctx context.Context, session authn.Session, config reports.ReportConfig) (res reports.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.String("report_name", config.Name),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Add report config failed", args...)
return
}
lm.logger.Info("Add report config completed successfully", args...)
}(time.Now())
return lm.svc.AddReportConfig(ctx, session, config)
}
func (lm *loggingMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (res reports.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report_config",
slog.String("id", res.ID),
slog.String("name", res.Name),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("View report config failed", args...)
return
}
lm.logger.Info("View report config completed successfully", args...)
}(time.Now())
return lm.svc.ViewReportConfig(ctx, session, id)
}
func (lm *loggingMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, config reports.ReportConfig) (res reports.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report_config",
slog.String("id", config.ID),
slog.String("name", config.Name),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Update report config failed", args...)
return
}
lm.logger.Info("Update report config completed successfully", args...)
}(time.Now())
return lm.svc.UpdateReportConfig(ctx, session, config)
}
func (lm *loggingMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (res reports.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report",
slog.String("id", cfg.ID),
slog.Any("schedule", cfg.Schedule),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Update report schedule failed", args...)
return
}
lm.logger.Info("Update report schedule completed successfully", args...)
}(time.Now())
return lm.svc.UpdateReportSchedule(ctx, session, cfg)
}
func (lm *loggingMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (pg reports.ReportConfigPage, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("page",
slog.Uint64("offset", pm.Offset),
slog.Uint64("limit", pm.Limit),
slog.Uint64("total", pg.Total),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("List reports config failed", args...)
return
}
lm.logger.Info("List reports config completed successfully", args...)
}(time.Now())
return lm.svc.ListReportsConfig(ctx, session, pm)
}
func (lm *loggingMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (res reports.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report_config",
slog.String("id", res.ID),
slog.String("name", res.Name),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Disable report config failed", args...)
return
}
lm.logger.Info("Disable report config completed successfully", args...)
}(time.Now())
return lm.svc.DisableReportConfig(ctx, session, id)
}
func (lm *loggingMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (res reports.ReportConfig, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.Group("report_config",
slog.String("id", res.ID),
slog.String("name", res.Name),
),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Enable report config failed", args...)
return
}
lm.logger.Info("Enable report config completed successfully", args...)
}(time.Now())
return lm.svc.EnableReportConfig(ctx, session, id)
}
func (lm *loggingMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("domain_id", session.DomainID),
slog.String("report_config_id", id),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("Remove report config failed", args...)
return
}
lm.logger.Info("Remove report config completed successfully", args...)
}(time.Now())
return lm.svc.RemoveReportConfig(ctx, session, id)
}
+475
View File
@@ -0,0 +1,475 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"context"
"time"
"github.com/absmach/magistrala/reports"
mock "github.com/stretchr/testify/mock"
)
// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRepository(t interface {
mock.TestingT
Cleanup(func())
}) *Repository {
mock := &Repository{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Repository is an autogenerated mock type for the Repository type
type Repository struct {
mock.Mock
}
type Repository_Expecter struct {
mock *mock.Mock
}
func (_m *Repository) EXPECT() *Repository_Expecter {
return &Repository_Expecter{mock: &_m.Mock}
}
// AddReportConfig provides a mock function for the type Repository
func (_mock *Repository) AddReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, cfg)
if len(ret) == 0 {
panic("no return value specified for AddReportConfig")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) reports.ReportConfig); ok {
r0 = returnFunc(ctx, cfg)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.ReportConfig) error); ok {
r1 = returnFunc(ctx, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_AddReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReportConfig'
type Repository_AddReportConfig_Call struct {
*mock.Call
}
// AddReportConfig is a helper method to define mock.On call
// - ctx
// - cfg
func (_e *Repository_Expecter) AddReportConfig(ctx interface{}, cfg interface{}) *Repository_AddReportConfig_Call {
return &Repository_AddReportConfig_Call{Call: _e.mock.On("AddReportConfig", ctx, cfg)}
}
func (_c *Repository_AddReportConfig_Call) Run(run func(ctx context.Context, cfg reports.ReportConfig)) *Repository_AddReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(reports.ReportConfig))
})
return _c
}
func (_c *Repository_AddReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_AddReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_AddReportConfig_Call) RunAndReturn(run func(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Repository_AddReportConfig_Call {
_c.Call.Return(run)
return _c
}
// ListReportsConfig provides a mock function for the type Repository
func (_mock *Repository) ListReportsConfig(ctx context.Context, pm reports.PageMeta) (reports.ReportConfigPage, error) {
ret := _mock.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for ListReportsConfig")
}
var r0 reports.ReportConfigPage
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.PageMeta) (reports.ReportConfigPage, error)); ok {
return returnFunc(ctx, pm)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.PageMeta) reports.ReportConfigPage); ok {
r0 = returnFunc(ctx, pm)
} else {
r0 = ret.Get(0).(reports.ReportConfigPage)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.PageMeta) error); ok {
r1 = returnFunc(ctx, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_ListReportsConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReportsConfig'
type Repository_ListReportsConfig_Call struct {
*mock.Call
}
// ListReportsConfig is a helper method to define mock.On call
// - ctx
// - pm
func (_e *Repository_Expecter) ListReportsConfig(ctx interface{}, pm interface{}) *Repository_ListReportsConfig_Call {
return &Repository_ListReportsConfig_Call{Call: _e.mock.On("ListReportsConfig", ctx, pm)}
}
func (_c *Repository_ListReportsConfig_Call) Run(run func(ctx context.Context, pm reports.PageMeta)) *Repository_ListReportsConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(reports.PageMeta))
})
return _c
}
func (_c *Repository_ListReportsConfig_Call) Return(reportConfigPage reports.ReportConfigPage, err error) *Repository_ListReportsConfig_Call {
_c.Call.Return(reportConfigPage, err)
return _c
}
func (_c *Repository_ListReportsConfig_Call) RunAndReturn(run func(ctx context.Context, pm reports.PageMeta) (reports.ReportConfigPage, error)) *Repository_ListReportsConfig_Call {
_c.Call.Return(run)
return _c
}
// RemoveReportConfig provides a mock function for the type Repository
func (_mock *Repository) RemoveReportConfig(ctx context.Context, id string) error {
ret := _mock.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for RemoveReportConfig")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = returnFunc(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Repository_RemoveReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveReportConfig'
type Repository_RemoveReportConfig_Call struct {
*mock.Call
}
// RemoveReportConfig is a helper method to define mock.On call
// - ctx
// - id
func (_e *Repository_Expecter) RemoveReportConfig(ctx interface{}, id interface{}) *Repository_RemoveReportConfig_Call {
return &Repository_RemoveReportConfig_Call{Call: _e.mock.On("RemoveReportConfig", ctx, id)}
}
func (_c *Repository_RemoveReportConfig_Call) Run(run func(ctx context.Context, id string)) *Repository_RemoveReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *Repository_RemoveReportConfig_Call) Return(err error) *Repository_RemoveReportConfig_Call {
_c.Call.Return(err)
return _c
}
func (_c *Repository_RemoveReportConfig_Call) RunAndReturn(run func(ctx context.Context, id string) error) *Repository_RemoveReportConfig_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportConfig provides a mock function for the type Repository
func (_mock *Repository) UpdateReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportConfig")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) reports.ReportConfig); ok {
r0 = returnFunc(ctx, cfg)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.ReportConfig) error); ok {
r1 = returnFunc(ctx, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_UpdateReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfig'
type Repository_UpdateReportConfig_Call struct {
*mock.Call
}
// UpdateReportConfig is a helper method to define mock.On call
// - ctx
// - cfg
func (_e *Repository_Expecter) UpdateReportConfig(ctx interface{}, cfg interface{}) *Repository_UpdateReportConfig_Call {
return &Repository_UpdateReportConfig_Call{Call: _e.mock.On("UpdateReportConfig", ctx, cfg)}
}
func (_c *Repository_UpdateReportConfig_Call) Run(run func(ctx context.Context, cfg reports.ReportConfig)) *Repository_UpdateReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(reports.ReportConfig))
})
return _c
}
func (_c *Repository_UpdateReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_UpdateReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_UpdateReportConfig_Call) RunAndReturn(run func(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Repository_UpdateReportConfig_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportConfigStatus provides a mock function for the type Repository
func (_mock *Repository) UpdateReportConfigStatus(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportConfigStatus")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) reports.ReportConfig); ok {
r0 = returnFunc(ctx, cfg)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.ReportConfig) error); ok {
r1 = returnFunc(ctx, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_UpdateReportConfigStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfigStatus'
type Repository_UpdateReportConfigStatus_Call struct {
*mock.Call
}
// UpdateReportConfigStatus is a helper method to define mock.On call
// - ctx
// - cfg
func (_e *Repository_Expecter) UpdateReportConfigStatus(ctx interface{}, cfg interface{}) *Repository_UpdateReportConfigStatus_Call {
return &Repository_UpdateReportConfigStatus_Call{Call: _e.mock.On("UpdateReportConfigStatus", ctx, cfg)}
}
func (_c *Repository_UpdateReportConfigStatus_Call) Run(run func(ctx context.Context, cfg reports.ReportConfig)) *Repository_UpdateReportConfigStatus_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(reports.ReportConfig))
})
return _c
}
func (_c *Repository_UpdateReportConfigStatus_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_UpdateReportConfigStatus_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_UpdateReportConfigStatus_Call) RunAndReturn(run func(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Repository_UpdateReportConfigStatus_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportDue provides a mock function for the type Repository
func (_mock *Repository) UpdateReportDue(ctx context.Context, id string, due time.Time) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, id, due)
if len(ret) == 0 {
panic("no return value specified for UpdateReportDue")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Time) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, id, due)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Time) reports.ReportConfig); ok {
r0 = returnFunc(ctx, id, due)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string, time.Time) error); ok {
r1 = returnFunc(ctx, id, due)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_UpdateReportDue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportDue'
type Repository_UpdateReportDue_Call struct {
*mock.Call
}
// UpdateReportDue is a helper method to define mock.On call
// - ctx
// - id
// - due
func (_e *Repository_Expecter) UpdateReportDue(ctx interface{}, id interface{}, due interface{}) *Repository_UpdateReportDue_Call {
return &Repository_UpdateReportDue_Call{Call: _e.mock.On("UpdateReportDue", ctx, id, due)}
}
func (_c *Repository_UpdateReportDue_Call) Run(run func(ctx context.Context, id string, due time.Time)) *Repository_UpdateReportDue_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(time.Time))
})
return _c
}
func (_c *Repository_UpdateReportDue_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_UpdateReportDue_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_UpdateReportDue_Call) RunAndReturn(run func(ctx context.Context, id string, due time.Time) (reports.ReportConfig, error)) *Repository_UpdateReportDue_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportSchedule provides a mock function for the type Repository
func (_mock *Repository) UpdateReportSchedule(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportSchedule")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) reports.ReportConfig); ok {
r0 = returnFunc(ctx, cfg)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.ReportConfig) error); ok {
r1 = returnFunc(ctx, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_UpdateReportSchedule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportSchedule'
type Repository_UpdateReportSchedule_Call struct {
*mock.Call
}
// UpdateReportSchedule is a helper method to define mock.On call
// - ctx
// - cfg
func (_e *Repository_Expecter) UpdateReportSchedule(ctx interface{}, cfg interface{}) *Repository_UpdateReportSchedule_Call {
return &Repository_UpdateReportSchedule_Call{Call: _e.mock.On("UpdateReportSchedule", ctx, cfg)}
}
func (_c *Repository_UpdateReportSchedule_Call) Run(run func(ctx context.Context, cfg reports.ReportConfig)) *Repository_UpdateReportSchedule_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(reports.ReportConfig))
})
return _c
}
func (_c *Repository_UpdateReportSchedule_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_UpdateReportSchedule_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_UpdateReportSchedule_Call) RunAndReturn(run func(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Repository_UpdateReportSchedule_Call {
_c.Call.Return(run)
return _c
}
// ViewReportConfig provides a mock function for the type Repository
func (_mock *Repository) ViewReportConfig(ctx context.Context, id string) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for ViewReportConfig")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, id)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string) reports.ReportConfig); ok {
r0 = returnFunc(ctx, id)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = returnFunc(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_ViewReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewReportConfig'
type Repository_ViewReportConfig_Call struct {
*mock.Call
}
// ViewReportConfig is a helper method to define mock.On call
// - ctx
// - id
func (_e *Repository_Expecter) ViewReportConfig(ctx interface{}, id interface{}) *Repository_ViewReportConfig_Call {
return &Repository_ViewReportConfig_Call{Call: _e.mock.On("ViewReportConfig", ctx, id)}
}
func (_c *Repository_ViewReportConfig_Call) Run(run func(ctx context.Context, id string)) *Repository_ViewReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *Repository_ViewReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_ViewReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Repository_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Context, id string) (reports.ReportConfig, error)) *Repository_ViewReportConfig_Call {
_c.Call.Return(run)
return _c
}
+584
View File
@@ -0,0 +1,584 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"context"
"github.com/absmach/magistrala/reports"
"github.com/absmach/supermq/pkg/authn"
mock "github.com/stretchr/testify/mock"
)
// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewService(t interface {
mock.TestingT
Cleanup(func())
}) *Service {
mock := &Service{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Service is an autogenerated mock type for the Service type
type Service struct {
mock.Mock
}
type Service_Expecter struct {
mock *mock.Mock
}
func (_m *Service) EXPECT() *Service_Expecter {
return &Service_Expecter{mock: &_m.Mock}
}
// AddReportConfig provides a mock function for the type Service
func (_mock *Service) AddReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, session, cfg)
if len(ret) == 0 {
panic("no return value specified for AddReportConfig")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, session, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) reports.ReportConfig); ok {
r0 = returnFunc(ctx, session, cfg)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.ReportConfig) error); ok {
r1 = returnFunc(ctx, session, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_AddReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReportConfig'
type Service_AddReportConfig_Call struct {
*mock.Call
}
// AddReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - cfg
func (_e *Service_Expecter) AddReportConfig(ctx interface{}, session interface{}, cfg interface{}) *Service_AddReportConfig_Call {
return &Service_AddReportConfig_Call{Call: _e.mock.On("AddReportConfig", ctx, session, cfg)}
}
func (_c *Service_AddReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig)) *Service_AddReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.ReportConfig))
})
return _c
}
func (_c *Service_AddReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_AddReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_AddReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Service_AddReportConfig_Call {
_c.Call.Return(run)
return _c
}
// DisableReportConfig provides a mock function for the type Service
func (_mock *Service) DisableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for DisableReportConfig")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, session, id)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) reports.ReportConfig); ok {
r0 = returnFunc(ctx, session, id)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
r1 = returnFunc(ctx, session, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_DisableReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisableReportConfig'
type Service_DisableReportConfig_Call struct {
*mock.Call
}
// DisableReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - id
func (_e *Service_Expecter) DisableReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_DisableReportConfig_Call {
return &Service_DisableReportConfig_Call{Call: _e.mock.On("DisableReportConfig", ctx, session, id)}
}
func (_c *Service_DisableReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_DisableReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
})
return _c
}
func (_c *Service_DisableReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_DisableReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_DisableReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error)) *Service_DisableReportConfig_Call {
_c.Call.Return(run)
return _c
}
// EnableReportConfig provides a mock function for the type Service
func (_mock *Service) EnableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for EnableReportConfig")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, session, id)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) reports.ReportConfig); ok {
r0 = returnFunc(ctx, session, id)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
r1 = returnFunc(ctx, session, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_EnableReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnableReportConfig'
type Service_EnableReportConfig_Call struct {
*mock.Call
}
// EnableReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - id
func (_e *Service_Expecter) EnableReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_EnableReportConfig_Call {
return &Service_EnableReportConfig_Call{Call: _e.mock.On("EnableReportConfig", ctx, session, id)}
}
func (_c *Service_EnableReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_EnableReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
})
return _c
}
func (_c *Service_EnableReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_EnableReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_EnableReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error)) *Service_EnableReportConfig_Call {
_c.Call.Return(run)
return _c
}
// GenerateReport provides a mock function for the type Service
func (_mock *Service) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error) {
ret := _mock.Called(ctx, session, config, action)
if len(ret) == 0 {
panic("no return value specified for GenerateReport")
}
var r0 reports.ReportPage
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig, reports.ReportAction) (reports.ReportPage, error)); ok {
return returnFunc(ctx, session, config, action)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig, reports.ReportAction) reports.ReportPage); ok {
r0 = returnFunc(ctx, session, config, action)
} else {
r0 = ret.Get(0).(reports.ReportPage)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.ReportConfig, reports.ReportAction) error); ok {
r1 = returnFunc(ctx, session, config, action)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_GenerateReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateReport'
type Service_GenerateReport_Call struct {
*mock.Call
}
// GenerateReport is a helper method to define mock.On call
// - ctx
// - session
// - config
// - action
func (_e *Service_Expecter) GenerateReport(ctx interface{}, session interface{}, config interface{}, action interface{}) *Service_GenerateReport_Call {
return &Service_GenerateReport_Call{Call: _e.mock.On("GenerateReport", ctx, session, config, action)}
}
func (_c *Service_GenerateReport_Call) Run(run func(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction)) *Service_GenerateReport_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.ReportConfig), args[3].(reports.ReportAction))
})
return _c
}
func (_c *Service_GenerateReport_Call) Return(reportPage reports.ReportPage, err error) *Service_GenerateReport_Call {
_c.Call.Return(reportPage, err)
return _c
}
func (_c *Service_GenerateReport_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error)) *Service_GenerateReport_Call {
_c.Call.Return(run)
return _c
}
// ListReportsConfig provides a mock function for the type Service
func (_mock *Service) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error) {
ret := _mock.Called(ctx, session, pm)
if len(ret) == 0 {
panic("no return value specified for ListReportsConfig")
}
var r0 reports.ReportConfigPage
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.PageMeta) (reports.ReportConfigPage, error)); ok {
return returnFunc(ctx, session, pm)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.PageMeta) reports.ReportConfigPage); ok {
r0 = returnFunc(ctx, session, pm)
} else {
r0 = ret.Get(0).(reports.ReportConfigPage)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.PageMeta) error); ok {
r1 = returnFunc(ctx, session, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_ListReportsConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReportsConfig'
type Service_ListReportsConfig_Call struct {
*mock.Call
}
// ListReportsConfig is a helper method to define mock.On call
// - ctx
// - session
// - pm
func (_e *Service_Expecter) ListReportsConfig(ctx interface{}, session interface{}, pm interface{}) *Service_ListReportsConfig_Call {
return &Service_ListReportsConfig_Call{Call: _e.mock.On("ListReportsConfig", ctx, session, pm)}
}
func (_c *Service_ListReportsConfig_Call) Run(run func(ctx context.Context, session authn.Session, pm reports.PageMeta)) *Service_ListReportsConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.PageMeta))
})
return _c
}
func (_c *Service_ListReportsConfig_Call) Return(reportConfigPage reports.ReportConfigPage, err error) *Service_ListReportsConfig_Call {
_c.Call.Return(reportConfigPage, err)
return _c
}
func (_c *Service_ListReportsConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error)) *Service_ListReportsConfig_Call {
_c.Call.Return(run)
return _c
}
// RemoveReportConfig provides a mock function for the type Service
func (_mock *Service) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
ret := _mock.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for RemoveReportConfig")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok {
r0 = returnFunc(ctx, session, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Service_RemoveReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveReportConfig'
type Service_RemoveReportConfig_Call struct {
*mock.Call
}
// RemoveReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - id
func (_e *Service_Expecter) RemoveReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_RemoveReportConfig_Call {
return &Service_RemoveReportConfig_Call{Call: _e.mock.On("RemoveReportConfig", ctx, session, id)}
}
func (_c *Service_RemoveReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_RemoveReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
})
return _c
}
func (_c *Service_RemoveReportConfig_Call) Return(err error) *Service_RemoveReportConfig_Call {
_c.Call.Return(err)
return _c
}
func (_c *Service_RemoveReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) error) *Service_RemoveReportConfig_Call {
_c.Call.Return(run)
return _c
}
// StartScheduler provides a mock function for the type Service
func (_mock *Service) StartScheduler(ctx context.Context) error {
ret := _mock.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for StartScheduler")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = returnFunc(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// Service_StartScheduler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartScheduler'
type Service_StartScheduler_Call struct {
*mock.Call
}
// StartScheduler is a helper method to define mock.On call
// - ctx
func (_e *Service_Expecter) StartScheduler(ctx interface{}) *Service_StartScheduler_Call {
return &Service_StartScheduler_Call{Call: _e.mock.On("StartScheduler", ctx)}
}
func (_c *Service_StartScheduler_Call) Run(run func(ctx context.Context)) *Service_StartScheduler_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *Service_StartScheduler_Call) Return(err error) *Service_StartScheduler_Call {
_c.Call.Return(err)
return _c
}
func (_c *Service_StartScheduler_Call) RunAndReturn(run func(ctx context.Context) error) *Service_StartScheduler_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportConfig provides a mock function for the type Service
func (_mock *Service) UpdateReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, session, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportConfig")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, session, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) reports.ReportConfig); ok {
r0 = returnFunc(ctx, session, cfg)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.ReportConfig) error); ok {
r1 = returnFunc(ctx, session, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_UpdateReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfig'
type Service_UpdateReportConfig_Call struct {
*mock.Call
}
// UpdateReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - cfg
func (_e *Service_Expecter) UpdateReportConfig(ctx interface{}, session interface{}, cfg interface{}) *Service_UpdateReportConfig_Call {
return &Service_UpdateReportConfig_Call{Call: _e.mock.On("UpdateReportConfig", ctx, session, cfg)}
}
func (_c *Service_UpdateReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig)) *Service_UpdateReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.ReportConfig))
})
return _c
}
func (_c *Service_UpdateReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_UpdateReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_UpdateReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Service_UpdateReportConfig_Call {
_c.Call.Return(run)
return _c
}
// UpdateReportSchedule provides a mock function for the type Service
func (_mock *Service) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, session, cfg)
if len(ret) == 0 {
panic("no return value specified for UpdateReportSchedule")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, session, cfg)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) reports.ReportConfig); ok {
r0 = returnFunc(ctx, session, cfg)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.ReportConfig) error); ok {
r1 = returnFunc(ctx, session, cfg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_UpdateReportSchedule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportSchedule'
type Service_UpdateReportSchedule_Call struct {
*mock.Call
}
// UpdateReportSchedule is a helper method to define mock.On call
// - ctx
// - session
// - cfg
func (_e *Service_Expecter) UpdateReportSchedule(ctx interface{}, session interface{}, cfg interface{}) *Service_UpdateReportSchedule_Call {
return &Service_UpdateReportSchedule_Call{Call: _e.mock.On("UpdateReportSchedule", ctx, session, cfg)}
}
func (_c *Service_UpdateReportSchedule_Call) Run(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig)) *Service_UpdateReportSchedule_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.ReportConfig))
})
return _c
}
func (_c *Service_UpdateReportSchedule_Call) Return(reportConfig reports.ReportConfig, err error) *Service_UpdateReportSchedule_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_UpdateReportSchedule_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Service_UpdateReportSchedule_Call {
_c.Call.Return(run)
return _c
}
// ViewReportConfig provides a mock function for the type Service
func (_mock *Service) ViewReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
ret := _mock.Called(ctx, session, id)
if len(ret) == 0 {
panic("no return value specified for ViewReportConfig")
}
var r0 reports.ReportConfig
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (reports.ReportConfig, error)); ok {
return returnFunc(ctx, session, id)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) reports.ReportConfig); ok {
r0 = returnFunc(ctx, session, id)
} else {
r0 = ret.Get(0).(reports.ReportConfig)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
r1 = returnFunc(ctx, session, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_ViewReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewReportConfig'
type Service_ViewReportConfig_Call struct {
*mock.Call
}
// ViewReportConfig is a helper method to define mock.On call
// - ctx
// - session
// - id
func (_e *Service_Expecter) ViewReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_ViewReportConfig_Call {
return &Service_ViewReportConfig_Call{Call: _e.mock.On("ViewReportConfig", ctx, session, id)}
}
func (_c *Service_ViewReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_ViewReportConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
})
return _c
}
func (_c *Service_ViewReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_ViewReportConfig_Call {
_c.Call.Return(reportConfig, err)
return _c
}
func (_c *Service_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error)) *Service_ViewReportConfig_Call {
_c.Call.Return(run)
return _c
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
migrate "github.com/rubenv/sql-migrate"
)
func Migration() *migrate.MemoryMigrationSource {
return &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "reports_01",
Up: []string{
`CREATE TABLE IF NOT EXISTS report_config (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(1024),
description TEXT,
domain_id VARCHAR(36) NOT NULL,
status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0),
created_at TIMESTAMP,
created_by VARCHAR(254),
updated_at TIMESTAMP,
updated_by VARCHAR(254),
due TIMESTAMPTZ,
recurring SMALLINT,
recurring_period SMALLINT,
start_datetime TIMESTAMP,
config JSONB,
email JSONB,
metrics JSONB
);`,
},
Down: []string{
`DROP TABLE IF EXISTS report_config;`,
},
},
},
}
}
+137
View File
@@ -0,0 +1,137 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"database/sql"
"encoding/json"
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/reports"
)
// 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"`
}
func reportToDb(r reports.ReportConfig) (dbReport, error) {
config := []byte("{}")
if r.Config != nil {
b, err := json.Marshal(r.Config)
if err != nil {
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
config = b
}
metrics := []byte("{}")
if r.Metrics != nil {
m, err := json.Marshal(r.Metrics)
if err != nil {
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
metrics = m
}
email := []byte("{}")
if r.Email != nil {
e, err := json.Marshal(r.Email)
if err != nil {
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
email = e
}
start := sql.NullTime{Time: r.Schedule.StartDateTime}
if !r.Schedule.StartDateTime.IsZero() {
start.Valid = true
}
t := sql.NullTime{Time: r.Schedule.Time}
if !r.Schedule.Time.IsZero() {
t.Valid = true
}
return dbReport{
ID: r.ID,
Name: r.Name,
Description: r.Description,
DomainID: r.DomainID,
StartDateTime: start,
Due: t,
Recurring: r.Schedule.Recurring,
RecurringPeriod: r.Schedule.RecurringPeriod,
Status: r.Status,
CreatedAt: r.CreatedAt,
CreatedBy: r.CreatedBy,
UpdatedAt: r.UpdatedAt,
UpdatedBy: r.UpdatedBy,
Config: config,
Metrics: metrics,
Email: email,
}, nil
}
func dbToReport(dto dbReport) (reports.ReportConfig, error) {
var config reports.MetricConfig
if dto.Config != nil {
if err := json.Unmarshal(dto.Config, &config); err != nil {
return reports.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
}
var email reports.EmailSetting
if dto.Email != nil {
if err := json.Unmarshal(dto.Email, &email); err != nil {
return reports.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
}
var metrics []reports.ReqMetric
if dto.Metrics != nil {
if err := json.Unmarshal(dto.Metrics, &metrics); err != nil {
return reports.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
}
}
rpt := reports.ReportConfig{
ID: dto.ID,
Name: dto.Name,
Description: dto.Description,
DomainID: dto.DomainID,
Config: &config,
Metrics: metrics,
Schedule: schedule.Schedule{
StartDateTime: dto.StartDateTime.Time,
Time: dto.Due.Time,
Recurring: dto.Recurring,
RecurringPeriod: dto.RecurringPeriod,
},
Email: &email,
Status: dto.Status,
CreatedAt: dto.CreatedAt,
CreatedBy: dto.CreatedBy,
UpdatedAt: dto.UpdatedAt,
UpdatedBy: dto.UpdatedBy,
}
return rpt, nil
}
+340
View File
@@ -0,0 +1,340 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/reports"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
"github.com/absmach/supermq/pkg/postgres"
)
type PostgresRepository struct {
DB postgres.Database
}
func NewRepository(db postgres.Database) reports.Repository {
return &PostgresRepository{DB: db}
}
func (repo *PostgresRepository) AddReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
q := `
INSERT INTO report_config (id, name, description, domain_id, config, metrics,
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status)
VALUES (:id, :name, :description, :domain_id, :config, :metrics,
:email, :start_datetime, :due, :recurring, :recurring_period, :created_at, :created_by, :updated_at, :updated_by, :status)
RETURNING id, name, description, domain_id, config, metrics,
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
`
dbr, err := reportToDb(cfg)
if err != nil {
return reports.ReportConfig{}, err
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
if err != nil {
return reports.ReportConfig{}, err
}
defer row.Close()
var dbReport dbReport
if row.Next() {
if err := row.StructScan(&dbReport); err != nil {
return reports.ReportConfig{}, err
}
}
report, err := dbToReport(dbReport)
if err != nil {
return reports.ReportConfig{}, err
}
return report, nil
}
func (repo *PostgresRepository) ViewReportConfig(ctx context.Context, id string) (reports.ReportConfig, error) {
q := `
SELECT id, name, description, domain_id, config, metrics,
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status
FROM report_config
WHERE id = $1;
`
row := repo.DB.QueryRowxContext(ctx, q, id)
if err := row.Err(); err != nil {
return reports.ReportConfig{}, err
}
var dbr dbReport
if err := row.StructScan(&dbr); err != nil {
return reports.ReportConfig{}, err
}
rpt, err := dbToReport(dbr)
if err != nil {
return reports.ReportConfig{}, err
}
return rpt, nil
}
func (repo *PostgresRepository) UpdateReportConfigStatus(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
q := `UPDATE report_config SET status = :status, updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id
RETURNING id, name, description, domain_id, metrics, email, config,
start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;`
dbRpt, err := reportToDb(cfg)
if err != nil {
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbRpt)
if err != nil {
return reports.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
}
defer row.Close()
dbr := dbReport{}
if row.Next() {
if err := row.StructScan(&dbr); err != nil {
return reports.ReportConfig{}, err
}
res, err := dbToReport(dbr)
if err != nil {
return reports.ReportConfig{}, err
}
return res, err
}
return reports.ReportConfig{}, repoerr.ErrNotFound
}
func (repo *PostgresRepository) UpdateReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
var query []string
if cfg.Name != "" {
query = append(query, "name = :name")
}
if cfg.Description != "" {
query = append(query, "description = :description")
}
if len(cfg.Metrics) > 0 {
query = append(query, "metrics = :metrics")
}
if cfg.Email != nil {
query = append(query, "email = :email")
}
if cfg.Config != nil {
query = append(query, "config = :config")
}
var q string
if len(query) > 0 {
q = fmt.Sprintf("%s", strings.Join(query, ", "))
}
q = fmt.Sprintf(`
UPDATE report_config
SET %s,
updated_at = :updated_at, updated_by = :updated_by
WHERE id = :id
RETURNING id, name, description, domain_id, config, metrics,
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
`, q)
dbr, err := reportToDb(cfg)
if err != nil {
return reports.ReportConfig{}, err
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
if err != nil {
return reports.ReportConfig{}, err
}
defer row.Close()
var dbReport dbReport
if row.Next() {
if err := row.StructScan(&dbReport); err != nil {
return reports.ReportConfig{}, err
}
}
rpt, err := dbToReport(dbReport)
if err != nil {
return reports.ReportConfig{}, err
}
return rpt, nil
}
func (repo *PostgresRepository) UpdateReportSchedule(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
q := `
UPDATE report_config
SET start_datetime = :start_datetime, due = :due, recurring = :recurring,
recurring_period = :recurring_period, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id
RETURNING id, name, description, domain_id, config, metrics,
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
`
dbr, err := reportToDb(cfg)
if err != nil {
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
if err != nil {
return reports.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
}
defer row.Close()
var dbReport dbReport
if row.Next() {
if err := row.StructScan(&dbReport); err != nil {
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
}
report, err := dbToReport(dbReport)
if err != nil {
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
return report, nil
}
func (repo *PostgresRepository) RemoveReportConfig(ctx context.Context, id string) error {
q := `
DELETE FROM report_config
WHERE id = $1;
`
result, err := repo.DB.ExecContext(ctx, q, id)
if err != nil {
return err
}
if _, err := result.RowsAffected(); err != nil {
return repoerr.ErrNotFound
}
return nil
}
func (repo *PostgresRepository) ListReportsConfig(ctx context.Context, pm reports.PageMeta) (reports.ReportConfigPage, error) {
listReportsQuery := `
SELECT id, name, description, domain_id, metrics, email, config,
start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status
FROM report_config rc %s %s;
`
pgData := ""
if pm.Limit != 0 {
pgData = "LIMIT :limit"
}
if pm.Offset != 0 {
pgData += " OFFSET :offset"
}
pq := pageReportQuery(pm)
q := fmt.Sprintf(listReportsQuery, pq, pgData)
rows, err := repo.DB.NamedQueryContext(ctx, q, pm)
if err != nil {
return reports.ReportConfigPage{}, err
}
defer rows.Close()
cfgs := []reports.ReportConfig{}
for rows.Next() {
var r dbReport
if err := rows.StructScan(&r); err != nil {
return reports.ReportConfigPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
rpt, err := dbToReport(r)
if err != nil {
return reports.ReportConfigPage{}, err
}
cfgs = append(cfgs, rpt)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM report_config rc %s;`, pq)
total, err := postgres.Total(ctx, repo.DB, cq, pm)
if err != nil {
return reports.ReportConfigPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
pm.Total = total
ret := reports.ReportConfigPage{
PageMeta: pm,
ReportConfigs: cfgs,
}
return ret, nil
}
func (repo *PostgresRepository) UpdateReportDue(ctx context.Context, id string, due time.Time) (reports.ReportConfig, error) {
q := `
UPDATE report_config
SET due = :due, updated_at = :updated_at WHERE id = :id
RETURNING id, name, description, domain_id, config, metrics,
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
`
dbr := dbReport{
ID: id,
UpdatedAt: time.Now().UTC(),
Due: sql.NullTime{Time: due},
}
if !due.IsZero() {
dbr.Due.Valid = true
}
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
if err != nil {
return reports.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
}
defer row.Close()
var dbReport dbReport
if row.Next() {
if err := row.StructScan(&dbReport); err != nil {
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
}
report, err := dbToReport(dbReport)
if err != nil {
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
}
return report, nil
}
func pageReportQuery(pm reports.PageMeta) string {
var query []string
if pm.Status != reports.AllStatus {
query = append(query, "rc.status = :status")
}
if pm.Domain != "" {
query = append(query, "rc.domain_id = :domain_id")
}
if pm.ScheduledBefore != nil {
query = append(query, "rc.due < :scheduled_before")
}
if pm.ScheduledAfter != nil {
query = append(query, "rc.due > :scheduled_after")
}
if pm.Name != "" {
query = append(query, "rc.name ILIKE '%' || :name || '%'")
}
var q string
if len(query) > 0 {
q = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
}
return q
}
+53 -14
View File
@@ -1,9 +1,10 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package re
package reports
import (
"context"
"encoding/json"
"fmt"
"net/mail"
@@ -11,6 +12,8 @@ import (
"time"
"github.com/absmach/magistrala/pkg/reltime"
"github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/transformers/senml"
)
@@ -142,19 +145,19 @@ 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 `json:"schedule,omitempty"`
Config *MetricConfig `json:"config,omitempty"`
Email *EmailSetting `json:"email,omitempty"`
Metrics []ReqMetric `json:"metrics,omitempty"`
Status Status `json:"status"`
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"`
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"`
Status Status `json:"status"`
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"`
}
type ReportConfigPage struct {
@@ -370,3 +373,39 @@ func (a *Aggregation) UnmarshalJSON(data []byte) error {
*a = val
return err
}
type PageMeta struct {
Total uint64 `json:"total" db:"total"`
Offset uint64 `json:"offset" db:"offset"`
Limit uint64 `json:"limit" db:"limit"`
Name string `json:"name" db:"name"`
Status Status `json:"status,omitempty" db:"status"`
Domain string `json:"domain_id,omitempty" db:"domain_id"`
ScheduledBefore *time.Time `json:"scheduled_before,omitempty" db:"scheduled_before"` // Filter rules scheduled before this time
ScheduledAfter *time.Time `json:"scheduled_after,omitempty" db:"scheduled_after"` // Filter rules scheduled after this time
}
type Repository interface {
AddReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
ViewReportConfig(ctx context.Context, id string) (ReportConfig, error)
UpdateReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
UpdateReportSchedule(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
RemoveReportConfig(ctx context.Context, id string) error
UpdateReportConfigStatus(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
ListReportsConfig(ctx context.Context, pm PageMeta) (ReportConfigPage, error)
UpdateReportDue(ctx context.Context, id string, due time.Time) (ReportConfig, error)
}
type Service interface {
AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
RemoveReportConfig(ctx context.Context, session authn.Session, id string) error
ListReportsConfig(ctx context.Context, session authn.Session, pm PageMeta) (ReportConfigPage, error)
EnableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error)
StartScheduler(ctx context.Context) error
}
+418
View File
@@ -0,0 +1,418 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package reports
import (
"context"
"fmt"
"strings"
"time"
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
"github.com/absmach/magistrala/pkg/emailer"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/reltime"
"github.com/absmach/magistrala/pkg/ticker"
"github.com/absmach/supermq"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/transformers/senml"
)
const limit = 1000
type report struct {
repo Repository
runInfo chan pkglog.RunInfo
idp supermq.IDProvider
email emailer.Emailer
ticker ticker.Ticker
readers grpcReadersV1.ReadersServiceClient
}
func NewService(repo Repository, runInfo chan pkglog.RunInfo, idp supermq.IDProvider, tck ticker.Ticker, emailer emailer.Emailer, readers grpcReadersV1.ReadersServiceClient) Service {
return &report{
repo: repo,
idp: idp,
runInfo: runInfo,
email: emailer,
ticker: tck,
readers: readers,
}
}
func (r *report) AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
id, err := r.idp.ID()
if err != nil {
return ReportConfig{}, err
}
now := time.Now()
cfg.ID = id
cfg.CreatedAt = now
cfg.CreatedBy = session.UserID
cfg.DomainID = session.DomainID
cfg.Status = EnabledStatus
if cfg.Schedule.StartDateTime.IsZero() {
cfg.Schedule.StartDateTime = now
}
cfg.Schedule.Time = cfg.Schedule.StartDateTime
reportConfig, err := r.repo.AddReportConfig(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrCreateEntity, err)
}
return reportConfig, nil
}
func (r *report) ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
cfg, err := r.repo.ViewReportConfig(ctx, id)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
return cfg, nil
}
func (r *report) UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
cfg.UpdatedAt = time.Now().UTC()
cfg.UpdatedBy = session.UserID
reportConfig, err := r.repo.UpdateReportConfig(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return reportConfig, nil
}
func (r *report) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
cfg.UpdatedAt = time.Now().UTC()
cfg.UpdatedBy = session.UserID
cfg.Schedule.Time = cfg.Schedule.StartDateTime
c, err := r.repo.UpdateReportSchedule(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return c, nil
}
func (r *report) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
if err := r.repo.RemoveReportConfig(ctx, id); err != nil {
return errors.Wrap(svcerr.ErrRemoveEntity, err)
}
return nil
}
func (r *report) ListReportsConfig(ctx context.Context, session authn.Session, pm PageMeta) (ReportConfigPage, error) {
pm.Domain = session.DomainID
page, err := r.repo.ListReportsConfig(ctx, pm)
if err != nil {
return ReportConfigPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
return page, nil
}
func (r *report) EnableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
status, err := ToStatus(Enabled)
if err != nil {
return ReportConfig{}, err
}
cfg := ReportConfig{
ID: id,
UpdatedAt: time.Now().UTC(),
UpdatedBy: session.UserID,
Status: status,
}
cfg, err = r.repo.UpdateReportConfigStatus(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return cfg, nil
}
func (r *report) DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
status, err := ToStatus(Disabled)
if err != nil {
return ReportConfig{}, err
}
cfg := ReportConfig{
ID: id,
UpdatedAt: time.Now().UTC(),
UpdatedBy: session.UserID,
Status: status,
}
cfg, err = r.repo.UpdateReportConfigStatus(ctx, cfg)
if err != nil {
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
}
return cfg, nil
}
func (r *report) GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error) {
config.DomainID = session.DomainID
if config.Status != EnabledStatus {
return ReportPage{}, svcerr.ErrInvalidStatus
}
reportPage, err := r.generateReport(ctx, config, action)
if err != nil {
return ReportPage{}, err
}
return reportPage, nil
}
func (r *report) generateReport(ctx context.Context, cfg ReportConfig, action ReportAction) (ReportPage, error) {
genReportFile, err := generateFileFunc(action, cfg.Config.FileFormat)
if err != nil {
return ReportPage{}, err
}
agg := grpcReadersV1.Aggregation_AGGREGATION_UNSPECIFIED
switch cfg.Config.Aggregation.AggType {
case AggregationMAX:
agg = grpcReadersV1.Aggregation_MAX
case AggregationMIN:
agg = grpcReadersV1.Aggregation_MIN
case AggregationCOUNT:
agg = grpcReadersV1.Aggregation_COUNT
case AggregationAVG:
agg = grpcReadersV1.Aggregation_AVG
case AggregationSUM:
agg = grpcReadersV1.Aggregation_SUM
}
from, err := reltime.Parse(cfg.Config.From)
if err != nil {
return ReportPage{}, err
}
to, err := reltime.Parse(cfg.Config.To)
if err != nil {
return ReportPage{}, err
}
pm := &grpcReadersV1.PageMetadata{
Aggregation: agg,
Limit: limit,
From: float64(from.UnixMicro()),
To: float64(to.UnixNano()),
Interval: cfg.Config.Aggregation.Interval,
}
var mets []Metric
var reports []Report
for _, metric := range cfg.Metrics {
switch {
case len(metric.ClientIDs) != 0:
for _, clientID := range metric.ClientIDs {
mets = append(mets, Metric{
ChannelID: metric.ChannelID,
ClientID: clientID,
Name: metric.Name,
Subtopic: metric.Subtopic,
Protocol: metric.Protocol,
Format: metric.Format,
})
}
default:
mets = append(mets, Metric{
ChannelID: metric.ChannelID,
Name: metric.Name,
Subtopic: metric.Subtopic,
Protocol: metric.Protocol,
Format: metric.Format,
})
}
}
for _, metric := range mets {
sMsgs := []senml.Message{}
pm.Offset = uint64(0)
pm.Name = metric.Name
if metric.ClientID != "" {
pm.Publisher = metric.ClientID
}
if metric.Subtopic != "" {
pm.Subtopic = metric.Subtopic
}
if metric.Protocol != "" {
pm.Protocol = metric.Protocol
}
if metric.Format != "" {
pm.Format = metric.Format
}
msgs, err := r.readers.ReadMessages(ctx, &grpcReadersV1.ReadMessagesReq{
ChannelId: metric.ChannelID,
DomainId: cfg.DomainID,
PageMetadata: pm,
})
if err != nil {
return ReportPage{}, err
}
for _, msg := range msgs.Messages {
sMsgs = append(sMsgs, convertToSenml(msg.GetSenml()))
}
for msgs.GetTotal() > (pm.Offset + pm.Limit) {
pm.Offset = pm.Offset + pm.Limit
msgs, err := r.readers.ReadMessages(ctx, &grpcReadersV1.ReadMessagesReq{
ChannelId: metric.ChannelID,
DomainId: cfg.DomainID,
PageMetadata: pm,
})
if err != nil {
return ReportPage{}, err
}
for _, msg := range msgs.Messages {
sMsgs = append(sMsgs, convertToSenml(msg.GetSenml()))
}
}
reports = append(reports, convertToReports(metric, sMsgs)...)
}
switch {
case genReportFile != nil:
data, err := genReportFile(cfg.Config.Title, reports)
if err != nil {
return ReportPage{}, err
}
timeStr := strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")
filePrefix := cfg.Name
if filePrefix == "" {
filePrefix = "report"
}
fileName := fmt.Sprintf("%s_%s.%s", filePrefix, timeStr, cfg.Config.FileFormat.Extension())
file := ReportFile{
Name: fileName,
Data: data,
Format: cfg.Config.FileFormat,
}
switch action {
case EmailReport:
if err := r.emailReports(*cfg.Email, file); err != nil {
return ReportPage{}, errors.Wrap(err, svcerr.ErrCreateEntity)
}
return ReportPage{}, nil
default:
return ReportPage{
File: file,
}, nil
}
default:
return ReportPage{
From: from,
To: to,
Aggregation: cfg.Config.Aggregation,
Total: uint64(len(reports)),
Reports: reports,
}, nil
}
}
func generateFileFunc(action ReportAction, format Format) (func(string, []Report) ([]byte, error), error) {
switch action {
case DownloadReport, EmailReport:
switch format {
case PDF:
return generatePDFReport, nil
case CSV:
return generateCSVReport, nil
default:
return nil, errors.New("file format not supported")
}
default:
return nil, nil
}
}
func (r *report) emailReports(es EmailSetting, file ReportFile) error {
if err := es.Validate(); err != nil {
return errors.Wrap(svcerr.ErrMalformedEntity, err)
}
attachments := map[string][]byte{
file.Name: file.Data,
}
if err := r.email.SendEmailNotification(
es.To,
"",
es.Subject,
"",
"",
es.Content,
"",
attachments,
); err != nil {
return err
}
return nil
}
func convertToSenml(g *grpcReadersV1.SenMLMessage) senml.Message {
if g == nil {
return senml.Message{}
}
return senml.Message{
Protocol: g.Base.GetProtocol(),
Subtopic: g.Base.GetSubtopic(),
Publisher: g.Base.GetPublisher(),
Channel: g.Base.GetChannel(),
Name: g.GetName(),
Unit: g.GetUnit(),
Time: g.GetTime(),
UpdateTime: g.GetUpdateTime(),
Value: g.Value,
StringValue: g.StringValue,
DataValue: g.DataValue,
BoolValue: g.BoolValue,
Sum: g.Sum,
}
}
func convertToReports(metric Metric, senmlMsgs []senml.Message) []Report {
if metric.ClientID != "" {
return []Report{
{
Metric: metric,
Messages: senmlMsgs,
},
}
}
return groupReportsByPublisher(metric, senmlMsgs)
}
func groupReportsByPublisher(metric Metric, sMsgs []senml.Message) []Report {
publishers := map[string][]senml.Message{}
for _, msg := range sMsgs {
publishers[msg.Publisher] = append(publishers[msg.Publisher], msg)
}
var groupedReports []Report
for publisher, messages := range publishers {
gMetric := metric
gMetric.ClientID = publisher
groupedReports = append(groupedReports, Report{
Metric: gMetric,
Messages: messages,
})
}
return groupedReports
}
+466
View File
@@ -0,0 +1,466 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package reports_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/0x6flab/namegenerator"
"github.com/absmach/magistrala/internal/testsutil"
pkglog "github.com/absmach/magistrala/pkg/logger"
pkgSch "github.com/absmach/magistrala/pkg/schedule"
remocks "github.com/absmach/magistrala/re/mocks"
readmocks "github.com/absmach/magistrala/readers/mocks"
"github.com/absmach/magistrala/reports"
"github.com/absmach/magistrala/reports/mocks"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var (
namegen = namegenerator.NewGenerator()
userID = testsutil.GenerateUUID(&testing.T{})
domainID = testsutil.GenerateUUID(&testing.T{})
schedule = pkgSch.Schedule{
StartDateTime: time.Now().Add(-time.Hour),
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: time.Now().Add(-time.Hour),
}
reportName = namegen.Generate()
rptConfig = reports.ReportConfig{
ID: testsutil.GenerateUUID(&testing.T{}),
Name: reportName,
DomainID: domainID,
Status: reports.EnabledStatus,
Schedule: schedule,
CreatedBy: userID,
UpdatedBy: userID,
UpdatedAt: time.Now(),
}
)
func newService(runInfo chan pkglog.RunInfo) (reports.Service, *mocks.Repository, *remocks.Ticker) {
repo := new(mocks.Repository)
mockTicker := new(remocks.Ticker)
idProvider := uuid.NewMock()
readersSvc := new(readmocks.ReadersServiceClient)
e := new(remocks.Emailer)
return reports.NewService(repo, runInfo, idProvider, mockTicker, e, readersSvc), repo, mockTicker
}
func TestAddReportConfig(t *testing.T) {
svc, repo, _ := newService(make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
cfg reports.ReportConfig
res reports.ReportConfig
err error
}{
{
desc: "Add report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: reports.ReportConfig{
Name: reportName,
Schedule: schedule,
},
res: rptConfig,
err: nil,
},
{
desc: "Add report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: reports.ReportConfig{
Name: reportName,
Schedule: schedule,
},
err: repoerr.ErrCreateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.AddReportConfig(context.Background(), tc.session, tc.cfg)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.NotEmpty(t, res.ID, "expected non-empty result in ID")
assert.Equal(t, tc.cfg.Name, res.Name)
assert.Equal(t, tc.cfg.Schedule, res.Schedule)
}
defer repoCall.Unset()
})
}
}
func TestViewReportConfig(t *testing.T) {
svc, repo, _ := newService(make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
res reports.ReportConfig
err error
}{
{
desc: "view report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
res: rptConfig,
err: nil,
},
{
desc: "view report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("ViewReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.ViewReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestUpdateReportConfig(t *testing.T) {
svc, repo, _ := newService(make(chan pkglog.RunInfo))
newName := namegen.Generate()
now := time.Now().Add(time.Hour)
cases := []struct {
desc string
session authn.Session
cfg reports.ReportConfig
res reports.ReportConfig
err error
}{
{
desc: "update report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: reports.ReportConfig{
Name: newName,
ID: rptConfig.ID,
Schedule: schedule,
},
res: reports.ReportConfig{
Name: newName,
ID: rptConfig.ID,
DomainID: rptConfig.DomainID,
Status: rptConfig.Status,
Schedule: rptConfig.Schedule,
UpdatedAt: now,
UpdatedBy: userID,
},
err: nil,
},
{
desc: "update report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: reports.ReportConfig{
Name: rptConfig.Name,
ID: rptConfig.ID,
Schedule: schedule,
},
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.UpdateReportConfig(context.Background(), tc.session, tc.cfg)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestListReportsConfig(t *testing.T) {
svc, repo, _ := newService(make(chan pkglog.RunInfo))
numConfigs := 50
now := time.Now().Add(time.Hour)
var configs []reports.ReportConfig
for i := 0; i < numConfigs; i++ {
c := reports.ReportConfig{
ID: testsutil.GenerateUUID(t),
Name: namegen.Generate(),
DomainID: domainID,
Status: reports.EnabledStatus,
CreatedAt: now,
CreatedBy: userID,
Schedule: schedule,
}
configs = append(configs, c)
}
cases := []struct {
desc string
session authn.Session
pageMeta reports.PageMeta
res reports.ReportConfigPage
err error
}{
{
desc: "list report configs successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: reports.PageMeta{},
res: reports.ReportConfigPage{
PageMeta: reports.PageMeta{
Total: uint64(numConfigs),
Offset: 0,
Limit: 10,
},
ReportConfigs: configs[0:10],
},
err: nil,
},
{
desc: "list report configs successfully with limit",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: reports.PageMeta{
Limit: 100,
},
res: reports.ReportConfigPage{
PageMeta: reports.PageMeta{
Total: uint64(numConfigs),
Offset: 0,
Limit: 100,
},
ReportConfigs: configs[0:numConfigs],
},
err: nil,
},
{
desc: "list report configs successfully with offset",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: reports.PageMeta{
Offset: 20,
Limit: 10,
},
res: reports.ReportConfigPage{
PageMeta: reports.PageMeta{
Total: uint64(numConfigs),
Offset: 20,
Limit: 10,
},
ReportConfigs: configs[20:30],
},
err: nil,
},
{
desc: "list report configs with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: reports.PageMeta{},
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("ListReportsConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.ListReportsConfig(context.Background(), tc.session, tc.pageMeta)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestRemoveReportConfig(t *testing.T) {
svc, repo, _ := newService(make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
err error
}{
{
desc: "remove report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: nil,
},
{
desc: "remove report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: svcerr.ErrRemoveEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("RemoveReportConfig", mock.Anything, mock.Anything).Return(tc.err)
err := svc.RemoveReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
defer repoCall.Unset()
})
}
}
func TestEnableReportConfig(t *testing.T) {
svc, repo, _ := newService(make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
status reports.Status
res reports.ReportConfig
err error
}{
{
desc: "enable report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: reports.EnabledStatus,
res: rptConfig,
err: nil,
},
{
desc: "enable report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: reports.EnabledStatus,
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfigStatus", context.Background(), mock.Anything).Return(tc.res, tc.err)
res, err := svc.EnableReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestDisableReportConfig(t *testing.T) {
svc, repo, _ := newService(make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
status reports.Status
res reports.ReportConfig
err error
}{
{
desc: "disable report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: reports.DisabledStatus,
res: reports.ReportConfig{
ID: rptConfig.ID,
Name: rptConfig.Name,
DomainID: rptConfig.DomainID,
Status: reports.DisabledStatus,
Schedule: schedule,
UpdatedBy: userID,
UpdatedAt: time.Now(),
},
err: nil,
},
{
desc: "disable report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: reports.DisabledStatus,
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfigStatus", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.DisableReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package reports
import (
"encoding/json"
"strings"
svcerr "github.com/absmach/supermq/pkg/errors/service"
)
// Status represents Rule status.
type Status uint8
// Possible User status values.
const (
// EnabledStatus represents enabled Rule.
EnabledStatus Status = iota
// DisabledStatus represents disabled Rule.
DisabledStatus
// DeletedStatus represents a rule that will be deleted.
DeletedStatus
// AllStatus is used for querying purposes to list rules irrespective
// of their status - both enabled and disabled. It is never stored in the
// database as the actual User status and should always be the largest
// value in this enumeration.
AllStatus
)
// String representation of the possible status values.
const (
Disabled = "disabled"
Enabled = "enabled"
Deleted = "deleted"
All = "all"
Unknown = "unknown"
)
func (s Status) String() string {
switch s {
case DisabledStatus:
return Disabled
case EnabledStatus:
return Enabled
case DeletedStatus:
return Deleted
case AllStatus:
return All
default:
return Unknown
}
}
// ToStatus converts string value to a valid status.
func ToStatus(status string) (Status, error) {
switch status {
case "", Enabled:
return EnabledStatus, nil
case Disabled:
return DisabledStatus, nil
case Deleted:
return DeletedStatus, nil
case All:
return AllStatus, nil
}
return Status(0), svcerr.ErrInvalidStatus
}
func (s Status) MarshalJSON() ([]byte, error) {
return json.Marshal(s.String())
}
func (s *Status) UnmarshalJSON(data []byte) error {
str := strings.Trim(string(data), "\"")
val, err := ToStatus(str)
*s = val
return err
}
+4
View File
@@ -38,6 +38,10 @@ packages:
interfaces:
Service:
Repository:
github.com/absmach/magistrala/reports:
interfaces:
Service:
Repository:
github.com/absmach/magistrala/api/grpc/readers/v1:
interfaces:
ReadersServiceClient: