Compare commits

...

175 Commits

Author SHA1 Message Date
dusan e35ac13390 NOISSUE - Archive
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 23:37:04 +02:00
dusan 94255e2393 Add warning
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 12:11:19 +02:00
dusan 02b13d8e0c Rename to magistrala-old to be replaced with magistrala
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 11:32:57 +02:00
Steve Munene 1f91de480e NOISSUE - Fix alarms metadata (#444)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-18 13:30:06 +01:00
dusan eb29b4e298 NOISSUE - Update SMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-18 10:26:47 +01:00
dusan 5841d3f7e4 NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-17 19:57:15 +01:00
dusan f44b910546 NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-17 18:11:14 +01:00
dusan 52896241c5 NOISSUE - Update dependencies
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-17 18:10:29 +01:00
dependabot[bot] 1b36c6e1b6 NOISSUE - Bump the gh-dependency group in /.github/workflows with 3 updates (#443)
Bumps the gh-dependency group in /.github/workflows with 3 updates: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [docker/login-action](https://github.com/docker/login-action) and [dorny/paths-filter](https://github.com/dorny/paths-filter).


Updates `docker/setup-buildx-action` from 3 to 4
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

Updates `docker/login-action` from 3 to 4
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

Updates `dorny/paths-filter` from 3 to 4
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dorny/paths-filter/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
- dependency-name: dorny/paths-filter
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 17:19:02 +01:00
Steve Munene d5d5e8bf7e NOISSUE - Add build tag constraint for middlewares (#442)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-17 14:49:52 +01:00
Steve Munene b2967fb2e5 NOISSUE - Fix SDK test (#441)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-16 16:37:19 +01:00
Steve Munene 7fb5dd7b55 NOISSUE - Refactor listing for rules and reports (#433)
* add access control to rules engine

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

* update authorization method

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

* revert code

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

* initial implementation

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

* remove domain from method

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

* fix failing linter

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

* fix userid parameter

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

* update checksuperadmin method

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

* revert changes

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

* address comments

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-16 14:39:49 +01:00
Steve Munene 2ef8437d8b MG-370 - Add fine grained access control to alarms (#404)
* add access control to rules engine

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

* add access control to reports

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

* add access control to alarms

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

* fix failing linter

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

* remove unused variables

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

* update authorization method

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

* revert code

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

* remove roles

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

* update alarm permissions

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

* update alarm permissions

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

* address comments

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

* fix tests

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

* revert endpoint changes

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

* fix make fetch

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

* revert env variable

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

* remove rule prefix

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

* remove trailing line

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

* remove unused constants

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

* re consumer

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

* update listing

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

* fix tests

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

* fix linter

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

* fix rule roles interface

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

* refactor listing commands

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

* fetch supermq

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

* address coments

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

* update script

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

* address comments

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

* fetch supermq

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

* fix time layout

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

* fix failing linter

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

* fix failing linter

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

* fix role name

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

* fix failing linter

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

* address comments

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

* remove white spaces

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

* update check usperadmin method

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

* update go mod file

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

* fix tests

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

* add missing env variable

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-13 14:29:32 +01:00
dusan 6dbcfcae58 NOISSUE - Update SuperMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-12 11:37:31 +01:00
Ian Ngethe Muchiri ab8d335767 NOISSUE - Update UI docker compose (#439)
Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
2026-03-11 16:22:06 +01:00
dusan 99cea4abe8 NOISSUE - Update Compose to use the latest UI
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-11 15:58:59 +01:00
dusan 04379dc7a9 NOISSUE - Fix SDK bugs
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-11 12:48:15 +01:00
dependabot[bot] 0800b260d5 NOISSUE - Bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp (#434)
Bumps [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.66.0 to 0.67.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.66.0...zpages/v0.67.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.67.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 10:59:45 +01:00
dusan 67feea693e NOISSUE - Fix Reports test status code bug
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-11 10:57:56 +01:00
dusan db1676cb0f NOISSUE - Update Mockery version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-11 09:54:10 +01:00
Steve Munene 4b57387110 NOISSUE - Remove Panic method from go scripts (#437)
* add regex

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

* fix failing linter

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-10 20:46:46 +01:00
Dušan Borovčanin e3373e1b49 NOISSUE - Fix SeaweedFS init (#436)
* Fix SeaweedFS init

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Use 20s wait period

Signed-off-by: dusan <borovcanindusan1@gmail.com>

---------

Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-10 16:29:59 +01:00
dependabot[bot] 453c880efc NOISSUE - Bump go.opentelemetry.io/otel/trace from 1.41.0 to 1.42.0 (#429)
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.41.0 to 1.42.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.41.0...v1.42.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:55:11 +01:00
dependabot[bot] dc2b063b6e NOISSUE - Bump go.opentelemetry.io/otel from 1.41.0 to 1.42.0 (#431)
Bumps [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) from 1.41.0 to 1.42.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.41.0...v1.42.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:54:48 +01:00
dependabot[bot] 65ee66dc32 NOISSUE - Bump github.com/slack-go/slack from 0.18.0 to 0.19.0 (#428)
Bumps [github.com/slack-go/slack](https://github.com/slack-go/slack) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/slack-go/slack/releases)
- [Changelog](https://github.com/slack-go/slack/blob/master/CHANGELOG.md)
- [Commits](https://github.com/slack-go/slack/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: github.com/slack-go/slack
  dependency-version: 0.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:48:14 +01:00
dependabot[bot] df9fe93a98 NOISSUE - Bump golang.org/x/sync from 0.19.0 to 0.20.0 (#430)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/sync/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:47:57 +01:00
dependabot[bot] 84fba105c9 NOISSUE - Bump google.golang.org/grpc from 1.79.1 to 1.79.2 (#432)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.1 to 1.79.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.1...v1.79.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 08:47:42 +01:00
Arvindh f75f08db4a NOISSUE - Fix alarms schema (#425)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2026-03-05 17:04:00 +01:00
Steve Munene be1dc130d6 NOISSUE - Add alarm relation to rules (#424)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-05 15:21:34 +01:00
Steve Munene 178a62c08f MG-370 - Add fine grained access control to reports (#403)
* add access control to rules engine

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

* fix build

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

* remove unused variable

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

* fix report database

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

* fix variable naming

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

* fix entity type

Signed-off-by: Arvindh <arvindh91@gmail.com>

* update authorize method

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

* fix generate report

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

* revert env changes

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

* fix linter

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

* fix failing linter

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

* update generate permission

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

* revert go mod file

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

* revert go mod file

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: Arvindh <arvindh91@gmail.com>
Co-authored-by: Arvindh <arvindh91@gmail.com>
2026-03-05 13:59:22 +01:00
Steve Munene 362a4fc76d MG-370 - Add fine grained access control to rules engine (#402)
* update go mod file

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

* fix rules endpoint tests

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

* fix yaml file

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

* fix build

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

* address comments

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

* remove roles from alarms

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

* change approach for schema combaine

Signed-off-by: Arvindh <arvindh91@gmail.com>

* change approach for schema combaine

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix permissions for rules

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

* fix authorization file

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

* fix linter

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

* fix linter

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: Arvindh <arvindh91@gmail.com>
Co-authored-by: Arvindh <arvindh91@gmail.com>
2026-03-05 11:42:51 +01:00
Ian Ngethe Muchiri 8e75edc9f5 NOISSUE - Add alarms, reports and rules sdk (#423)
* add alarms, reports and rules sdk

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* fix tests

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* fix linter

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

---------

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
2026-03-05 10:54:14 +01:00
Steve Munene a031426715 NOISSUE - Update metadata UI type to string (#422)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-03 16:01:48 +01:00
dusan 962b473a5f Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-03-03 15:35:30 +01:00
Steve Munene de6f3921a4 MG-406 - Fix panic issues in rules engine go script (#407)
* initial implementation

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

* revert variable

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

* fix tests

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

* fix tests

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

* address comments

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

* update tests

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

* update tests

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-03 15:34:27 +01:00
Steve Munene 0a45a96fac NOISSUE - Update Rules metadata migrations (#421)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-03 15:33:58 +01:00
dependabot[bot] c2afb88e79 NOISSUE - Bump github.com/nats-io/nats.go from 1.48.0 to 1.49.0 (#420)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.48.0 to 1.49.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.48.0...v1.49.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 13:07:29 +01:00
Steve Munene 9a3a07cd2e NOISSUE - Update Authorization method (#418)
* fix authorization

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

* fetch supermq

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

* address comments

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

* fetch supermq

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-03-02 12:55:07 +01:00
dependabot[bot] 28e809b9d8 NOISSUE - Bump golang in /docker in the docker-dependency group (#419)
Bumps the docker-dependency group in /docker with 1 update: golang.


Updates `golang` from 1.26rc2-alpine3.22 to 1.26-alpine3.22

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.26-alpine3.22
  dependency-type: direct:production
  dependency-group: docker-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 11:08:51 +01:00
Felix Gateru eb14615cf5 MG-344 - Update Provision Service (#386)
* feat: update provison service

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* refactor: remove duplicate env variables

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* ci: make fetch_supermq

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* docs(README.md): update README

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

---------

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-02-28 17:55:22 +01:00
dusan d652652b79 NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-02-27 21:33:18 +01:00
dependabot[bot] a2087a1f1f NOISSUE - Bump google.golang.org/grpc from 1.78.0 to 1.79.1 (#405)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.78.0 to 1.79.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.78.0...v1.79.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:33:17 +01:00
dependabot[bot] f195ced6a0 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.17.3 to 9.18.0 (#410)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.17.3 to 9.18.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.17.3...v9.18.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:07:10 +01:00
dependabot[bot] a33d1cfe4f NOISSUE - Bump github.com/authzed/authzed-go from 1.7.0 to 1.8.0 (#413)
Bumps [github.com/authzed/authzed-go](https://github.com/authzed/authzed-go) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/authzed/authzed-go/releases)
- [Commits](https://github.com/authzed/authzed-go/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/authzed/authzed-go
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:01:31 +01:00
dependabot[bot] 40cbb66638 NOISSUE - Bump github.com/slack-go/slack from 0.17.3 to 0.18.0 (#411)
Bumps [github.com/slack-go/slack](https://github.com/slack-go/slack) from 0.17.3 to 0.18.0.
- [Release notes](https://github.com/slack-go/slack/releases)
- [Changelog](https://github.com/slack-go/slack/blob/master/CHANGELOG.md)
- [Commits](https://github.com/slack-go/slack/compare/v0.17.3...v0.18.0)

---
updated-dependencies:
- dependency-name: github.com/slack-go/slack
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:00:26 +01:00
dependabot[bot] 08b5ac52cf NOISSUE - Bump github.com/caarlos0/env/v11 from 11.3.1 to 11.4.0 (#412)
Bumps [github.com/caarlos0/env/v11](https://github.com/caarlos0/env) from 11.3.1 to 11.4.0.
- [Release notes](https://github.com/caarlos0/env/releases)
- [Commits](https://github.com/caarlos0/env/compare/v11.3.1...v11.4.0)

---
updated-dependencies:
- dependency-name: github.com/caarlos0/env/v11
  dependency-version: 11.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 10:00:03 +01:00
Felix Gateru 0c1ccf1f04 MG-408 - Fix client authentication in readers (#409)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-02-25 09:59:43 +01:00
Felix Gateru f28a3e8390 MG-401 - Fix authorization error in Magistrala services (#400)
* chore: update SMQ version

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* chore: run make fetch_supermq

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* tests: fix provison tests

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* ci(build.yaml): get go version from go mod

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

---------

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-02-11 11:17:13 +01:00
Felix Gateru 3685d231cf NOISSUE - Fix API docs page (#399)
* fix: fix api docs page

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* chore: run make fetch_supermq

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

---------

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-02-09 16:21:58 +01:00
Steve Munene f3c5d603a0 MG-379 - Refactor makefile (#385)
* refactor command

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

* update makefile and readme file

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

* update commands

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

* Add changes for apple silicon

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Co-authored-by: dorcaslitunya <anonolitunya@gmail.com>
2026-01-29 09:50:38 +01:00
dependabot[bot] 5050caa3d3 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.17.2 to 9.17.3 (#391)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.17.2 to 9.17.3.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/v9.17.3/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.17.2...v9.17.3)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.17.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-29 09:45:44 +01:00
dependabot[bot] 4ed31c2c66 NOISSUE - Bump github.com/absmach/certs from 0.18.4 to 0.18.5 (#392)
Bumps [github.com/absmach/certs](https://github.com/absmach/certs) from 0.18.4 to 0.18.5.
- [Release notes](https://github.com/absmach/certs/releases)
- [Commits](https://github.com/absmach/certs/compare/v0.18.4...v0.18.5)

---
updated-dependencies:
- dependency-name: github.com/absmach/certs
  dependency-version: 0.18.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-29 09:45:23 +01:00
dusan bba6c57532 NOISSUE - Update SMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-01-23 17:37:28 +01:00
Felix Gateru 2c37cfc53c MG-389 - Return all values on alarm update (#390)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-01-23 10:08:43 +01:00
Felix Gateru f3ce37a80d NOISSUE - Update SMQ & Postgres version (#388)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-01-22 16:40:44 +01:00
Nataly Musilah 8203666e58 NOISSUE - Update READMEs (#380)
* fix web url

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* add new readmes

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* add other services

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* fix docker link

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* fix reports examples

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* revert go.sum change

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

---------

Signed-off-by: Musilah <nataleigh.nk@gmail.com>
2026-01-22 14:16:52 +01:00
dependabot[bot] e320051ec0 NOISSUE - Bump golang in /docker in the docker-dependency group (#381)
Bumps the docker-dependency group in /docker with 1 update: golang.


Updates `golang` from 1.25.5-alpine3.22 to 1.26rc2-alpine3.22

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.26rc2-alpine3.22
  dependency-type: direct:production
  dependency-group: docker-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 14:14:26 +01:00
dependabot[bot] ec055cb4b4 NOISSUE - Bump github.com/go-chi/chi/v5 from 5.2.3 to 5.2.4 (#382)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.2.3 to 5.2.4.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.3...v5.2.4)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 14:13:36 +01:00
dependabot[bot] bcae0de50e NOISSUE - Bump github.com/absmach/certs from 0.18.3 to 0.18.4 (#384)
Bumps [github.com/absmach/certs](https://github.com/absmach/certs) from 0.18.3 to 0.18.4.
- [Release notes](https://github.com/absmach/certs/releases)
- [Commits](https://github.com/absmach/certs/compare/v0.18.3...v0.18.4)

---
updated-dependencies:
- dependency-name: github.com/absmach/certs
  dependency-version: 0.18.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 14:13:18 +01:00
dusan ba344290ed Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-01-22 14:03:52 +01:00
Steve Munene c7bc9b7cf9 NOISSUE - Fetch SuperMQ (#387)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-01-21 15:59:46 +01:00
Steve Munene b02b3411db NOISSUE - Fetch certs (#383)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2026-01-19 13:54:31 +01:00
Felix Gateru 5a769e1981 MG-327 - Allow superadmin to access channel messages (#377)
* refactor: allow superadmin to read channel messages

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* dep: update SMQ version

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

---------

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2026-01-15 17:53:41 +01:00
Arvindh 91bdb274b2 NOISSUE - Add ui-backend-db health check (#376)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2026-01-14 14:56:07 +01:00
dependabot[bot] 982636a87a NOISSUE - Bump gonum.org/v1/gonum from 0.16.0 to 0.17.0 (#375)
Bumps [gonum.org/v1/gonum](https://github.com/gonum/gonum) from 0.16.0 to 0.17.0.
- [Release notes](https://github.com/gonum/gonum/releases)
- [Commits](https://github.com/gonum/gonum/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: gonum.org/v1/gonum
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-12 08:54:20 +01:00
dusan 68ef843564 NOISSUE - Update Nginx ports config
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-31 18:28:08 +01:00
dusan a6cd64ed6a NOISSUE - Update Dockerfile Go version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-31 17:38:51 +01:00
Arvindh 67180a55f7 NOISSUE - Update Errors (#374)
* update MG errors

Signed-off-by: Arvindh <arvindh91@gmail.com>

* update MG errors

Signed-off-by: Arvindh <arvindh91@gmail.com>

* sync with supermq main

Signed-off-by: Arvindh <arvindh91@gmail.com>

* update MG errors

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-12-31 16:57:06 +01:00
dusan c9b3107ad9 NOISSUE - Update SMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-31 11:04:52 +01:00
dependabot[bot] da289e58b5 NOISSUE - Bump google.golang.org/grpc from 1.77.0 to 1.78.0 (#372)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.77.0 to 1.78.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.77.0...v1.78.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.78.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 12:11:36 +01:00
dependabot[bot] 94458dde3e NOISSUE - Bump github.com/jackc/pgx/v5 from 5.7.6 to 5.8.0 (#373)
* NOISSUE - Housekeeping

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Add DeepWiki link

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Update GH Actions for API docs

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Update SMQ

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Bump github.com/spf13/cobra from 1.10.1 to 1.10.2 (#362)

Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.10.1...v1.10.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* NOISSUE - Bump github.com/redis/go-redis/v9 from 9.17.1 to 9.17.2 (#361)

Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.17.1 to 9.17.2.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/v9.17.2/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.17.1...v9.17.2)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.17.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* NOISSUE - Update SMQ

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Bump golang.org/x/sync from 0.18.0 to 0.19.0 (#365)

Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.18.0 to 0.19.0.
- [Commits](https://github.com/golang/sync/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Update SMQ version

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Bump go.opentelemetry.io/otel/trace from 1.38.0 to 1.39.0 (#364)

Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.38.0 to 1.39.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.38.0...v1.39.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* NOISSUE - Bump google.golang.org/protobuf from 1.36.10 to 1.36.11 (#363)

Bumps google.golang.org/protobuf from 1.36.10 to 1.36.11.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* NOISSUE - Bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp (#366)

Bumps [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.63.0 to 0.64.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.63.0...zpages/v0.64.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.64.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* NOISSUE - Bump github.com/absmach/certs from 0.18.2 to 0.18.3 (#359)

Bumps [github.com/absmach/certs](https://github.com/absmach/certs) from 0.18.2 to 0.18.3.
- [Release notes](https://github.com/absmach/certs/releases)
- [Commits](https://github.com/absmach/certs/compare/v0.18.2...v0.18.3)

---
updated-dependencies:
- dependency-name: github.com/absmach/certs
  dependency-version: 0.18.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* NOISSUE - Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0 (#368)

Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.47.0 to 1.48.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* NOISSUE - Update SMQ version

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Refactor `alarms`, `reports` and `rule engines` middlewares (#369)

* refactor middleware

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

* update go mod file

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

* fix tests

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

* fix rules tests

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

* revert common code

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

* update supermq version

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

---------

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

* NOISSUE - Sync with SMQ

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Update MG and SMQ versions

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Update MG and SMQ versions

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Revert "NOISSUE - Update MG and SMQ versions"

This reverts commit 7faca6b73d6ac27ae4d28ee45f4860bbb7340ef4.

* Revert SMQ version tag

Use latest for development.

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Bump github.com/jackc/pgx/v5 from 5.7.6 to 5.8.0

Bumps [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) from 5.7.6 to 5.8.0.
- [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v5.7.6...v5.8.0)

---
updated-dependencies:
- dependency-name: github.com/jackc/pgx/v5
  dependency-version: 5.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dusan <borovcanindusan1@gmail.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Co-authored-by: dusan <borovcanindusan1@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Steve Munene <stevenyaga2014@gmail.com>
2025-12-29 11:38:54 +01:00
dusan 8dc4d72b98 NOISSUE - Improve running different versions
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dusan 4dbdea585d Revert SMQ version tag
Use latest for development.

Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dusan e75536418a Revert "NOISSUE - Update MG and SMQ versions"
This reverts commit 7faca6b73d6ac27ae4d28ee45f4860bbb7340ef4.
2025-12-29 11:23:43 +01:00
dusan e64140ce75 NOISSUE - Update MG and SMQ versions
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dusan 12a5919a73 NOISSUE - Update MG and SMQ versions
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dusan 845cf4c75e NOISSUE - Sync with SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
Steve Munene 99e2c7aec4 NOISSUE - Refactor alarms, reports and rule engines middlewares (#369)
* refactor middleware

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

* update go mod file

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

* fix tests

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

* fix rules tests

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

* revert common code

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

* update supermq version

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-12-29 11:23:43 +01:00
dusan e4cef0fdc2 NOISSUE - Update SMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dependabot[bot] cadb035405 NOISSUE - Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0 (#368)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.47.0 to 1.48.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 11:23:43 +01:00
dependabot[bot] f38b6c072a NOISSUE - Bump github.com/absmach/certs from 0.18.2 to 0.18.3 (#359)
Bumps [github.com/absmach/certs](https://github.com/absmach/certs) from 0.18.2 to 0.18.3.
- [Release notes](https://github.com/absmach/certs/releases)
- [Commits](https://github.com/absmach/certs/compare/v0.18.2...v0.18.3)

---
updated-dependencies:
- dependency-name: github.com/absmach/certs
  dependency-version: 0.18.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 11:23:43 +01:00
dependabot[bot] af93588588 NOISSUE - Bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp (#366)
Bumps [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.63.0 to 0.64.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.63.0...zpages/v0.64.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.64.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 11:23:43 +01:00
dependabot[bot] a1a0e459d9 NOISSUE - Bump google.golang.org/protobuf from 1.36.10 to 1.36.11 (#363)
Bumps google.golang.org/protobuf from 1.36.10 to 1.36.11.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 11:23:43 +01:00
dependabot[bot] a5f6afcb14 NOISSUE - Bump go.opentelemetry.io/otel/trace from 1.38.0 to 1.39.0 (#364)
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.38.0 to 1.39.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.38.0...v1.39.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 11:23:43 +01:00
dusan 8348633e06 Update SMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dependabot[bot] 31a96ac7a1 NOISSUE - Bump golang.org/x/sync from 0.18.0 to 0.19.0 (#365)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.18.0 to 0.19.0.
- [Commits](https://github.com/golang/sync/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 11:23:43 +01:00
dusan a7f6d33cf0 NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dependabot[bot] 1342760593 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.17.1 to 9.17.2 (#361)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.17.1 to 9.17.2.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/v9.17.2/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.17.1...v9.17.2)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.17.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 11:23:43 +01:00
dependabot[bot] 4f99fffcee NOISSUE - Bump github.com/spf13/cobra from 1.10.1 to 1.10.2 (#362)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.10.1...v1.10.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 11:23:43 +01:00
dusan 833701cf66 NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dusan 35f99e6e6c NOISSUE - Update GH Actions for API docs
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dusan 867ae73a2a NOISSUE - Add DeepWiki link
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:43 +01:00
dusan 5e504aa104 NOISSUE - Housekeeping
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-12-29 11:23:36 +01:00
dusan 8e8e02f0cb NOISSUE - Update SMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-11-28 18:36:50 +01:00
dependabot[bot] a257e8049e NOISSUE - Bump github.com/rubenv/sql-migrate from 1.8.0 to 1.8.1 (#354)
Bumps [github.com/rubenv/sql-migrate](https://github.com/rubenv/sql-migrate) from 1.8.0 to 1.8.1.
- [Commits](https://github.com/rubenv/sql-migrate/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/rubenv/sql-migrate
  dependency-version: 1.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-26 19:26:42 +01:00
dependabot[bot] 1c2050bc20 NOISSUE - Bump actions/checkout in /.github/workflows in the gh-dependency group (#353)
Bumps the gh-dependency group in /.github/workflows with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-26 17:42:26 +01:00
dependabot[bot] c6ade6603e NOISSUE - Bump google.golang.org/grpc from 1.76.0 to 1.77.0 (#355)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.76.0 to 1.77.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.76.0...v1.77.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.77.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-26 17:42:00 +01:00
dependabot[bot] 5149cf9ba0 NOISSUE - Bump github.com/authzed/authzed-go from 1.6.0 to 1.7.0 (#356)
Bumps [github.com/authzed/authzed-go](https://github.com/authzed/authzed-go) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/authzed/authzed-go/releases)
- [Commits](https://github.com/authzed/authzed-go/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/authzed/authzed-go
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-26 17:41:40 +01:00
dependabot[bot] bd953651b4 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.16.0 to 9.17.0 (#357)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.16.0 to 9.17.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.16.0...v9.17.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-26 17:41:10 +01:00
dependabot[bot] f11625071f NOISSUE - Bump golangci/golangci-lint-action (#351)
Bumps the gh-dependency group in /.github/workflows with 1 update: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action).


Updates `golangci/golangci-lint-action` from 8 to 9
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v8...v9)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-19 10:44:36 +01:00
dusan 98397efd9d Festch SuperMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-11-19 10:13:16 +01:00
Steve Munene 257db27769 MG-132 - Improve RE tests (#346)
* initial implementation

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

* add coverage for api tests

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

* add coverage for api tests

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

* add tests for handler

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

* add tests for start schedular

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

* fix failing linter

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

* fix failing linter

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

* fix failing linter

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

* fix race condition

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

* address comments

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

* fix addrule test

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

* fix list rule method

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

* use sorting for the slice

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

* fetch supermq

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-11-10 18:03:10 +01:00
dependabot[bot] a26d84b12d NOISSUE - Bump github.com/gofrs/uuid/v5 from 5.3.2 to 5.4.0 (#345)
Bumps [github.com/gofrs/uuid/v5](https://github.com/gofrs/uuid) from 5.3.2 to 5.4.0.
- [Release notes](https://github.com/gofrs/uuid/releases)
- [Commits](https://github.com/gofrs/uuid/compare/v5.3.2...v5.4.0)

---
updated-dependencies:
- dependency-name: github.com/gofrs/uuid/v5
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 13:54:01 +01:00
dependabot[bot] a769de809e NOISSUE - Bump github.com/vadv/gopher-lua-libs from 0.7.0 to 0.8.0 (#348)
Bumps [github.com/vadv/gopher-lua-libs](https://github.com/vadv/gopher-lua-libs) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/vadv/gopher-lua-libs/releases)
- [Changelog](https://github.com/vadv/gopher-lua-libs/blob/master/.goreleaser.yml)
- [Commits](https://github.com/vadv/gopher-lua-libs/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: github.com/vadv/gopher-lua-libs
  dependency-version: 0.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 13:53:14 +01:00
dependabot[bot] 06a42526dd NOISSUE - Bump golang.org/x/sync from 0.17.0 to 0.18.0 (#349)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/sync/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 13:52:03 +01:00
dusan de74711554 NOISSUE - Simplify object storage setup
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-11-05 12:25:37 +01:00
dependabot[bot] 678c61498b NOISSUE - Bump golang in /docker in the docker-dependency group (#343)
Bumps the docker-dependency group in /docker with 1 update: golang.


Updates `golang` from 1.25.1-alpine to 1.25.3-alpine

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.25.3-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docker-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 09:53:21 +01:00
Dušan Borovčanin 7bc02a2816 NOISSUE - Update SMQ and Certs dependencies (#342)
* NOISSUE - Update Certs dependencies

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* NOISSUE - Update Certs dependencies

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Fix linter errors

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Fix testing errors

Signed-off-by: dusan <borovcanindusan1@gmail.com>

---------

Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-11-03 09:52:17 +01:00
Sammy Kerata Oina 2720ad0497 NOISSUE - Update callhome dependency version (#341)
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-10-31 13:30:46 +01:00
Steve Munene 517588b675 NOISSUE - Update API docs (#340)
* update rules api docs

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

* update api docs

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

* fix url

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-31 13:09:42 +01:00
dusan 7ee8c26864 NOISSUE - Fix API docs
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-30 18:28:29 +01:00
dependabot[bot] 6015c548fe NOISSUE - Bump github.com/redis/go-redis/v9 from 9.14.1 to 9.16.0 (#338)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.14.1 to 9.16.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.14.1...v9.16.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 11:20:23 +01:00
Dušan Borovčanin f5171a2c03 NOISSUE - Use S3 storage for images (#337)
* NOISSUE - Add object storage for UI images

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Simplify local object storage

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* WIP - Seaweed

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Add dynamic domain

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Update compose

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Update bucket init

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Update bucket init

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Update mod

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Fix compose format

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Fix UI backend env

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Fix env for backend URL

Signed-off-by: dusan <borovcanindusan1@gmail.com>

---------

Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-29 10:07:36 +01:00
Nataly Musilah 7e4f26ecf5 NOISSUE - Add SenML ordering (#339)
Signed-off-by: Musilah <nataleigh.nk@gmail.com>
2025-10-29 09:21:16 +01:00
dusan 7236666e1d NOISSUE - Sync SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-27 13:52:46 +01:00
Dušan Borovčanin 7bdf4c681e NOISSUE - Update dependencies (#336)
* NOISSUE - Update dependencies

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Update go.mod

Signed-off-by: dusan <borovcanindusan1@gmail.com>

---------

Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-20 20:45:24 +02:00
dependabot[bot] d4b7ed2a1e NOISSUE - Bump github.com/nats-io/nats.go from 1.46.1 to 1.47.0 (#335)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.46.1 to 1.47.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.46.1...v1.47.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 12:46:23 +02:00
dependabot[bot] 02c99ac7e4 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.14.0 to 9.14.1 (#334)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.14.0 to 9.14.1.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/v9.14.1/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.14.0...v9.14.1)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 11:45:15 +02:00
dusan 164a64bc25 NOISSUE - Fetch Supermq
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-15 21:53:12 +02:00
dependabot[bot] b4108d14a9 NOISSUE - Bump google.golang.org/grpc from 1.75.1 to 1.76.0 (#331)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.75.1 to 1.76.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.75.1...v1.76.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.76.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-13 13:38:49 +02:00
Steve Munene 5a6e0343dc NOISSUE - Fix Report time range display (#330)
* fx to and from

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

* change to UTC

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

* fix template pagination and address comment

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

* revert env variable

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

* fix pagination

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

* fix failing linter

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

* fix failing linter

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-09 16:28:15 +02:00
Steve Munene 8d4ead8e86 NOISSUE - Add timezone support for reports (#329)
* add timezone support

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

* update supermq

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

* revert env variable

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

* fix failing linter

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

* address comments

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

* revert env variable

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

* add reports title for context

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-08 18:27:02 +02:00
Steve Munene ce5cb76dd4 NOISSUE - Remove reports trailing empty page (#328)
* remove empty page

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

* revert env variable

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-07 17:24:09 +02:00
dependabot[bot] b9f401cb73 NOISSUE - Bump peter-evans/repository-dispatch (#323)
Bumps the gh-dependency group in /.github/workflows with 1 update: [peter-evans/repository-dispatch](https://github.com/peter-evans/repository-dispatch).


Updates `peter-evans/repository-dispatch` from 3 to 4
- [Release notes](https://github.com/peter-evans/repository-dispatch/releases)
- [Commits](https://github.com/peter-evans/repository-dispatch/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peter-evans/repository-dispatch
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 11:12:44 +02:00
dependabot[bot] 264e0c1a25 NOISSUE - Bump github.com/authzed/authzed-go from 1.5.0 to 1.6.0 (#324)
Bumps [github.com/authzed/authzed-go](https://github.com/authzed/authzed-go) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/authzed/authzed-go/releases)
- [Commits](https://github.com/authzed/authzed-go/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/authzed/authzed-go
  dependency-version: 1.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 11:12:24 +02:00
dependabot[bot] 7fa86fe9b1 NOISSUE - Bump github.com/nats-io/nats.go from 1.46.0 to 1.46.1 (#325)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.46.0 to 1.46.1.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.46.0...v1.46.1)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.46.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 11:10:39 +02:00
dependabot[bot] a5f847b064 NOISSUE - Bump google.golang.org/protobuf from 1.36.9 to 1.36.10 (#326)
Bumps google.golang.org/protobuf from 1.36.9 to 1.36.10.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 11:05:30 +02:00
dusan 776c77cfcf Fix docs link
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-02 16:09:28 +02:00
dusan 7b52fc2a60 Fix docs link
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-02 15:59:11 +02:00
dusan 282bcc50e8 Fix website link
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-02 14:54:31 +02:00
Steve Munene 3702e99f17 NOISSUE - Update Magistrala certs (#322)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-01 17:38:48 +02:00
dusan 9078a67566 NOISSUE - Update mod file
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-09-28 12:42:29 +02:00
Ian Ngethe Muchiri 7ef90440f2 MG-853 - Add Slack output integration (#315)
* add slack integration

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* allow support for multiple message options

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* add message to slack struct

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* update template name

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* group postgres and slack

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

---------

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
2025-09-26 11:24:31 +02:00
dependabot[bot] a4abb61239 NOISSUE - Bump github.com/nats-io/nats.go from 1.45.0 to 1.46.0 (#319)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.45.0 to 1.46.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.45.0...v1.46.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-25 16:48:15 +02:00
Nataly Musilah 196e323f46 update readmessages via grpc (#316)
Signed-off-by: musilah <nataleigh.nk@gmail.com>
2025-09-19 16:05:07 +02:00
dependabot[bot] 16be00c50c NOISSUE - Bump github.com/eclipse/paho.mqtt.golang from 1.5.0 to 1.5.1 (#313)
Bumps [github.com/eclipse/paho.mqtt.golang](https://github.com/eclipse/paho.mqtt.golang) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/eclipse/paho.mqtt.golang/releases)
- [Commits](https://github.com/eclipse/paho.mqtt.golang/compare/v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: github.com/eclipse/paho.mqtt.golang
  dependency-version: 1.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-17 14:39:34 +02:00
Arvindh f44903b63b NOISSUE - Add user verification env to Rules Engine (#312)
* add user verification env to RE

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add user verification env to Alarms, Reports

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-09-16 10:51:35 +02:00
dependabot[bot] 1433ad6fe4 NOISSUE - Bump google.golang.org/grpc from 1.75.0 to 1.75.1 (#310)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.75.0 to 1.75.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.75.0...v1.75.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.75.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-12 13:38:09 +02:00
dependabot[bot] 699cd052bc NOISSUE - Bump github.com/redis/go-redis/v9 from 9.13.0 to 9.14.0 (#311)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.13.0 to 9.14.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.13.0...v9.14.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-12 13:37:53 +02:00
Nataly Musilah a22685f168 NOISSUE - Add Messages page ordering (#306)
* add messaging ordering

Signed-off-by: musilah <nataleigh.nk@gmail.com>

* fix lint

Signed-off-by: musilah <nataleigh.nk@gmail.com>

* use orderByTime

Signed-off-by: musilah <nataleigh.nk@gmail.com>

* fix endpoint tests

Signed-off-by: musilah <nataleigh.nk@gmail.com>

* return ui build

Signed-off-by: musilah <nataleigh.nk@gmail.com>

* use switch cases

Signed-off-by: musilah <nataleigh.nk@gmail.com>

---------

Signed-off-by: musilah <nataleigh.nk@gmail.com>
2025-09-12 12:17:34 +02:00
Steve Munene 9a621f4a88 MG-308 - Fix reports default template (#308)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-09-12 12:17:00 +02:00
dependabot[bot] f57f8f5a8a NOISSUE - Bump google.golang.org/protobuf from 1.36.8 to 1.36.9 (#303)
Bumps google.golang.org/protobuf from 1.36.8 to 1.36.9.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 13:43:41 +02:00
dependabot[bot] 424d5a6d22 NOISSUE - Bump github.com/spf13/viper from 1.20.1 to 1.21.0 (#304)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.20.1 to 1.21.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.20.1...v1.21.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 13:43:26 +02:00
dependabot[bot] 4428fdc31b NOISSUE - Bump github.com/jackc/pgx/v5 from 5.7.5 to 5.7.6 (#305)
Bumps [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) from 5.7.5 to 5.7.6.
- [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v5.7.5...v5.7.6)

---
updated-dependencies:
- dependency-name: github.com/jackc/pgx/v5
  dependency-version: 5.7.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 13:42:57 +02:00
dependabot[bot] de9637f71a NOISSUE - Bump golang.org/x/sync from 0.16.0 to 0.17.0 (#302)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/sync/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 13:14:17 +02:00
dependabot[bot] 4aa7f71224 NOISSUE - Bump actions/setup-go in /.github/workflows in the gh-dependency group (#295)
Bumps the gh-dependency group in /.github/workflows with 1 update: [actions/setup-go](https://github.com/actions/setup-go).


Updates `actions/setup-go` from 5 to 6
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 10:48:22 +02:00
dependabot[bot] 963eeaa87e NOISSUE - Bump golang in /docker in the docker-dependency group (#300)
Bumps the docker-dependency group in /docker with 1 update: golang.


Updates `golang` from 1.25.0-alpine to 1.25.1-alpine

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.25.1-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docker-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 10:47:26 +02:00
dependabot[bot] b08b457039 NOISSUE - Bump github.com/prometheus/client_golang from 1.23.0 to 1.23.2 (#299)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.23.0 to 1.23.2.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.23.0...v1.23.2)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.23.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 10:46:07 +02:00
dependabot[bot] d718eb0151 NOISSUE - Bump github.com/absmach/callhome from 0.14.0 to 0.18.0 (#296)
Bumps [github.com/absmach/callhome](https://github.com/absmach/callhome) from 0.14.0 to 0.18.0.
- [Release notes](https://github.com/absmach/callhome/releases)
- [Commits](https://github.com/absmach/callhome/compare/v0.14.0...v0.18.0)

---
updated-dependencies:
- dependency-name: github.com/absmach/callhome
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-06 22:30:28 +02:00
Dušan Borovčanin be7ee7a877 NOISSUE - Fix bugs caused by SMQ update (#301)
* Fix SMQ-caused issues

Signed-off-by: dusan <borovcanindusan1@gmail.com>

* Fix tests

Signed-off-by: dusan <borovcanindusan1@gmail.com>

---------

Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-09-06 21:58:41 +02:00
dependabot[bot] 2b97993c30 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.12.1 to 9.13.0 (#298)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.12.1 to 9.13.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.12.1...v9.13.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-06 18:16:24 +02:00
dusan 42af2b4cdf NOISSUE - Update SMQ
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-09-06 18:15:54 +02:00
dependabot[bot] a7bd60ea51 NOISSUE - Bump github.com/absmach/supermq from 0.18.0 to 0.18.1 (#297)
Bumps [github.com/absmach/supermq](https://github.com/absmach/supermq) from 0.18.0 to 0.18.1.
- [Release notes](https://github.com/absmach/supermq/releases)
- [Commits](https://github.com/absmach/supermq/compare/v0.18.0...v0.18.1)

---
updated-dependencies:
- dependency-name: github.com/absmach/supermq
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-06 18:12:24 +02:00
dependabot[bot] b4fc9ea54c NOISSUE - Bump github.com/go-chi/chi/v5 from 5.2.2 to 5.2.3 (#288)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.2.2 to 5.2.3.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.2...v5.2.3)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 15:38:46 +02:00
dependabot[bot] 671d8ddd1d NOISSUE - Bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp (#286)
Bumps [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.62.0 to 0.63.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.62.0...zpages/v0.63.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.63.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 15:23:41 +02:00
dependabot[bot] aac49e5a5d NOISSUE - Bump github.com/gookit/color from 1.5.4 to 1.6.0 (#290)
Bumps [github.com/gookit/color](https://github.com/gookit/color) from 1.5.4 to 1.6.0.
- [Release notes](https://github.com/gookit/color/releases)
- [Commits](https://github.com/gookit/color/compare/v1.5.4...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/gookit/color
  dependency-version: 1.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 15:23:19 +02:00
Dušan Borovčanin 60e256c267 NOISSUE - Replace interface{} with any (#285)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-26 13:26:32 +02:00
dependabot[bot] 21494525fe NOISSUE - Bump actions/checkout in /.github/workflows in the gh-dependency group (#279)
Bumps the gh-dependency group in /.github/workflows with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 09:55:32 +02:00
dependabot[bot] 49fe83fa01 NOISSUE - Bump github.com/nats-io/nats.go from 1.44.0 to 1.45.0 (#280)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.44.0 to 1.45.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.44.0...v1.45.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 09:51:19 +02:00
dependabot[bot] 46dfd26285 NOISSUE - Bump github.com/stretchr/testify from 1.10.0 to 1.11.0 (#283)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 09:50:33 +02:00
dependabot[bot] d2f11592a9 NOISSUE - Bump google.golang.org/protobuf from 1.36.7 to 1.36.8 (#282)
Bumps google.golang.org/protobuf from 1.36.7 to 1.36.8.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 09:49:44 +02:00
dusan 8a1967f98a NOISSUE - Remove duplicate mocks target
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-26 09:46:52 +02:00
dusan 0ab6889000 NOISSUE - Update Go version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-26 09:45:11 +02:00
dependabot[bot] 6abf94ce4a NOISSUE - Bump golang in /docker in the docker-dependency group (#278)
Bumps the docker-dependency group in /docker with 1 update: golang.


Updates `golang` from 1.24.5-alpine to 1.25.0-alpine

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.25.0-alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docker-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 09:18:28 +02:00
dependabot[bot] 879c5e4c4c NOISSUE - Bump google.golang.org/grpc from 1.74.2 to 1.75.0 (#281)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.74.2 to 1.75.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.74.2...v1.75.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.75.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 09:18:11 +02:00
dependabot[bot] 31ae32bd16 NOISSUE - Bump github.com/authzed/authzed-go from 1.4.1 to 1.5.0 (#284)
Bumps [github.com/authzed/authzed-go](https://github.com/authzed/authzed-go) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/authzed/authzed-go/releases)
- [Commits](https://github.com/authzed/authzed-go/compare/v1.4.1...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/authzed/authzed-go
  dependency-version: 1.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 09:17:54 +02:00
Arvindh ffc7a1ff78 NOISSUE - Add callout in Rule Engine Service (#277)
* add callout to re

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add callout to re

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add callout to re

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add rule events

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add rule events

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add rule events

Signed-off-by: Arvindh <arvindh91@gmail.com>

* remove lints

Signed-off-by: Arvindh <arvindh91@gmail.com>

* remove lints

Signed-off-by: Arvindh <arvindh91@gmail.com>

* remove decoders

Signed-off-by: Arvindh <arvindh91@gmail.com>

* remove lints

Signed-off-by: Arvindh <arvindh91@gmail.com>

* remove lints

Signed-off-by: Arvindh <arvindh91@gmail.com>

* replace interface{} with any

Signed-off-by: Arvindh <arvindh91@gmail.com>

* optimization of event

Signed-off-by: Arvindh <arvindh91@gmail.com>

* remove lints

Signed-off-by: Arvindh <arvindh91@gmail.com>

* align code

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-08-26 09:17:27 +02:00
dusan 3413564148 NOISSUE - Update SMQ version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-21 23:16:53 +02:00
dusan 4f1d96cc21 NOISSUE - Update login action
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-20 10:13:35 +02:00
dependabot[bot] 61225a98aa NOISSUE - Bump google.golang.org/protobuf from 1.36.6 to 1.36.7 (#271)
Bumps google.golang.org/protobuf from 1.36.6 to 1.36.7.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 22:58:04 +02:00
dependabot[bot] c3a5cf3e4f NOISSUE - Bump github.com/redis/go-redis/v9 from 9.12.0 to 9.12.1 (#275)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.12.0 to 9.12.1.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.12.0...v9.12.1)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 22:55:57 +02:00
Nataly Musilah e7633ffafd NOISSUE - Update alarms and reports sorting (#272)
* update alarms and reports

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* fix linter

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* fix structs

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* fix defDir

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* remove name from alarms

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* use switch clases

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* chore: update SMQ dep

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* use if statements for 3 cases

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* chore: bump SMQ version

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>

* use api keys

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* use COALESCE for updatedAt

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

* fix lint

Signed-off-by: Musilah <nataleigh.nk@gmail.com>

---------

Signed-off-by: Musilah <nataleigh.nk@gmail.com>
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
Co-authored-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-19 22:34:55 +02:00
dusan 83b5d0b9eb NOISSUE - Update dependencies
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-06 18:50:51 +02:00
Steve Munene 8b4766d740 NOISSUE - Add reports template tests (#264)
* fix template tests

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

* fix failing linter

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

* add endpoint tests

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

* fix failing linter

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

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-08-06 10:18:36 +02:00
Nataly Musilah f4e3cfab6d NOISSUE - Add rules ordering by order and name (#265)
Signed-off-by: Musilah <nataleigh.nk@gmail.com>
2025-08-06 09:49:10 +02:00
277 changed files with 28881 additions and 5283 deletions
+181
View File
@@ -0,0 +1,181 @@
<!--
Copyright (c) Abstract Machines
SPDX-License-Identifier: Apache-2.0
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Magistrala API Documentation</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.30.3/swagger-ui.css">
<style>
body {
margin: 0;
padding: 0;
}
.topbar {
display: none;
}
.service-selector {
background: #1b1b1b;
padding: 20px;
text-align: center;
}
.service-selector h1 {
color: #fff;
margin: 0 0 15px 0;
font-family: sans-serif;
}
.service-dropdown-container {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
.service-dropdown-container label {
color: #fff;
font-family: sans-serif;
font-size: 14px;
font-weight: 500;
}
.service-dropdown {
background: #2d2d2d;
color: white;
border: 1px solid #4990e2;
padding: 10px 40px 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
min-width: 200px;
appearance: none;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path fill="%23ffffff" d="M6 9L1 4h10z"/></svg>');
background-repeat: no-repeat;
background-position: right 12px center;
}
.service-dropdown:hover {
background-color: #3a3a3a;
border-color: #357abd;
}
.service-dropdown:focus {
outline: none;
border-color: #4990e2;
box-shadow: 0 0 0 2px rgba(73, 144, 226, 0.3);
}
/* Responsive styles for mobile */
@media (max-width: 768px) {
.service-selector {
padding: 15px 10px;
}
.service-selector h1 {
font-size: 1.5rem;
margin: 0 0 12px 0;
}
.service-dropdown-container {
flex-direction: column;
gap: 8px;
}
.service-dropdown-container label {
font-size: 13px;
}
.service-dropdown {
width: 100%;
max-width: 300px;
min-width: auto;
font-size: 13px;
padding: 8px 35px 8px 12px;
}
}
@media (max-width: 480px) {
.service-selector h1 {
font-size: 1.25rem;
}
.service-dropdown {
max-width: 250px;
}
}
</style>
</head>
<body>
<div class="service-selector">
<h1>Magistrala API Documentation</h1>
<div class="service-dropdown-container">
<label for="serviceDropdown">Select Service:</label>
<select id="serviceDropdown" class="service-dropdown"></select>
</div>
</div>
<div id="swagger-ui"></div>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.30.3/swagger-ui-bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.30.3/swagger-ui-standalone-preset.js"></script>
<script>
// Available API specifications
const APIs = APIS_PLACEHOLDER;
// Get the service from URL query parameter, default to first service
function getServiceFromURL() {
const params = new URLSearchParams(window.location.search);
const service = params.get('service');
return service && APIs.includes(service) ? service : APIs[0];
}
// Update URL with selected service
function updateURL(service) {
const url = new URL(window.location);
url.searchParams.set('service', service);
window.history.pushState({}, '', url);
}
// Create service selector dropdown
function createServiceDropdown() {
const dropdown = document.getElementById('serviceDropdown');
const currentService = getServiceFromURL();
APIs.forEach(api => {
const option = document.createElement('option');
option.value = api;
let serviceName = api.replace('.yaml', '').replace(/^\w/, c => c.toUpperCase());
if (serviceName.toLowerCase() === 'http') {
serviceName = 'HTTP';
}
option.textContent = serviceName;
if (api === currentService) {
option.selected = true;
}
dropdown.appendChild(option);
});
// Handle dropdown change
dropdown.addEventListener('change', (e) => {
const selectedApi = e.target.value;
loadSwaggerUI(selectedApi);
updateURL(selectedApi);
});
}
// Load Swagger UI with specified API spec
function loadSwaggerUI(apiSpec) {
SwaggerUIBundle({
url: apiSpec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
}
// Initialize
createServiceDropdown();
loadSwaggerUI(getServiceFromURL());
</script>
</body>
</html>
+16 -6
View File
@@ -16,18 +16,28 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Fetch tags for the build
run: |
git fetch --prune --unshallow --tags
- name: Get Go version from go.mod
id: go-version
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.24.x
go-version: ${{ steps.go-version.outputs.version }}
cache-dependency-path: "go.sum"
- name: Set GOBIN
run: echo "GOBIN=$HOME/.local/bin" >> $GITHUB_ENV
- name: Add GOBIN to PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Run tests
run: |
make test
@@ -40,10 +50,10 @@ jobs:
verbose: true
- name: Set up Docker Build
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to DockerHub
uses: docker/login-action@v3.4
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ secrets.DOCKER_USERNAME }}
@@ -55,7 +65,7 @@ jobs:
- name: Trigger Helm Chart Deployment
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: peter-evans/repository-dispatch@v3
uses: peter-evans/repository-dispatch@v4
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: absmach/amdm
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Check License Header
run: |
+24 -10
View File
@@ -8,24 +8,38 @@ on:
branches:
- main
permissions:
contents: write
jobs:
swagger-ui:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Swagger UI action
id: swagger-ui-action
uses: blokovi/swagger-ui-action@main
with:
dir: "./apidocs/openapi"
pattern: "*.yaml"
debug: "true"
- name: Build Swagger UI
run: |
# Create output directory
mkdir -p swagger-ui
# Copy OpenAPI YAML files and schemas directory
cp apidocs/openapi/*.yaml swagger-ui/
cp -r apidocs/openapi/schemas swagger-ui/
# Get list of YAML files
cd apidocs/openapi
YAML_FILES=$(ls *.yaml | jq -R -s -c 'split("\n")[:-1]')
cd ../..
# Generate index.html from template
sed "s|APIS_PLACEHOLDER|$YAML_FILES|g" .github/swagger-ui-template.html > swagger-ui/index.html
echo "Generated Swagger UI with APIs: $YAML_FILES"
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: swagger-ui
cname: docs.api.magistrala.abstractmachines.fr
publish_dir: ./swagger-ui
cname: docs.api.magistrala.absmach.eu
+28 -18
View File
@@ -15,14 +15,24 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Get Go version from go.mod
id: go-version
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.24.x
go-version: ${{ steps.go-version.outputs.version }}
cache-dependency-path: "go.sum"
- name: Set GOBIN
run: echo "GOBIN=$HOME/.local/bin" >> $GITHUB_ENV
- name: Add GOBIN to PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Fetch SuperMQ
run: |
make fetch_supermq
@@ -39,9 +49,9 @@ jobs:
make all -j $(nproc)
- name: Run linters
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@v9
with:
version: v2.1.6
version: latest
args: --config ./tools/config/.golangci.yaml
run-tests:
@@ -51,18 +61,22 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get Go version from go.mod
id: go-version
run: echo "version=$(grep '^go ' go.mod | awk '{print $2}')" >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.22.x
go-version: ${{ steps.go-version.outputs.version }}
cache-dependency-path: "go.sum"
- name: Check for changes in specific paths
uses: dorny/paths-filter@v3
uses: dorny/paths-filter@v4
id: changes
with:
base: main
@@ -94,9 +108,6 @@ jobs:
internal:
- "internal/**"
pkg-errors:
- "pkg/errors/**"
pkg-events:
- "pkg/events/**"
- "pkg/messaging/**"
@@ -130,7 +141,6 @@ jobs:
- "alarms/**"
- "cmd/alarms/**"
- name: Create coverage directory
run: |
mkdir coverage
@@ -155,11 +165,6 @@ jobs:
run: |
go test --race -v -count=1 -coverprofile=coverage/internal.out ./internal/...
- name: Run pkg errors tests
if: steps.changes.outputs.pkg-errors == 'true' || steps.changes.outputs.workflow == 'true'
run: |
go test --race -v -count=1 -coverprofile=coverage/pkg-errors.out ./pkg/errors/...
- name: Run pkg sdk tests
if: steps.changes.outputs.pkg-sdk == 'true' || steps.changes.outputs.workflow == 'true'
run: |
@@ -180,6 +185,11 @@ jobs:
run: |
go test --race -v -count=1 -coverprofile=coverage/re.out ./re/...
- name: Run reports tests
if: steps.changes.outputs.reports == 'true' || steps.changes.outputs.workflow == 'true'
run: |
go test --race -v -count=1 -coverprofile=coverage/reports.out ./reports/...
- name: Run alarms tests
if: steps.changes.outputs.alarms == 'true' || steps.changes.outputs.workflow == 'true'
run: |
+3
View File
@@ -15,3 +15,6 @@ coverage
# Schemathesis
.hypothesis
# Docker volume mounted data
docker/data/*
+2 -2
View File
@@ -7,7 +7,7 @@
[[drasko]]
Name = "Drasko Draskovic"
Email = "draasko.draskovic@abstractmachines.fr"
Email = "draasko.draskovic@absmach.eu"
GitHub = "drasko"
# However, this role serves only in dead-lock events, or in a special and very rare cases
@@ -26,5 +26,5 @@
[[dusan]]
Name = "Dusan Borovcanin"
Email = "dusan.borovcanin@abstractmachines.fr"
Email = "dusan.borovcanin@absmach.eu"
GitHub = "dborovcanin"
+82 -13
View File
@@ -7,7 +7,31 @@ SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-wri
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
GOARCH ?= amd64
# Auto-detect architecture: use arm64 for Apple Silicon, default to amd64 otherwise
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_M),arm64)
GOARCH ?= arm64
else ifeq ($(UNAME_M),aarch64)
GOARCH ?= arm64
else
GOARCH ?= amd64
endif
# Detect OS for sed compatibility: macOS (BSD sed) vs Linux (GNU sed)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
SED_INPLACE := sed -i ''
else
SED_INPLACE := sed -i
endif
# For Apple Silicon: use amd64 platform for pre-built images (emulation via Rosetta)
# This is needed because upstream stable images may not have ARM64 builds
ifeq ($(UNAME_M),arm64)
DOCKER_PLATFORM := --platform linux/amd64
else
DOCKER_PLATFORM :=
endif
VERSION ?= $(shell git describe --abbrev=0 --tags 2>/dev/null || echo 'unknown')
COMMIT ?= $(shell git rev-parse HEAD)
TIME ?= $(shell date +%F_%T)
@@ -19,7 +43,8 @@ DOCKER_PROJECT ?= $(shell echo $(subst $(space),,$(USER_REPO)) | sed -E 's/[^a-z
DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config
DEFAULT_DOCKER_COMPOSE_COMMAND := up
GRPC_MTLS_CERT_FILES_EXISTS = 0
MOCKERY_VERSION=v3.5.0
MOCKERY = $(GOBIN)/mockery
MOCKERY_VERSION=3.7.0
PKG_PROTO_GEN_OUT_DIR=api/grpc
INTERNAL_PROTO_DIR=internal/proto
INTERNAL_PROTO_FILES := $(shell find $(INTERNAL_PROTO_DIR) -name "*.proto" | sed 's|$(INTERNAL_PROTO_DIR)/||')
@@ -70,6 +95,31 @@ define make_docker_dev
-f docker/Dockerfile.dev ./build
endef
define run_with_arch_detection
@echo "Detecting architecture..."
@if [ "$(DETECTED_ARCH)" = "arm64" ] || [ "$(DETECTED_ARCH)" = "aarch64" ]; then \
echo "ARM64 architecture detected."; \
git checkout $(1); \
GOARCH=arm64 $(MAKE) dockers; \
for svc in $(SERVICES); do \
docker tag ghcr.io/absmach/magistrala/$$svc ghcr.io/absmach/magistrala/$$svc:latest; \
done; \
sed -i.bak 's/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=latest/' docker/.env && rm -f docker/.env.bak; \
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
else \
echo "x86_64 architecture detected."; \
git checkout $(1); \
sed -i.bak 's/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=$(2)/' docker/.env && rm -f docker/.env.bak; \
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
fi
endef
ADDON_SERVICES = bootstrap provision certs timescale-reader timescale-writer postgres-reader postgres-writer
EXTERNAL_SERVICES = prometheus
@@ -96,7 +146,7 @@ FILTERED_SERVICES = $(filter-out $(RUN_ADDON_ARGS), $(SERVICES))
all: $(SERVICES)
.PHONY: all $(SERVICES) dockers dockers_dev latest release run run_addons grpc_mtls_certs check_mtls check_certs test_api mocks
.PHONY: all $(SERVICES) dockers dockers_dev latest release run_latest run_stable run_addons grpc_mtls_certs check_mtls check_certs test_api mocks
clean:
rm -rf ${BUILD_DIR}
@@ -115,10 +165,16 @@ install:
cp $$file $(GOBIN)/magistrala-`basename $$file`; \
done
mocks:
@which mockery > /dev/null || go install github.com/vektra/mockery/v3@$(MOCKERY_VERSION)
mockery --config ./tools/config/.mockery.yaml
$(MOCKERY):
@mkdir -p $(GOBIN)
@mkdir -p mockery
@echo ">> downloading mockery $(MOCKERY_VERSION)..."
@curl -sL https://github.com/vektra/mockery/releases/download/v$(MOCKERY_VERSION)/mockery_$(MOCKERY_VERSION)_Linux_x86_64.tar.gz | tar -xz -C mockery
@mv mockery/mockery $(GOBIN)
@rm -r mockery
mocks: $(MOCKERY)
@$(MOCKERY) --config ./tools/config/.mockery.yaml
DIRS = consumers readers postgres internal
test: mocks
@@ -222,21 +278,21 @@ grpc_mtls_certs:
check_tls:
ifeq ($(GRPC_TLS),true)
@unset GRPC_MTLS
@bash -c 'unset GRPC_MTLS'
@echo "gRPC TLS is enabled"
GRPC_MTLS=
else
@unset GRPC_TLS
@bash -c 'unset GRPC_TLS'
GRPC_TLS=
endif
check_mtls:
ifeq ($(GRPC_MTLS),true)
@unset GRPC_TLS
@bash -c 'unset GRPC_TLS'
@echo "gRPC MTLS is enabled"
GRPC_TLS=
else
@unset GRPC_MTLS
@bash -c 'unset GRPC_MTLS'
GRPC_MTLS=
endif
@@ -252,14 +308,27 @@ endif
fetch_supermq:
@./scripts/supermq.sh
run: check_certs
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
run_latest: check_certs
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
run_stable: check_certs
@version=$$(git describe --abbrev=0 --tags 2>/dev/null) || { echo "Error: No git tags found. Please create a release tag first (e.g., git tag v0.1.0) or use 'make run_latest' instead."; exit 1; }; \
echo "Using stable version: $$version"; \
git checkout $$version; \
$(SED_INPLACE) "s/^SMQ_RELEASE_TAG=.*/SMQ_RELEASE_TAG=$$version/" docker/supermq-docker/.env; \
$(SED_INPLACE) "s/^MG_RELEASE_TAG=.*/MG_RELEASE_TAG=$$version/" docker/.env; \
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) docker compose -f docker/docker-compose.yaml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args); \
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/docker-compose.yaml \
-f docker/addons/timescale-reader/docker-compose.yaml \
-f docker/addons/timescale-writer/docker-compose.yaml \
--env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
run_addons: check_certs
$(foreach SVC,$(RUN_ADDON_ARGS),$(if $(filter $(SVC),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)),,$(error Invalid Service $(SVC))))
@for SVC in $(RUN_ADDON_ARGS); do \
MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yaml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \
DOCKER_DEFAULT_PLATFORM=$(if $(DOCKER_PLATFORM),linux/amd64,) MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yaml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \
done
+114 -75
View File
@@ -1,118 +1,157 @@
> [!WARNING]
> This repository is obsolete. All of its content has been merged to [www.github.com/absmach/magistrala](https://www.github.com/absmach/magistrala).
> Please use that repository for all active development.
<div align="center">
# Magistrala
**A Modern IoT Platform Built on SuperMQ**
**Scalable • Secure • Open-Source**
[![Check License Header](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml)
[![Continuous Delivery](https://github.com/absmach/magistrala/actions/workflows/build.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/build.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/absmach/magistrala)](https://goreportcard.com/report/github.com/absmach/magistrala)
[![Coverage](https://codecov.io/gh/absmach/magistrala/graph/badge.svg?token=SEMDAO3L09)](https://codecov.io/gh/absmach/magistrala)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](LICENSE)
[![Matrix](https://img.shields.io/matrix/magistrala:matrix.org?style=flat-square)](https://matrix.to/#/#magistrala:matrix.org)
### [Guide](https://docs.magistrala.abstractmachines.fr) | [Contributing](CONTRIBUTING.md) | [Website](https://abstractmachines.fr/magistrala.html) | [Chat](https://matrix.to/#/#magistrala:matrix.org)
# SuperMQ
Made with ❤️ by [Abstract Machines](https://abstractmachines.fr/)
### Planetary event-driven infrastructure
**Made with ❤️ by [Abstract Machines](https://absmach.eu/)**
[![Build Status](https://github.com/absmach/magistrala/actions/workflows/build.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/build.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/absmach/magistrala)](https://goreportcard.com/report/github.com/absmach/magistrala)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/absmach/magistrala)
[![Check License Header](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml)
[![Check Generated Files](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yaml)
[![Coverage](https://codecov.io/gh/absmach/magistrala/graph/badge.svg?token=nPCEr5nW8S)](https://codecov.io/gh/absmach/magistrala)
[![License](https://img.shields.io/badge/license-Apache%20v2.0-blue.svg)](LICENSE)
[![Matrix](https://img.shields.io/matrix/supermq%3Amatrix.org?label=Chat&style=flat&logo=matrix&logoColor=white)](https://matrix.to/#/#supermq:matrix.org)
### [Guide](https://magistrala.absmach.eu/docs/) | [Contributing](CONTRIBUTING.md) | [Website](https://absmach.eu/) | [Chat](https://matrix.to/#/#supermq:matrix.org)
</div>
## Introduction 📖
## Introduction 🌍
SuperMQ is a distributed, highly scalable, and secure open-source cloud platform for messaging and event-driven architecture (EDA). It is a planetarily distributed, highly scalable, and secure platform that serves as a robust foundation for building advanced real-time and reactive systems.
Magistrala is a cutting-edge, open-source IoT cloud platform built on top of [SuperMQ](https://github.com/absmach/supermq). It serves as a robust middleware solution for building complex IoT applications. With Magistrala, you can connect and manage IoT devices seamlessly using multi-protocol support, all while ensuring security and scalability.
## Why SuperMQ Stands Out 🚀
### Key Benefits:
- **Unified IoT Management**: Connect sensors, actuators, and applications over various network protocols.
- **Scalability and Performance**: Designed to handle enterprise-grade IoT deployments.
- **Secure by Design**: Features such as mutual TLS authentication and fine-grained access control.
- **Open-Source Freedom**: Patent-free, community-driven, and designed for extensibility.
SuperMQ bridges the gap between various network protocols (HTTP, MQTT, WebSocket, CoAP, and more) to provide a seamless messaging experience. Whether you're working on IoT solutions, real-time data pipelines, or event-driven systems, MagisSuperMQtrala has you covered. 🌐✨
## Key Features 🌟
## ✨ Features
- **Multi-Protocol Connectivity**: HTTP, MQTT, WebSocket, CoAP, and more! 🌉
- **Secure by Design**: Mutual TLS (mTLS) with X.509 Certificates, JWT support, and multi-protocol authorization. 🔒
- **Fine-Grained Access Control**: Support for ABAC and RBAC policies. 📜
- **Multi-Tenant**: Manage multiple domains seamlessly. 🏢
- **Multi-User**: Unlimited organizational hierarchies for user management. 👥
- **Application Management**: Group and share messaging clients for streamlined operations. 📱
- **Ease of Use**: Simple and powerful communication channel management, grouping, and sharing. ✨
- **Personal Access Tokens (PATs)**: Scoped and revocable tokens for enhanced security. 🔑
- **Observability**: Integrated logging and instrumentation with Prometheus and OpenTelemetry. 📈
- **Event Sourcing**: Build robust and scalable architectures. ⚡
- **Edge and IoT Ready**: Supports MQTT and CoAP protocols for seamless IoT gateway and sensor communication and management. 🌍
- **Developer-Friendly**: SDKs, CLI tools, and comprehensive documentation to get you started. 👩‍💻👨‍💻
- **Production-Ready**: Container-based deployment using Docker and Kubernetes. 🐳☸️
- 🏢 **Multi-Tenancy**: Support for managing multiple independent domains seamlessly.
- 👥 **Multi-User Platform**: Unlimited organizational hierarchies and user roles for streamlined collaboration.
- 🌐 **Multi-Protocol Connectivity**: HTTP, MQTT, WebSocket, CoAP, and more (see [contrib repository](https://www.github.com/absmach/mg-contrib) for LoRa and OPC UA).
- 💻 **Device Management and Provisioning**: Including Zero-Touch provisioning for seamless device onboarding.
- 🛡️ **Mutual TLS Authentication (mTLS)**: Secure communication using X.509 certificates.
- 📜 **Fine-Grained Access Control**: Support for ABAC and RBAC policies.
- 💾 **Message Persistence**: Timescale and PostgreSQL support (see [contrib repository](https://www.github.com/absmach/mg-contrib) for Cassandra, InfluxDB, and MongoDB).
- 🔄 **Rules Engine (RE)**: Automate processes with flexible rules for decision-making.
- 🚨 **Alarms and Triggers**: Immediate notifications for critical IoT events.
- 📅 **Scheduled Actions**: Plan and execute tasks at predefined times.
- 📝 **Audit Logs**: Maintain a detailed history of platform activities for compliance and debugging.
- 📊 **Platform Logging and Instrumentation**: Integrated with Prometheus and OpenTelemetry.
-**Event Sourcing**: Streamlined architecture for real-time IoT event processing.
- 🐳 **Container-Based Deployment**: Fully compatible with Docker and Kubernetes.
- 🌍 **Edge and IoT Ready**: Agent and Export services for managing remote IoT gateways.
- 🛠️ **Developer Tools**: Comprehensive SDK and CLI for efficient development.
- 🏗️ **Domain-Driven Design**: High-quality codebase and extensive test coverage.
## Installation 🛠️
## 🔧 Install
Clone the repository and start the services:
There are multiple ways to run SuperMQ.
First, clone the repository and position to it:
```bash
git clone https://github.com/absmach/supermq.git
cd supermq
```
To run the latest stable (tagged) version, use:
```bash
# Run with latest stable tagged version
make run_stable
```
To run the latest version, use:
```bash
# Run with latest development version (from main branch)
make run_latest
```
The `make run_stable` command will:
- Checkout the repository to the latest git tag
- Update the version in the environment configuration
- Start the services with the stable release
**Note:** After running `make run_stable`, you'll be on a detached HEAD state. To return to your working branch:
```bash
git checkout main
```
### Running on Apple Silicon (M1/M2/M3) Macs
When running SuperMQ on Apple Silicon Macs, the Makefile will automatically detect your ARM64 architecture and build Docker images locally.
**If using Docker Desktop:**
1. **Enable Apple Virtualization Framework**: In Docker Desktop, go to:
- Settings → General → Enable "Use the new Virtualization framework"
2. **Enable Rosetta for x86_64 Emulation**: In Docker Desktop, go to:
- Settings → General → Enable "Use Rosetta for x86_64/amd64 emulation on Apple Silicon"
After enabling these options, restart Docker Desktop, then run `make run_stable` or `make run_latest` as usual.
To manually run SuperMQ, clone the repository and start all core services:
```bash
git clone https://github.com/absmach/magistrala.git
cd magistrala
docker compose -f docker/docker-compose.yaml --env-file docker/.env up
```
Alternatively, use the Makefile for a simpler command:
### Usage 📤📥
```bash
make run args=-d
```
## 📤 Usage
#### Using the CLI:
Check the health of a specific service using the CLI:
**Using the CLI :**
```bash
make cli
./build/cli health <service>
./build/supermq-cli status
```
Replace `<service>` with the name of the service you want to check.
This command retrieves the status of the SuperMQ server and outputs it to the console.
#### Using Curl:
Alternatively, use a simple HTTP GET request to check the platform's health:
**Using HTTP with Curl :**
```bash
curl -X GET http://localhost:8080/health
curl -X GET http://localhost:8080/status
```
For additional usage examples and advanced configurations, visit the [official documentation](https://docs.magistrala.abstractmachines.fr).
This request fetches the server status over HTTP and provides a JSON response.
See our [CLI documentation](https://magistrala.absmach.eu/docs/dev-guide/cli/introduction-to-cli/) for more details.
## 📚 Documentation
## Documentation 📚
Complete documentation is available at the [Magistrala official docs page](https://docs.magistrala.abstractmachines.fr).
The official documentation is hosted at [SuperMQ docs page](https://magistrala.absmach.eu/docs/).
For CLI usage details, visit the [CLI Documentation](https://docs.magistrala.abstractmachines.fr/cli).
Documentation is auto-generated, check out the instructions in the [docs repository](https://github.com/absmach/magistrala-website).
If you spot an error or a need for corrections, please let us know - or even better: send us a PR! 💌
## Community and Contributing 🤝
## 🌐 Community and Contributing
Thank you for your interest in SuperMQ and the desire to contribute!
Join the community and contribute to the future of IoT middleware:
1. Take a look at our [open issues](https://github.com/absmach/magistrala/issues). The [good-first-issue](https://github.com/absmach/magistrala/labels/good-first-issue) label is specifically for issues that are great for getting started.
2. Checkout the [contribution guide](CONTRIBUTING.md) to learn more about our style and conventions.
3. Make your changes compatible to our workflow.
- [Open Issues](https://github.com/absmach/magistrala/issues)
- [Contribution Guide](CONTRIBUTING.md)
- [Matrix Chat](https://matrix.to/#/#magistrala:matrix.org)
Join our community:
- [Matrix Room](https://matrix.to/#/#supermq\:matrix.org)
## 📜 License
## Professional Support 💼
Magistrala is open-source software licensed under the [Apache-2.0](LICENSE) license. Contributions are welcome and encouraged!
Need help deploying SuperMQ or integrating it into your system? Reach out to **[Abstract Machines](https://absmach.eu/)** for professional support and guidance.
## License 📜
## 💼 Professional Support
SuperMQ is open-source software licensed under the [Apache License 2.0](LICENSE). Contributions are welcome!
Need help deploying Magistrala or integrating it into your systems? Contact **[Abstract Machines](https://abstractmachines.fr/)** for expert guidance and support.
## Acknowledgments 🙌
Special thanks to the amazing contributors who make SuperMQ possible. Check out the [MAINTAINERS](MAINTAINERS) file to see the team behind the magic.
Ready to build the future of messaging and event-driven systems? Let's get started! 🚀
+197
View File
@@ -0,0 +1,197 @@
# Alarms
The Alarms service stores, manages and exposes alarms raised by rules and device activity. It consumes alarm events from the message broker, persists them to PostgreSQL, and provides an HTTP API for listing, viewing, updating, and deleting alarms with full authn/authz, metrics, and tracing support.
## Configuration
The service is configured using the following environment variables (values shown are from [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env) as consumed by [docker/docker-compose.yaml](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yaml)):
| Variable | Description | Default |
| --- | --- | --- |
| `MG_ALARMS_LOG_LEVEL` | Log level for the service | `debug` |
| `MG_ALARMS_HTTP_HOST` | HTTP host to bind | `alarms` |
| `MG_ALARMS_HTTP_PORT` | HTTP port to bind | `8050` |
| `MG_ALARMS_HTTP_SERVER_CERT` | Path to PEM-encoded HTTPS server certificate | "" |
| `MG_ALARMS_HTTP_SERVER_KEY` | Path to PEM-encoded HTTPS server key | "" |
| `MG_ALARMS_DB_HOST` | PostgreSQL host | `alarms-db` |
| `MG_ALARMS_DB_PORT` | PostgreSQL port | `5432` |
| `MG_ALARMS_DB_USER` | PostgreSQL user | `magistrala` |
| `MG_ALARMS_DB_PASS` | PostgreSQL password | `magistrala` |
| `MG_ALARMS_DB_NAME` | PostgreSQL database name | `alarms` |
| `MG_ALARMS_DB_SSL_MODE` | PostgreSQL SSL mode | `disable` |
| `MG_ALARMS_DB_SSL_CERT` | PostgreSQL SSL client cert | "" |
| `MG_ALARMS_DB_SSL_KEY` | PostgreSQL SSL client key | "" |
| `MG_ALARMS_DB_SSL_ROOT_CERT` | PostgreSQL SSL root cert | "" |
| `MG_ALARMS_INSTANCE_ID` | Instance ID for tracing/health | "" |
| `SMQ_MESSAGE_BROKER_URL` | Message broker URL for alarm ingestion | `nats://nats:4222` |
| `SMQ_JAEGER_URL` | Jaeger collector endpoint | `http://jaeger:4318/v1/traces` |
| `SMQ_JAEGER_TRACE_RATIO` | Trace sampling ratio | `1.0` |
| `SMQ_AUTH_GRPC_URL` | Auth gRPC endpoint | `auth:7001` |
| `SMQ_AUTH_GRPC_TIMEOUT` | Auth gRPC timeout | `300s` |
| `SMQ_AUTH_GRPC_CLIENT_CERT` | Auth gRPC client cert path | `${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt}` |
| `SMQ_AUTH_GRPC_CLIENT_KEY` | Auth gRPC client key path | `${GRPC_MTLS:+./ssl/certs/auth-grpc-client.key}` |
| `SMQ_AUTH_GRPC_SERVER_CA_CERTS` | Auth gRPC server CA path | `${GRPC_MTLS:+./ssl/certs/ca.crt}` |
| `SMQ_DOMAINS_GRPC_URL` | Domains gRPC endpoint | `domains:7003` |
| `SMQ_DOMAINS_GRPC_TIMEOUT` | Domains gRPC timeout | `300s` |
| `SMQ_DOMAINS_GRPC_CLIENT_CERT` | Domains gRPC client cert path | `${GRPC_MTLS:+./ssl/certs/domains-grpc-client.crt}` |
| `SMQ_DOMAINS_GRPC_CLIENT_KEY` | Domains gRPC client key path | `${GRPC_MTLS:+./ssl/certs/domains-grpc-client.key}` |
| `SMQ_DOMAINS_GRPC_SERVER_CA_CERTS` | Domains gRPC server CA path | `${GRPC_MTLS:+./ssl/certs/ca.crt}` |
| `SMQ_ALLOW_UNVERIFIED_USER` | Allow unverified users to access | `true` |
## Features
- **Alarm ingestion**: Consumes alarms from the message broker and persists them to PostgreSQL.
- **Stateful updates**: Updates assignee, acknowledgment, resolution, and metadata fields.
- **Filtering and paging**: Lists alarms by domain, rule, channel, client, subtopic, status, severity, and time range.
- **Observability**: `/metrics` Prometheus endpoint and Jaeger tracing support.
- **Auth and authorization**: Authn/authz enforced via gRPC auth and domains services.
## Architecture
### Runtime flow
1. The message broker publishes alarm events under the `alarms.>` subject.
2. The Alarms consumer decodes the event payload, enriches it with message metadata, validates it, and calls `CreateAlarm`.
3. The repository writes to PostgreSQL while deduplicating repeated active alarms with the same severity.
4. The HTTP API exposes list/view/update/delete operations with authn/authz, metrics, and tracing middleware.
### Components
- **HTTP API**: `alarms/api` exposes REST endpoints and health/metrics handlers.
- **Service layer**: `alarms/service.go` validates requests and coordinates repository operations.
- **Repository**: `alarms/postgres/alarms.go` implements persistence and filtering.
- **Consumer**: `alarms/consumer` processes broker messages and creates alarms.
- **Message broker**: `alarms/brokers` uses NATS JetStream with stream `alarms` and subject `alarms.>`.
- **Migrations**: `alarms/postgres/init.go` defines the alarms schema and indexes.
### Alarms table
Defined in `alarms/postgres/init.go`:
| Column | Type | Description |
| --- | --- | --- |
| `id` | `VARCHAR(36)` | Alarm UUID (primary key) |
| `rule_id` | `VARCHAR(36)` | Rule ID that triggered the alarm |
| `domain_id` | `VARCHAR(36)` | Domain ID |
| `channel_id` | `VARCHAR(36)` | Channel ID |
| `subtopic` | `TEXT` | Subtopic associated with the alarm |
| `client_id` | `VARCHAR(36)` | Client ID |
| `measurement` | `TEXT` | Measurement name |
| `value` | `TEXT` | Measured value |
| `unit` | `TEXT` | Measurement unit |
| `threshold` | `TEXT` | Threshold value |
| `cause` | `TEXT` | Cause/description |
| `status` | `SMALLINT` | 0 = active, 1 = cleared |
| `severity` | `SMALLINT` | Severity (0-100) |
| `assignee_id` | `VARCHAR(36)` | Assignee ID |
| `created_at` | `TIMESTAMPTZ` | Creation timestamp |
| `updated_at` | `TIMESTAMPTZ` | Last update timestamp |
| `updated_by` | `VARCHAR(36)` | User who updated |
| `assigned_at` | `TIMESTAMPTZ` | When assigned |
| `assigned_by` | `VARCHAR(36)` | Who assigned |
| `acknowledged_at` | `TIMESTAMPTZ` | When acknowledged |
| `acknowledged_by` | `VARCHAR(36)` | Who acknowledged |
| `resolved_at` | `TIMESTAMPTZ` | When resolved |
| `resolved_by` | `VARCHAR(36)` | Who resolved |
| `metadata` | `JSONB` | Custom metadata |
Index: `idx_alarms_state (domain_id, rule_id, channel_id, subtopic, client_id, measurement, created_at DESC)`
## Deployment
### Build and run locally
```bash
make alarms
MG_ALARMS_LOG_LEVEL=debug \
MG_ALARMS_HTTP_PORT=8050 \
MG_ALARMS_DB_HOST=localhost \
MG_ALARMS_DB_PORT=5432 \
MG_ALARMS_DB_USER=magistrala \
MG_ALARMS_DB_PASS=magistrala \
MG_ALARMS_DB_NAME=alarms \
SMQ_MESSAGE_BROKER_URL=nats://localhost:4222 \
SMQ_AUTH_GRPC_URL=localhost:7001 \
SMQ_AUTH_GRPC_TIMEOUT=300s \
SMQ_DOMAINS_GRPC_URL=localhost:7003 \
SMQ_DOMAINS_GRPC_TIMEOUT=300s \
./build/alarms
```
### Docker Compose
The service is available as a Docker container. Refer to [docker/docker-compose.yaml](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yaml) for the `alarms` and `alarms-db` services and their environment variables. For a full local stack, make sure the auth, domains, and message broker services are also running.
```bash
docker compose -f docker/docker-compose.yaml up alarms alarms-db
```
### Health check
```bash
curl -X GET http://localhost:8050/health \
-H "accept: application/health+json"
```
## Testing
```bash
go test ./alarms/...
```
## Usage
The Alarms service supports the following operations:
| Operation | Method & Path | Description |
| --- | --- | --- |
| `listAlarms` | `GET /{domainID}/alarms` | List alarms with filters |
| `viewAlarm` | `GET /{domainID}/alarms/{alarmID}` | Retrieve a single alarm |
| `updateAlarm` | `PUT /{domainID}/alarms/{alarmID}` | Update alarm status/assignee/metadata |
| `deleteAlarm` | `DELETE /{domainID}/alarms/{alarmID}` | Delete an alarm |
| `health` | `GET /health` | Service health check |
Alarm creation is driven by message broker events and is not exposed as an HTTP endpoint.
### Example: List alarms
```bash
curl -X GET "http://localhost:8050/<domainID>/alarms?limit=10&offset=0&status=active&severity=50" \
-H "Authorization: Bearer <your_access_token>"
```
### Example: View an alarm
```bash
curl -X GET http://localhost:8050/<domainID>/alarms/<alarmID> \
-H "Authorization: Bearer <your_access_token>"
```
### Example: Update an alarm
```bash
curl -X PUT http://localhost:8050/<domainID>/alarms/<alarmID> \
-H "Authorization: Bearer <your_access_token>" \
-H "Content-Type: application/json" \
-d '{
"status": "cleared",
"assignee_id": "<userID>",
"severity": 40,
"metadata": { "note": "cleared after inspection" }
}'
```
### Example: Delete an alarm
```bash
curl -X DELETE http://localhost:8050/<domainID>/alarms/<alarmID> \
-H "Authorization: Bearer <your_access_token>"
```
### Example: Health check
```bash
curl -X GET http://localhost:8050/health \
-H "accept: application/health+json"
```
+6 -2
View File
@@ -15,7 +15,7 @@ const SeverityMax uint8 = 100
var ErrInvalidSeverity = errors.New("invalid severity. Must be between 0 and 100")
type Metadata map[string]interface{}
type Metadata map[string]any
// Alarm represents an alarm instance.
type Alarm struct {
@@ -61,6 +61,8 @@ type PageMetadata struct {
ClientID string `json:"client_id" db:"client_id"`
Subtopic string `json:"subtopic" db:"subtopic"`
Measurement string `json:"measurement" db:"measurement"`
Dir string `json:"dir" db:"dir"`
Order string `json:"order" db:"order"`
Status Status `json:"status" db:"status"`
CreatedFrom time.Time `json:"created_from" db:"created_from"`
CreatedTo time.Time `json:"created_to" db:"created_to"`
@@ -70,6 +72,7 @@ type PageMetadata struct {
AssignedBy string `json:"assigned_by" db:"assigned_by"`
AcknowledgedBy string `json:"acknowledged_by" db:"acknowledged_by"`
ResolvedBy string `json:"resolved_by" db:"resolved_by"`
UserID string `json:"user_id" db:"user_id"`
}
func (a Alarm) Validate() error {
@@ -114,6 +117,7 @@ type Repository interface {
CreateAlarm(ctx context.Context, alarm Alarm) (Alarm, error)
UpdateAlarm(ctx context.Context, alarm Alarm) (Alarm, error)
ViewAlarm(ctx context.Context, alarmID, domainID string) (Alarm, error)
ListAlarms(ctx context.Context, pm PageMetadata) (AlarmsPage, error)
ListAllAlarms(ctx context.Context, pm PageMetadata) (AlarmsPage, error)
ListUserAlarms(ctx context.Context, userID string, pm PageMetadata) (AlarmsPage, error)
DeleteAlarm(ctx context.Context, id string) error
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/absmach/magistrala/alarms"
"github.com/absmach/magistrala/internal/testsutil"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/supermq/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
+9 -10
View File
@@ -7,7 +7,6 @@ import (
"context"
"github.com/absmach/magistrala/alarms"
api "github.com/absmach/supermq/api/http"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
@@ -16,13 +15,13 @@ import (
)
func updateAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(alarmReq)
return func(ctx context.Context, request any) (any, error) {
req := request.(updateAlarmReq)
if err := req.validate(); err != nil {
return alarmRes{}, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return alarmRes{}, svcerr.ErrAuthorization
}
@@ -39,13 +38,13 @@ func updateAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
}
func viewAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(alarmReq)
if err := req.validate(); err != nil {
return alarmRes{}, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return alarmRes{}, svcerr.ErrAuthorization
}
@@ -62,13 +61,13 @@ func viewAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
}
func listAlarmsEndpoint(svc alarms.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(listAlarmsReq)
if err := req.validate(); err != nil {
return alarmsPageRes{}, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return alarmsPageRes{}, svcerr.ErrAuthorization
}
@@ -85,13 +84,13 @@ func listAlarmsEndpoint(svc alarms.Service) endpoint.Endpoint {
}
func deleteAlarmEndpoint(svc alarms.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(alarmReq)
if err := req.validate(); err != nil {
return alarmRes{}, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return alarmRes{}, svcerr.ErrAuthorization
}
+23
View File
@@ -23,6 +23,21 @@ func (req alarmReq) validate() error {
return nil
}
type updateAlarmReq struct {
alarms.Alarm `json:",inline"`
}
func (req updateAlarmReq) validate() error {
if req.Alarm.ID == "" {
return errors.New("missing alarm id")
}
if req.Alarm.AssigneeID == "" && req.Alarm.AcknowledgedBy == "" && req.Alarm.ResolvedBy == "" && len(req.Alarm.Metadata) == 0 {
return errors.New("at least one of assignee_id, acknowledged_by, resolved_by, or metadata must be set")
}
return nil
}
type listAlarmsReq struct {
alarms.PageMetadata
}
@@ -32,5 +47,13 @@ func (req listAlarmsReq) validate() error {
return apiutil.ErrLimitSize
}
if req.Order != "" && req.Order != api.UpdatedAtOrder && req.Order != api.CreatedAtOrder {
return apiutil.ErrInvalidOrder
}
if req.Dir != api.AscDir && req.Dir != api.DescDir {
return apiutil.ErrInvalidDirection
}
return nil
}
+18 -8
View File
@@ -24,7 +24,7 @@ import (
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func MakeHandler(svc alarms.Service, logger *slog.Logger, idp supermq.IDProvider, instanceID string, authn smqauthn.Authentication) http.Handler {
func MakeHandler(svc alarms.Service, logger *slog.Logger, idp supermq.IDProvider, instanceID string, authn smqauthn.AuthNMiddleware) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
}
@@ -33,7 +33,7 @@ func MakeHandler(svc alarms.Service, logger *slog.Logger, idp supermq.IDProvider
mux.Route("/{domainID}/alarms", func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(api.AuthenticateMiddleware(authn, true))
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
r.Use(api.RequestIDMiddleware(idp))
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
@@ -71,7 +71,7 @@ func MakeHandler(svc alarms.Service, logger *slog.Logger, idp supermq.IDProvider
return mux
}
func decodeListAlarmsReq(_ context.Context, r *http.Request) (interface{}, error) {
func decodeListAlarmsReq(_ context.Context, r *http.Request) (any, error) {
offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
if err != nil {
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
@@ -140,6 +140,14 @@ func decodeListAlarmsReq(_ context.Context, r *http.Request) (interface{}, error
if err != nil {
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
}
order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder)
if err != nil {
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
}
dir, err := apiutil.ReadStringQuery(r, api.DirKey, "desc")
if err != nil {
return listAlarmsReq{}, errors.Wrap(apiutil.ErrValidation, err)
}
var createdFrom, createdTo time.Time
if cfrom != "" {
@@ -171,11 +179,13 @@ func decodeListAlarmsReq(_ context.Context, r *http.Request) (interface{}, error
AssignedBy: assignedBy,
CreatedFrom: createdFrom,
CreatedTo: createdTo,
Dir: dir,
Order: order,
},
}, nil
}
func decodeAlarmReq(_ context.Context, r *http.Request) (interface{}, error) {
func decodeAlarmReq(_ context.Context, r *http.Request) (any, error) {
return alarmReq{
Alarm: alarms.Alarm{
ID: chi.URLParam(r, "alarmID"),
@@ -183,14 +193,14 @@ func decodeAlarmReq(_ context.Context, r *http.Request) (interface{}, error) {
}, nil
}
func decodeUpdateAlarmReq(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateAlarmReq(_ context.Context, r *http.Request) (any, error) {
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
return alarmReq{}, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
return updateAlarmReq{}, apiutil.ErrUnsupportedContentType
}
req := alarmReq{}
req := updateAlarmReq{}
if err := json.NewDecoder(r.Body).Decode(&req.Alarm); err != nil {
return alarmReq{}, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err))
return updateAlarmReq{}, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
req.Alarm.ID = chi.URLParam(r, "alarmID")
+107 -59
View File
@@ -7,77 +7,89 @@ import (
"context"
"github.com/absmach/magistrala/alarms"
"github.com/absmach/magistrala/alarms/operations"
"github.com/absmach/supermq/auth"
"github.com/absmach/supermq/pkg/authn"
smqauthz "github.com/absmach/supermq/pkg/authz"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
)
var (
errDomainUpdateAlarms = errors.New("not authorized to update alarms in domain")
errDomainDeleteAlarms = errors.New("not authorized to delete alarms in domain")
errDomainViewAlarms = errors.New("not authorized to view alarms in domain")
)
type authorizationMiddleware struct {
svc alarms.Service
authz smqauthz.Authorization
svc alarms.Service
authz smqauthz.Authorization
entitiesOps permissions.EntitiesOperations[permissions.Operation]
}
var _ alarms.Service = (*authorizationMiddleware)(nil)
func NewAuthorizationMiddleware(svc alarms.Service, authz smqauthz.Authorization) alarms.Service {
return &authorizationMiddleware{
svc: svc,
authz: authz,
func NewAuthorizationMiddleware(svc alarms.Service, authz smqauthz.Authorization, entitiesOps permissions.EntitiesOperations[permissions.Operation]) (alarms.Service, error) {
if err := entitiesOps.Validate(); err != nil {
return nil, err
}
return &authorizationMiddleware{
svc: svc,
authz: authz,
entitiesOps: entitiesOps,
}, nil
}
func (am *authorizationMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) (err error) {
func (am *authorizationMiddleware) CreateAlarm(ctx context.Context, alarm alarms.Alarm) error {
return am.svc.CreateAlarm(ctx, alarm)
}
func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (dba alarms.Alarm, err error) {
// if assignee is present check if assignee is member of domain
req := smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Permission: policies.AdminPermission,
ObjectType: policies.DomainType,
Object: session.DomainID,
}
if err := am.authz.Authorize(ctx, req); err != nil {
return alarms.Alarm{}, err
func (am *authorizationMiddleware) UpdateAlarm(ctx context.Context, session authn.Session, alarm alarms.Alarm) (alarms.Alarm, error) {
if len(alarm.Metadata) > 0 {
if err := am.authorize(ctx, operations.OpUpdateAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
}
}
if alarm.AssigneeID != "" {
domainUserId := auth.EncodeDomainUserID(session.DomainID, alarm.AssigneeID)
if err := am.authorize(ctx, operations.OpAssignAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
}
domainUserID := auth.EncodeDomainUserID(session.DomainID, alarm.AssigneeID)
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: domainUserId,
Subject: domainUserID,
Permission: policies.MembershipPermission,
ObjectType: policies.DomainType,
Object: session.DomainID,
}); err != nil {
}, nil); err != nil {
return alarms.Alarm{}, err
}
}
if alarm.AcknowledgedBy != "" {
if err := am.authorize(ctx, operations.OpAcknowledgeAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
}
}
if alarm.ResolvedBy != "" {
if err := am.authorize(ctx, operations.OpResolveAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainUpdateAlarms, err)
}
}
return am.svc.UpdateAlarm(ctx, session, alarm)
}
func (am *authorizationMiddleware) DeleteAlarm(ctx context.Context, session authn.Session, id string) error {
req := smqauthz.PolicyReq{
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Permission: policies.AdminPermission,
ObjectType: policies.DomainType,
Object: session.DomainID,
}
if err := am.authz.Authorize(ctx, req); err != nil {
return err
if err := am.authorize(ctx, operations.OpDeleteAlarm, session, policies.DomainType, session.DomainID); err != nil {
return errors.Wrap(errDomainDeleteAlarms, err)
}
return am.svc.DeleteAlarm(ctx, session, id)
@@ -88,17 +100,11 @@ func (am *authorizationMiddleware) ListAlarms(ctx context.Context, session authn
pm.DomainID = session.DomainID
}
req := smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Permission: policies.MembershipPermission,
ObjectType: policies.DomainType,
Object: session.DomainID,
}
if err := am.authz.Authorize(ctx, req); err != nil {
switch err := am.checkSuperAdmin(ctx, session); {
case err == nil:
session.SuperAdmin = true
case errors.Contains(err, svcerr.ErrSuperAdminAction):
default:
return alarms.AlarmsPage{}, err
}
@@ -106,19 +112,61 @@ func (am *authorizationMiddleware) ListAlarms(ctx context.Context, session authn
}
func (am *authorizationMiddleware) ViewAlarm(ctx context.Context, session authn.Session, id string) (alarms.Alarm, error) {
req := smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Permission: policies.MembershipPermission,
ObjectType: policies.DomainType,
Object: session.DomainID,
}
if err := am.authz.Authorize(ctx, req); err != nil {
return alarms.Alarm{}, err
if err := am.authorize(ctx, operations.OpViewAlarm, session, policies.DomainType, session.DomainID); err != nil {
return alarms.Alarm{}, errors.Wrap(errDomainViewAlarms, err)
}
return am.svc.ViewAlarm(ctx, session, id)
}
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj string) error {
perm, err := am.entitiesOps.GetPermission(operations.EntityType, op)
if err != nil {
return err
}
pr := smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Subject: session.DomainUserID,
Object: obj,
ObjectType: objType,
Permission: perm.String(),
}
var pat *smqauthz.PATReq
if session.PatID != "" {
opName := am.entitiesOps.OperationName(operations.EntityType, op)
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: session.DomainID,
EntityType: operations.EntityType,
Operation: opName,
Domain: session.DomainID,
}
}
if err := am.authz.Authorize(ctx, pr, pat); err != nil {
return err
}
return nil
}
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
if session.Role != authn.SuperAdminRole {
return svcerr.ErrSuperAdminAction
}
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
SubjectType: policies.UserType,
Subject: session.UserID,
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}, nil); err != nil {
return err
}
return nil
}
+87 -14
View File
@@ -1,10 +1,11 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
@@ -164,12 +165,12 @@ func (_c *Repository_DeleteAlarm_Call) RunAndReturn(run func(ctx context.Context
return _c
}
// ListAlarms provides a mock function for the type Repository
func (_mock *Repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
// ListAllAlarms provides a mock function for the type Repository
func (_mock *Repository) ListAllAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
ret := _mock.Called(ctx, pm)
if len(ret) == 0 {
panic("no return value specified for ListAlarms")
panic("no return value specified for ListAllAlarms")
}
var r0 alarms.AlarmsPage
@@ -190,19 +191,19 @@ func (_mock *Repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata)
return r0, r1
}
// Repository_ListAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAlarms'
type Repository_ListAlarms_Call struct {
// Repository_ListAllAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAllAlarms'
type Repository_ListAllAlarms_Call struct {
*mock.Call
}
// ListAlarms is a helper method to define mock.On call
// ListAllAlarms is a helper method to define mock.On call
// - ctx context.Context
// - pm alarms.PageMetadata
func (_e *Repository_Expecter) ListAlarms(ctx interface{}, pm interface{}) *Repository_ListAlarms_Call {
return &Repository_ListAlarms_Call{Call: _e.mock.On("ListAlarms", ctx, pm)}
func (_e *Repository_Expecter) ListAllAlarms(ctx interface{}, pm interface{}) *Repository_ListAllAlarms_Call {
return &Repository_ListAllAlarms_Call{Call: _e.mock.On("ListAllAlarms", ctx, pm)}
}
func (_c *Repository_ListAlarms_Call) Run(run func(ctx context.Context, pm alarms.PageMetadata)) *Repository_ListAlarms_Call {
func (_c *Repository_ListAllAlarms_Call) Run(run func(ctx context.Context, pm alarms.PageMetadata)) *Repository_ListAllAlarms_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
@@ -220,12 +221,84 @@ func (_c *Repository_ListAlarms_Call) Run(run func(ctx context.Context, pm alarm
return _c
}
func (_c *Repository_ListAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListAlarms_Call {
func (_c *Repository_ListAllAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListAllAlarms_Call {
_c.Call.Return(alarmsPage, err)
return _c
}
func (_c *Repository_ListAlarms_Call) RunAndReturn(run func(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListAlarms_Call {
func (_c *Repository_ListAllAlarms_Call) RunAndReturn(run func(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListAllAlarms_Call {
_c.Call.Return(run)
return _c
}
// ListUserAlarms provides a mock function for the type Repository
func (_mock *Repository) ListUserAlarms(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
ret := _mock.Called(ctx, userID, pm)
if len(ret) == 0 {
panic("no return value specified for ListUserAlarms")
}
var r0 alarms.AlarmsPage
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string, alarms.PageMetadata) (alarms.AlarmsPage, error)); ok {
return returnFunc(ctx, userID, pm)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string, alarms.PageMetadata) alarms.AlarmsPage); ok {
r0 = returnFunc(ctx, userID, pm)
} else {
r0 = ret.Get(0).(alarms.AlarmsPage)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string, alarms.PageMetadata) error); ok {
r1 = returnFunc(ctx, userID, pm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repository_ListUserAlarms_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListUserAlarms'
type Repository_ListUserAlarms_Call struct {
*mock.Call
}
// ListUserAlarms is a helper method to define mock.On call
// - ctx context.Context
// - userID string
// - pm alarms.PageMetadata
func (_e *Repository_Expecter) ListUserAlarms(ctx interface{}, userID interface{}, pm interface{}) *Repository_ListUserAlarms_Call {
return &Repository_ListUserAlarms_Call{Call: _e.mock.On("ListUserAlarms", ctx, userID, pm)}
}
func (_c *Repository_ListUserAlarms_Call) Run(run func(ctx context.Context, userID string, pm alarms.PageMetadata)) *Repository_ListUserAlarms_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(context.Context)
}
var arg1 string
if args[1] != nil {
arg1 = args[1].(string)
}
var arg2 alarms.PageMetadata
if args[2] != nil {
arg2 = args[2].(alarms.PageMetadata)
}
run(
arg0,
arg1,
arg2,
)
})
return _c
}
func (_c *Repository_ListUserAlarms_Call) Return(alarmsPage alarms.AlarmsPage, err error) *Repository_ListUserAlarms_Call {
_c.Call.Return(alarmsPage, err)
return _c
}
func (_c *Repository_ListUserAlarms_Call) RunAndReturn(run func(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error)) *Repository_ListUserAlarms_Call {
_c.Call.Return(run)
return _c
}
+4 -3
View File
@@ -1,10 +1,11 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package operations
import "github.com/absmach/supermq/pkg/permissions"
const EntityType = "alarm"
// Alarm Operations.
const (
OpViewAlarm permissions.Operation = iota
OpDeleteAlarm
OpListAlarms
OpAssignAlarm
OpAcknowledgeAlarm
OpResolveAlarm
OpUpdateAlarm
)
func OperationDetails() map[permissions.Operation]permissions.OperationDetails {
return map[permissions.Operation]permissions.OperationDetails{
OpViewAlarm: {
Name: "view",
PermissionRequired: true,
},
OpDeleteAlarm: {
Name: "delete",
PermissionRequired: true,
},
OpListAlarms: {
Name: "list",
PermissionRequired: true,
},
OpAssignAlarm: {
Name: "assign",
PermissionRequired: true,
},
OpAcknowledgeAlarm: {
Name: "acknowledge",
PermissionRequired: true,
},
OpResolveAlarm: {
Name: "resolve",
PermissionRequired: true,
},
OpUpdateAlarm: {
Name: "update",
PermissionRequired: true,
},
}
}
+65 -26
View File
@@ -13,12 +13,17 @@ import (
"time"
"github.com/absmach/magistrala/alarms"
api "github.com/absmach/supermq/api/http"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
"github.com/absmach/supermq/pkg/postgres"
"github.com/jmoiron/sqlx"
)
const alarmColumns = `alarms.id, alarms.rule_id, alarms.domain_id, alarms.channel_id, alarms.client_id, alarms.subtopic, alarms.measurement, alarms.value, alarms.unit,
alarms.threshold, alarms.cause, alarms.status, alarms.severity, alarms.assignee_id, alarms.created_at, alarms.updated_at, alarms.updated_by, alarms.assigned_at,
alarms.assigned_by, alarms.acknowledged_at, alarms.acknowledged_by, alarms.resolved_at, alarms.resolved_by, alarms.metadata`
type repository struct {
db *sqlx.DB
}
@@ -129,7 +134,9 @@ func (r *repository) UpdateAlarm(ctx context.Context, alarm alarms.Alarm) (alarm
}
q := fmt.Sprintf(`UPDATE alarms SET %s updated_by = :updated_by, updated_at = :updated_at WHERE id = :id
RETURNING id, rule_id, measurement, value, unit, cause, status, domain_id, assignee_id, metadata, created_at, updated_by, updated_at, resolved_by, resolved_at;`, upq)
RETURNING id, rule_id, domain_id, channel_id, client_id, subtopic, measurement, value, unit, threshold,
cause, status, severity, assignee_id, assigned_at, assigned_by, acknowledged_at, acknowledged_by,
resolved_by, resolved_at, metadata, created_at, updated_by, updated_at;`, upq)
dba, err := toDBAlarm(alarm)
if err != nil {
@@ -155,7 +162,7 @@ func (r *repository) UpdateAlarm(ctx context.Context, alarm alarms.Alarm) (alarm
func (r *repository) ViewAlarm(ctx context.Context, alarmID, domainID string) (alarms.Alarm, error) {
query := `SELECT * FROM alarms WHERE id = :id AND domain_id = :domain_id;`
row, err := r.db.NamedQueryContext(ctx, query, map[string]interface{}{
row, err := r.db.NamedQueryContext(ctx, query, map[string]any{
"id": alarmID, "domain_id": domainID,
})
if err != nil {
@@ -180,16 +187,49 @@ func (r *repository) ViewAlarm(ctx context.Context, alarmID, domainID string) (a
return alarm, nil
}
func (r *repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
func (r *repository) ListAllAlarms(ctx context.Context, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
query, err := pageQuery(pm)
if err != nil {
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
q := fmt.Sprintf(`SELECT id, rule_id, domain_id, channel_id, client_id, subtopic, measurement, value, unit,
threshold, cause, status, severity, assignee_id, created_at, updated_at, updated_by, assigned_at,
assigned_by, acknowledged_at, acknowledged_by, resolved_at, resolved_by, metadata
FROM alarms %s ORDER BY created_at DESC LIMIT :limit OFFSET :offset;`, query)
comQuery := fmt.Sprintf(`SELECT %s FROM alarms %s`, alarmColumns, query)
return r.alarmsPage(ctx, comQuery, pm)
}
func (r *repository) ListUserAlarms(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
query, err := pageQuery(pm)
if err != nil {
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
pm.UserID = userID
comQuery := fmt.Sprintf(`SELECT DISTINCT %s
FROM alarms
INNER JOIN rules_roles rr ON rr.entity_id = alarms.rule_id
INNER JOIN rules_role_members rrm ON rrm.role_id = rr.id AND rrm.member_id = :user_id
%s`, alarmColumns, query)
return r.alarmsPage(ctx, comQuery, pm)
}
func (r *repository) alarmsPage(ctx context.Context, comQuery string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
dir := api.DescDir
if pm.Dir == api.AscDir {
dir = api.AscDir
}
var orderClause string
switch pm.Order {
case api.CreatedAtOrder:
orderClause = fmt.Sprintf("ORDER BY created_at %s, id %s", dir, dir)
default:
orderClause = fmt.Sprintf("ORDER BY COALESCE(updated_at, created_at) %s, id %s", dir, dir)
}
q := fmt.Sprintf(`SELECT * FROM (%s) AS sub_query %s LIMIT :limit OFFSET :offset;`, comQuery, orderClause)
cq := fmt.Sprintf(`SELECT COUNT(*) AS total_count FROM (%s) AS sub_query;`, comQuery)
rows, err := r.db.NamedQueryContext(ctx, q, pm)
if err != nil {
@@ -212,8 +252,7 @@ func (r *repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (al
items = append(items, a)
}
q = fmt.Sprintf(`SELECT COUNT(*) FROM alarms %s;`, query)
total, err := postgres.Total(ctx, r.db, q, pm)
total, err := postgres.Total(ctx, r.db, cq, pm)
if err != nil {
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
@@ -228,7 +267,7 @@ func (r *repository) ListAlarms(ctx context.Context, pm alarms.PageMetadata) (al
func (r *repository) DeleteAlarm(ctx context.Context, id string) error {
query := `DELETE FROM alarms WHERE id = :id;`
result, err := r.db.NamedExecContext(ctx, query, map[string]interface{}{"id": id})
result, err := r.db.NamedExecContext(ctx, query, map[string]any{"id": id})
if err != nil {
return errors.Wrap(repoerr.ErrRemoveEntity, err)
}
@@ -386,7 +425,7 @@ func toAlarm(dbr dbAlarm) (alarms.Alarm, error) {
resolvedAt = dbr.ResolvedAt.Time
}
var metadata map[string]interface{}
var metadata map[string]any
if len(dbr.Metadata) > 0 {
err := json.Unmarshal(dbr.Metadata, &metadata)
if err != nil {
@@ -425,49 +464,49 @@ func toAlarm(dbr dbAlarm) (alarms.Alarm, error) {
func pageQuery(pm alarms.PageMetadata) (string, error) {
var query []string
if pm.DomainID != "" {
query = append(query, "domain_id = :domain_id")
query = append(query, "alarms.domain_id = :domain_id")
}
if pm.RuleID != "" {
query = append(query, "rule_id = :rule_id")
query = append(query, "alarms.rule_id = :rule_id")
}
if pm.ChannelID != "" {
query = append(query, "channel_id = :channel_id")
query = append(query, "alarms.channel_id = :channel_id")
}
if pm.Subtopic != "" {
query = append(query, "subtopic = :subtopic")
query = append(query, "alarms.subtopic = :subtopic")
}
if pm.ClientID != "" {
query = append(query, "client_id = :client_id")
query = append(query, "alarms.client_id = :client_id")
}
if pm.Measurement != "" {
query = append(query, "measurement = :measurement")
query = append(query, "alarms.measurement = :measurement")
}
if pm.Status != alarms.AllStatus {
query = append(query, "status = :status")
query = append(query, "alarms.status = :status")
}
if pm.Severity != math.MaxUint8 {
query = append(query, "severity = :severity")
query = append(query, "alarms.severity = :severity")
}
if pm.AssigneeID != "" {
query = append(query, "assignee_id = :assignee_id")
query = append(query, "alarms.assignee_id = :assignee_id")
}
if pm.UpdatedBy != "" {
query = append(query, "updated_by = :updated_by")
query = append(query, "alarms.updated_by = :updated_by")
}
if pm.ResolvedBy != "" {
query = append(query, "resolved_by = :resolved_by")
query = append(query, "alarms.resolved_by = :resolved_by")
}
if pm.AcknowledgedBy != "" {
query = append(query, "acknowledged_by = :acknowledged_by")
query = append(query, "alarms.acknowledged_by = :acknowledged_by")
}
if pm.AssignedBy != "" {
query = append(query, "assigned_by = :assigned_by")
query = append(query, "alarms.assigned_by = :assigned_by")
}
if !pm.CreatedFrom.IsZero() {
query = append(query, "created_at >= :created_from")
query = append(query, "alarms.created_at >= :created_from")
}
if !pm.CreatedTo.IsZero() {
query = append(query, "created_at <= :created_to")
query = append(query, "alarms.created_at <= :created_to")
}
var emq string
+281 -96
View File
@@ -34,11 +34,11 @@ func TestCreateAlarm(t *testing.T) {
repo := postgres.NewAlarmsRepo(db)
alarm := alarms.Alarm{
ID: generateUUID(&testing.T{}),
RuleID: generateUUID(&testing.T{}),
DomainID: generateUUID(&testing.T{}),
ChannelID: generateUUID(&testing.T{}),
ClientID: generateUUID(&testing.T{}),
ID: generateUUID(t),
RuleID: generateUUID(t),
DomainID: generateUUID(t),
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Subtopic: namegen.Generate(),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
@@ -46,9 +46,9 @@ func TestCreateAlarm(t *testing.T) {
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(&testing.T{}),
CreatedAt: time.Now().Local(),
Metadata: map[string]interface{}{
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC(),
Metadata: map[string]any{
"key": "value",
},
}
@@ -71,10 +71,10 @@ func TestCreateAlarm(t *testing.T) {
{
desc: "missing rule id",
alarm: alarms.Alarm{
ID: generateUUID(&testing.T{}),
DomainID: generateUUID(&testing.T{}),
ChannelID: generateUUID(&testing.T{}),
ClientID: generateUUID(&testing.T{}),
ID: generateUUID(t),
DomainID: generateUUID(t),
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Subtopic: namegen.Generate(),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
@@ -82,10 +82,10 @@ func TestCreateAlarm(t *testing.T) {
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(&testing.T{}),
CreatedAt: time.Now().Local(),
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC(),
Metadata: map[string]interface{}{
Metadata: map[string]any{
"key": "value",
},
},
@@ -94,10 +94,10 @@ func TestCreateAlarm(t *testing.T) {
{
desc: "invalid alarm",
alarm: alarms.Alarm{
ID: generateUUID(&testing.T{}),
DomainID: generateUUID(&testing.T{}),
ChannelID: generateUUID(&testing.T{}),
ClientID: generateUUID(&testing.T{}),
ID: generateUUID(t),
DomainID: generateUUID(t),
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Subtopic: namegen.Generate(),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
@@ -105,10 +105,10 @@ func TestCreateAlarm(t *testing.T) {
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(&testing.T{}),
CreatedAt: time.Now().Local(),
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC(),
Metadata: map[string]interface{}{
Metadata: map[string]any{
"key": make(chan int),
},
},
@@ -129,17 +129,17 @@ func TestCreateAlarm(t *testing.T) {
return
}
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
require.NotEmpty(t, alarm.ID)
require.Equal(t, tc.alarm.RuleID, alarm.RuleID)
require.Equal(t, tc.alarm.Measurement, alarm.Measurement)
require.Equal(t, tc.alarm.Value, alarm.Value)
require.Equal(t, tc.alarm.Unit, alarm.Unit)
require.Equal(t, tc.alarm.Cause, alarm.Cause)
require.Equal(t, tc.alarm.Status, alarm.Status)
require.Equal(t, tc.alarm.DomainID, alarm.DomainID)
require.Equal(t, tc.alarm.AssigneeID, alarm.AssigneeID)
require.Equal(t, tc.alarm.Metadata, alarm.Metadata)
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
assert.NotEmpty(t, alarm.ID)
assert.Equal(t, tc.alarm.RuleID, alarm.RuleID)
assert.Equal(t, tc.alarm.Measurement, alarm.Measurement)
assert.Equal(t, tc.alarm.Value, alarm.Value)
assert.Equal(t, tc.alarm.Unit, alarm.Unit)
assert.Equal(t, tc.alarm.Cause, alarm.Cause)
assert.Equal(t, tc.alarm.Status, alarm.Status)
assert.Equal(t, tc.alarm.DomainID, alarm.DomainID)
assert.Equal(t, tc.alarm.AssigneeID, alarm.AssigneeID)
assert.Equal(t, tc.alarm.Metadata, alarm.Metadata)
})
}
}
@@ -153,20 +153,20 @@ func TestUpdateAlarm(t *testing.T) {
repo := postgres.NewAlarmsRepo(db)
alarm := alarms.Alarm{
ID: generateUUID(&testing.T{}),
RuleID: generateUUID(&testing.T{}),
DomainID: generateUUID(&testing.T{}),
ChannelID: generateUUID(&testing.T{}),
ClientID: generateUUID(&testing.T{}),
ID: generateUUID(t),
RuleID: generateUUID(t),
DomainID: generateUUID(t),
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
Unit: namegen.Generate(),
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(&testing.T{}),
CreatedAt: time.Now().Local(),
Metadata: map[string]interface{}{
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC(),
Metadata: map[string]any{
"key": "value",
},
}
@@ -181,16 +181,20 @@ func TestUpdateAlarm(t *testing.T) {
{
desc: "valid alarm",
alarm: alarms.Alarm{
ID: alarm.ID,
Status: alarms.ActiveStatus,
DomainID: alarm.DomainID,
AssigneeID: generateUUID(&testing.T{}),
CreatedAt: alarm.CreatedAt,
UpdatedAt: time.Now().Local(),
UpdatedBy: generateUUID(&testing.T{}),
ResolvedAt: time.Now().Local(),
ResolvedBy: generateUUID(&testing.T{}),
Metadata: map[string]interface{}{
ID: alarm.ID,
Status: alarms.ClearedStatus,
DomainID: alarm.DomainID,
AssigneeID: generateUUID(t),
AssignedBy: generateUUID(t),
AssignedAt: time.Now().UTC(),
AcknowledgedBy: generateUUID(t),
AcknowledgedAt: time.Now().UTC(),
CreatedAt: alarm.CreatedAt,
UpdatedAt: time.Now().UTC(),
UpdatedBy: generateUUID(t),
ResolvedAt: time.Now().UTC(),
ResolvedBy: generateUUID(t),
Metadata: map[string]any{
"key": "value",
},
},
@@ -199,7 +203,7 @@ func TestUpdateAlarm(t *testing.T) {
{
desc: "non existing alarm",
alarm: alarms.Alarm{
ID: generateUUID(&testing.T{}),
ID: generateUUID(t),
},
err: repoerr.ErrNotFound,
},
@@ -207,12 +211,12 @@ func TestUpdateAlarm(t *testing.T) {
desc: "invalid alarm",
alarm: alarms.Alarm{
ID: alarm.ID,
RuleID: generateUUID(&testing.T{}),
RuleID: generateUUID(t),
Status: 0,
DomainID: generateUUID(&testing.T{}),
DomainID: generateUUID(t),
AssigneeID: strings.Repeat("a", 40),
CreatedAt: time.Now().Local(),
Metadata: map[string]interface{}{
CreatedAt: time.Now().UTC(),
Metadata: map[string]any{
"key": "value",
},
},
@@ -233,12 +237,15 @@ func TestUpdateAlarm(t *testing.T) {
return
}
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
require.NotEmpty(t, alarm.ID)
require.Equal(t, tc.alarm.Status, alarm.Status)
require.Equal(t, tc.alarm.DomainID, alarm.DomainID)
require.Equal(t, tc.alarm.AssigneeID, alarm.AssigneeID)
require.Equal(t, tc.alarm.Metadata, alarm.Metadata)
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
assert.NotEmpty(t, alarm.ID)
assert.Equal(t, tc.alarm.Status, alarm.Status)
assert.Equal(t, tc.alarm.DomainID, alarm.DomainID)
assert.Equal(t, tc.alarm.AssigneeID, alarm.AssigneeID)
assert.Equal(t, tc.alarm.UpdatedBy, alarm.UpdatedBy)
assert.Equal(t, tc.alarm.ResolvedBy, alarm.ResolvedBy)
assert.Equal(t, tc.alarm.AcknowledgedBy, alarm.AcknowledgedBy)
assert.Equal(t, tc.alarm.Metadata, alarm.Metadata)
})
}
}
@@ -252,20 +259,20 @@ func TestViewAlarm(t *testing.T) {
repo := postgres.NewAlarmsRepo(db)
alarm := alarms.Alarm{
ID: generateUUID(&testing.T{}),
RuleID: generateUUID(&testing.T{}),
DomainID: generateUUID(&testing.T{}),
ChannelID: generateUUID(&testing.T{}),
ClientID: generateUUID(&testing.T{}),
ID: generateUUID(t),
RuleID: generateUUID(t),
DomainID: generateUUID(t),
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
Unit: namegen.Generate(),
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(&testing.T{}),
CreatedAt: time.Now().Local(),
Metadata: map[string]interface{}{
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC(),
Metadata: map[string]any{
"key": "value",
},
}
@@ -286,14 +293,14 @@ func TestViewAlarm(t *testing.T) {
},
{
desc: "non existing alarm id",
id: generateUUID(&testing.T{}),
id: generateUUID(t),
domainID: alarm.DomainID,
err: repoerr.ErrNotFound,
},
{
desc: "non existing domain id",
id: alarm.ID,
domainID: generateUUID(&testing.T{}),
domainID: generateUUID(t),
err: repoerr.ErrNotFound,
},
}
@@ -306,9 +313,9 @@ func TestViewAlarm(t *testing.T) {
return
}
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
require.NotEmpty(t, alarm.ID)
require.Equal(t, tc.id, alarm.ID)
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
assert.NotEmpty(t, alarm.ID)
assert.Equal(t, tc.id, alarm.ID)
})
}
}
@@ -322,20 +329,20 @@ func TestListAlarms(t *testing.T) {
items := make([]alarms.Alarm, 1000)
for i := range 1000 {
items[i] = alarms.Alarm{
ID: generateUUID(&testing.T{}),
RuleID: generateUUID(&testing.T{}),
DomainID: generateUUID(&testing.T{}),
ChannelID: generateUUID(&testing.T{}),
ClientID: generateUUID(&testing.T{}),
ID: generateUUID(t),
RuleID: generateUUID(t),
DomainID: generateUUID(t),
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
Unit: namegen.Generate(),
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(&testing.T{}),
CreatedAt: time.Now().Local(),
Metadata: map[string]interface{}{
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC(),
Metadata: map[string]any{
"key": "value",
},
}
@@ -388,7 +395,7 @@ func TestListAlarms(t *testing.T) {
pm: alarms.PageMetadata{
Offset: 0,
Limit: 10,
AssigneeID: generateUUID(&testing.T{}),
AssigneeID: generateUUID(t),
},
response: []alarms.Alarm{},
err: nil,
@@ -396,14 +403,192 @@ func TestListAlarms(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
alarms, err := repo.ListAlarms(context.Background(), tc.pm)
alarms, err := repo.ListAllAlarms(context.Background(), tc.pm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
return
}
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
assert.Equal(t, len(tc.response), len(alarms.Alarms))
})
}
}
func TestListUserAlarms(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM alarms")
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
_, err = db.Exec("DELETE FROM rules")
require.Nil(t, err, fmt.Sprintf("clean rules unexpected error: %s", err))
})
repo := postgres.NewAlarmsRepo(db)
domainID := generateUUID(t)
userID := generateUUID(t)
otherUserID := generateUUID(t)
adminUserID := generateUUID(t)
// Create 10 rules and 10 alarms referencing them.
// Assign userID to the first 6 rules via role membership.
var ruleIDs []string
var createdAlarms []alarms.Alarm
for i := range 10 {
ruleID := generateUUID(t)
_, err := db.Exec(`INSERT INTO rules (id, name, domain_id, status, logic_type, logic_value) VALUES ($1, $2, $3, 0, 0, '')`,
ruleID, fmt.Sprintf("rule-%d", i), domainID)
require.Nil(t, err, fmt.Sprintf("insert rule unexpected error: %s", err))
ruleIDs = append(ruleIDs, ruleID)
alarm := alarms.Alarm{
ID: generateUUID(t),
RuleID: ruleID,
DomainID: domainID,
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
Unit: namegen.Generate(),
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute),
}
alarm, err = repo.CreateAlarm(context.Background(), alarm)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
createdAlarms = append(createdAlarms, alarm)
}
// Assign userID to the first 6 rules via rules_roles + rules_role_members.
userRoleIDs := make([]string, 6)
for i := range 6 {
roleID := generateUUID(t)
userRoleIDs[i] = roleID
_, err := db.Exec(`INSERT INTO rules_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", ruleIDs[i])
require.Nil(t, err, fmt.Sprintf("insert rules_roles unexpected error: %s", err))
_, err = db.Exec(`INSERT INTO rules_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, userID, ruleIDs[i])
require.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
}
for i := range 10 {
var roleID string
if i < 6 {
roleID = userRoleIDs[i]
} else {
roleID = generateUUID(t)
_, err := db.Exec(`INSERT INTO rules_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", ruleIDs[i])
require.Nil(t, err, fmt.Sprintf("insert rules_roles unexpected error: %s", err))
}
_, err := db.Exec(`INSERT INTO rules_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, adminUserID, ruleIDs[i])
require.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
}
_ = createdAlarms
cases := []struct {
desc string
userID string
pm alarms.PageMetadata
count int
err error
}{
{
desc: "list user alarms returns only accessible alarms",
userID: userID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 100,
},
count: 6,
err: nil,
},
{
desc: "list user alarms with limit",
userID: userID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 3,
},
count: 3,
err: nil,
},
{
desc: "list user alarms with offset",
userID: userID,
pm: alarms.PageMetadata{
Offset: 4,
Limit: 100,
},
count: 2,
err: nil,
},
{
desc: "list user alarms with domain filter",
userID: userID,
pm: alarms.PageMetadata{
DomainID: domainID,
Offset: 0,
Limit: 100,
},
count: 6,
err: nil,
},
{
desc: "list user alarms with non-existing domain returns 0",
userID: userID,
pm: alarms.PageMetadata{
DomainID: generateUUID(t),
Offset: 0,
Limit: 100,
},
count: 0,
err: nil,
},
{
desc: "list alarms for user with no role assignments returns 0",
userID: otherUserID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 100,
},
count: 0,
err: nil,
},
{
desc: "list alarms for admin user with role on all rules returns all alarms",
userID: adminUserID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 100,
},
count: 10,
err: nil,
},
{
desc: "list user alarms ordered by created_at ascending",
userID: userID,
pm: alarms.PageMetadata{
Offset: 0,
Limit: 100,
Order: "created_at",
Dir: "asc",
},
count: 6,
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
page, err := repo.ListUserAlarms(context.Background(), tc.userID, tc.pm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
return
}
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
require.Equal(t, len(tc.response), len(alarms.Alarms))
assert.Equal(t, tc.count, len(page.Alarms), fmt.Sprintf("%s: expected %d alarms, got %d", tc.desc, tc.count, len(page.Alarms)))
})
}
}
@@ -417,20 +602,20 @@ func TestDeleteAlarm(t *testing.T) {
repo := postgres.NewAlarmsRepo(db)
alarm := alarms.Alarm{
ID: generateUUID(&testing.T{}),
RuleID: generateUUID(&testing.T{}),
DomainID: generateUUID(&testing.T{}),
ChannelID: generateUUID(&testing.T{}),
ClientID: generateUUID(&testing.T{}),
ID: generateUUID(t),
RuleID: generateUUID(t),
DomainID: generateUUID(t),
ChannelID: generateUUID(t),
ClientID: generateUUID(t),
Measurement: namegen.Generate(),
Value: namegen.Generate(),
Unit: namegen.Generate(),
Threshold: namegen.Generate(),
Cause: namegen.Generate(),
Status: 0,
AssigneeID: generateUUID(&testing.T{}),
CreatedAt: time.Now().Local(),
Metadata: map[string]interface{}{
AssigneeID: generateUUID(t),
CreatedAt: time.Now().UTC(),
Metadata: map[string]any{
"key": "value",
},
}
@@ -449,7 +634,7 @@ func TestDeleteAlarm(t *testing.T) {
},
{
desc: "non existing alarm",
id: generateUUID(&testing.T{}),
id: generateUUID(t),
err: repoerr.ErrNotFound,
},
}
@@ -462,7 +647,7 @@ func TestDeleteAlarm(t *testing.T) {
return
}
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
})
}
}
+15 -3
View File
@@ -4,13 +4,16 @@
package postgres
import (
rpostgres "github.com/absmach/magistrala/re/postgres"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
migrate "github.com/rubenv/sql-migrate"
)
// Migration of Users service.
func Migration() *migrate.MemoryMigrationSource {
return &migrate.MemoryMigrationSource{
// Migration of Alarms service.
func Migration() (*migrate.MemoryMigrationSource, error) {
alarmsMigration := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "alarms_01",
@@ -50,4 +53,13 @@ func Migration() *migrate.MemoryMigrationSource {
},
},
}
rulesMigration, err := rpostgres.Migration()
if err != nil {
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
}
alarmsMigration.Migrations = append(alarmsMigration.Migrations, rulesMigration.Migrations...)
return alarmsMigration, nil
}
+5 -1
View File
@@ -75,7 +75,11 @@ func TestMain(m *testing.M) {
SSLRootCert: "",
}
if db, err = postgres.Setup(dbConfig, *apostgres.Migration()); err != nil {
migration, err := apostgres.Migration()
if err != nil {
log.Fatalf("Could not get migration: %s", err)
}
if db, err = postgres.Setup(dbConfig, *migration); err != nil {
log.Fatalf("Could not setup test DB connection: %s", err)
}
+5 -1
View File
@@ -43,6 +43,7 @@ func (s *service) CreateAlarm(ctx context.Context, alarm Alarm) error {
if _, err = s.repo.CreateAlarm(ctx, alarm); err != nil && err != repoerr.ErrNotFound {
return err
}
return nil
}
@@ -51,7 +52,10 @@ func (s *service) ViewAlarm(ctx context.Context, session authn.Session, alarmID
}
func (s *service) ListAlarms(ctx context.Context, session authn.Session, pm PageMetadata) (AlarmsPage, error) {
return s.repo.ListAlarms(ctx, pm)
if session.SuperAdmin {
return s.repo.ListAllAlarms(ctx, pm)
}
return s.repo.ListUserAlarms(ctx, session.UserID, pm)
}
func (s *service) DeleteAlarm(ctx context.Context, session authn.Session, alarmID string) error {
+12 -26
View File
@@ -6,14 +6,13 @@ package alarms_test
import (
"context"
"fmt"
"math"
"testing"
"time"
"github.com/absmach/magistrala/alarms"
"github.com/absmach/magistrala/alarms/mocks"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
repoerr "github.com/absmach/supermq/pkg/errors/repository"
"github.com/absmach/supermq/pkg/uuid"
"github.com/stretchr/testify/assert"
@@ -22,9 +21,13 @@ import (
var idp = uuid.New()
func newService(t *testing.T, repo *mocks.Repository) alarms.Service {
return alarms.NewService(idp, repo)
}
func TestCreateAlarm(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
ts := time.Now()
cases := []struct {
desc string
@@ -69,33 +72,16 @@ func TestCreateAlarm(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("CreateAlarm", context.Background(), mock.Anything).Return(tc.alarm, tc.err)
repoCall1 := repo.On("ListAlarms", context.Background(), alarms.PageMetadata{
Offset: 0, Limit: 1,
DomainID: tc.alarm.DomainID,
ChannelID: tc.alarm.ChannelID,
ClientID: tc.alarm.ClientID,
Subtopic: tc.alarm.Subtopic,
Measurement: tc.alarm.Measurement,
RuleID: tc.alarm.RuleID,
Status: alarms.AllStatus,
Severity: math.MaxUint8,
CreatedTo: tc.alarm.CreatedAt,
}).Return(alarms.AlarmsPage{}, tc.err)
err := svc.CreateAlarm(context.Background(), tc.alarm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
return
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
repoCall1.Unset()
})
}
}
func TestViewAlarm(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
cases := []struct {
desc string
@@ -134,7 +120,7 @@ func TestViewAlarm(t *testing.T) {
func TestUpdateAlarm(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
cases := []struct {
desc string
@@ -192,7 +178,7 @@ func TestUpdateAlarm(t *testing.T) {
func TestListAlarms(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
cases := []struct {
desc string
@@ -219,7 +205,7 @@ func TestListAlarms(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
s := authn.Session{DomainID: tc.pm.DomainID}
repoCall := repo.On("ListAlarms", context.Background(), tc.pm).Return(tc.page, tc.err)
repoCall := repo.On("ListUserAlarms", context.Background(), s.UserID, tc.pm).Return(tc.page, tc.err)
_, err := svc.ListAlarms(context.Background(), s, tc.pm)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
@@ -233,7 +219,7 @@ func TestListAlarms(t *testing.T) {
func TestDeleteAlarm(t *testing.T) {
repo := new(mocks.Repository)
svc := alarms.NewService(idp, repo)
svc := newService(t, repo)
cases := []struct {
desc string
+22 -4
View File
@@ -3,8 +3,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.6
// protoc v6.30.2
// protoc-gen-go v1.36.10
// protoc v6.33.0
// source: readers/v1/readers.proto
package v1
@@ -102,6 +102,8 @@ type PageMetadata struct {
Aggregation Aggregation `protobuf:"varint,15,opt,name=aggregation,proto3,enum=readers.v1.Aggregation" json:"aggregation,omitempty"`
Comparator string `protobuf:"bytes,16,opt,name=comparator,proto3" json:"comparator,omitempty"`
Format string `protobuf:"bytes,17,opt,name=format,proto3" json:"format,omitempty"`
Order string `protobuf:"bytes,18,opt,name=order,proto3" json:"order,omitempty"`
Dir string `protobuf:"bytes,19,opt,name=dir,proto3" json:"dir,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -255,6 +257,20 @@ func (x *PageMetadata) GetFormat() string {
return ""
}
func (x *PageMetadata) GetOrder() string {
if x != nil {
return x.Order
}
return ""
}
func (x *PageMetadata) GetDir() string {
if x != nil {
return x.Dir
}
return ""
}
type ReadMessagesRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
Total uint64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"`
@@ -706,7 +722,7 @@ var File_readers_v1_readers_proto protoreflect.FileDescriptor
const file_readers_v1_readers_proto_rawDesc = "" +
"\n" +
"\x18readers/v1/readers.proto\x12\n" +
"readers.v1\"\xe4\x03\n" +
"readers.v1\"\x8c\x04\n" +
"\fPageMetadata\x12\x14\n" +
"\x05limit\x18\x01 \x01(\x04R\x05limit\x12\x16\n" +
"\x06offset\x18\x02 \x01(\x04R\x06offset\x12\x1a\n" +
@@ -729,7 +745,9 @@ const file_readers_v1_readers_proto_rawDesc = "" +
"\n" +
"comparator\x18\x10 \x01(\tR\n" +
"comparator\x12\x16\n" +
"\x06format\x18\x11 \x01(\tR\x06format\"\x97\x01\n" +
"\x06format\x18\x11 \x01(\tR\x06format\x12\x14\n" +
"\x05order\x18\x12 \x01(\tR\x05order\x12\x10\n" +
"\x03dir\x18\x13 \x01(\tR\x03dir\"\x97\x01\n" +
"\x0fReadMessagesRes\x12\x14\n" +
"\x05total\x18\x01 \x01(\x04R\x05total\x12=\n" +
"\rpage_metadata\x18\x02 \x01(\v2\x18.readers.v1.PageMetadataR\fpageMetadata\x12/\n" +
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v6.30.2
// - protoc v6.33.0
// source: readers/v1/readers.proto
package v1
+1 -1
View File
@@ -9,7 +9,7 @@ info:
contact:
name: Magistrala Team
url: 'https://github.com/absmach/magistrala'
email: info@abstractmachines.fr
email: info@absmach.eu
description: |
MQTT adapter provides an MQTT API for sending messages through the platform. MQTT adapter uses [mProxy](https://github.com/absmach/mproxy) for proxying traffic between client and MQTT broker.
Additionally, the MQTT adapter and the message broker are replicating the traffic between brokers.
+1 -1
View File
@@ -10,7 +10,7 @@ info:
contact:
name: Magistrala Team
url: 'https://github.com/absmach/magistrala'
email: info@abstractmachines.fr
email: info@absmach.eu
license:
name: Apache 2.0
url: 'https://github.com/absmach/magistrala/blob/main/LICENSE'
+1 -1
View File
@@ -2,4 +2,4 @@
This folder contains an OpenAPI specifications for Magistrala API.
View specification in Swagger UI at [docs.api.magistrala.abstractmachines.fr](https://docs.api.magistrala.abstractmachines.fr)
View specification in Swagger UI at [docs.api.magistrala.absmach.eu](https://docs.api.magistrala.absmach.eu)
+508
View File
@@ -0,0 +1,508 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
openapi: 3.0.1
info:
title: Magistrala Alarms API
description: |
HTTP API for managing alarms service.
Some useful links:
- [The Magistrala repository](https://github.com/absmach/magistrala)
contact:
email: info@absmach.eu
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.18.5
servers:
- url: http://localhost:8050
- url: https://localhost:8050
tags:
- name: alarms
description: Everything about your Alarms
externalDocs:
description: Find out more about alarms
url: https://docs.magistrala.absmach.eu
paths:
/{domainID}/alarms:
get:
operationId: listAlarms
summary: List Alarms
description: |
Retrieves a list of alarms with optional filtering
tags:
- alarms
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/Offset'
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Order'
- $ref: '#/components/parameters/Dir'
- $ref: '#/components/parameters/ChannelID'
- $ref: '#/components/parameters/ClientID'
- $ref: '#/components/parameters/Subtopic'
- $ref: '#/components/parameters/RuleID'
- $ref: '#/components/parameters/Status'
- $ref: '#/components/parameters/AssigneeID'
- $ref: '#/components/parameters/Severity'
- $ref: '#/components/parameters/UpdatedBy'
- $ref: '#/components/parameters/AssignedBy'
- $ref: '#/components/parameters/AcknowledgedBy'
- $ref: '#/components/parameters/ResolvedBy'
- $ref: '#/components/parameters/CreatedFrom'
- $ref: '#/components/parameters/CreatedTo'
security:
- bearerAuth: []
responses:
'200':
$ref: '#/components/responses/AlarmsPageRes'
'400':
description: Failed due to malformed query parameters
'401':
description: Missing or invalid access token
'422':
description: Database can't process request
'500':
$ref: '#/components/responses/ServiceError'
/{domainID}/alarms/{alarmID}:
get:
operationId: viewAlarm
summary: View Alarm
description: Retrieves an alarm by ID
tags:
- alarms
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/AlarmID'
security:
- bearerAuth: []
responses:
'200':
$ref: '#/components/responses/AlarmRes'
'400':
description: Missing or invalid alarm ID
'401':
description: Missing or invalid access token
'403':
description: Failed to perform authorization over the entity
'404':
description: Alarm does not exist
'422':
description: Database can't process request
'500':
$ref: '#/components/responses/ServiceError'
put:
operationId: updateAlarm
summary: Update Alarm
description: Updates an existing alarm
tags:
- alarms
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/AlarmID'
security:
- bearerAuth: []
requestBody:
$ref: '#/components/requestBodies/AlarmUpdateReq'
responses:
'200':
$ref: '#/components/responses/AlarmRes'
'400':
description: Failed due to malformed JSON
'401':
description: Missing or invalid access token
'403':
description: Failed to perform authorization over the entity
'404':
description: Alarm does not exist
'415':
description: Missing or invalid content type
'422':
description: Database can't process request
'500':
$ref: '#/components/responses/ServiceError'
delete:
operationId: deleteAlarm
summary: Delete Alarm
description: Deletes an alarm
tags:
- alarms
parameters:
- $ref: '#/components/parameters/DomainID'
- $ref: '#/components/parameters/AlarmID'
security:
- bearerAuth: []
responses:
'204':
description: Alarm deleted successfully
'400':
description: Failed due to malformed alarm ID
'401':
description: Missing or invalid access token
'403':
description: Failed to perform authorization over the entity
'404':
description: Alarm does not exist
'422':
description: Database can't process request
'500':
$ref: '#/components/responses/ServiceError'
/health:
get:
summary: Retrieves service health check info
tags:
- health
security: []
responses:
'200':
$ref: '#/components/responses/HealthRes'
'500':
$ref: '#/components/responses/ServiceError'
components:
schemas:
Alarm:
type: object
properties:
id:
type: string
description: Unique alarm identifier
readOnly: true
rule_id:
type: string
description: Rule ID that triggered this alarm
domain_id:
type: string
description: Domain ID this alarm belongs to
channel_id:
type: string
description: Channel ID where the alarm was triggered
client_id:
type: string
description: Client ID that triggered the alarm
subtopic:
type: string
description: Subtopic associated with the alarm
status:
type: string
description: Alarm status
enum: [active, cleared]
measurement:
type: string
description: Measurement that triggered the alarm
value:
type: string
description: Value that triggered the alarm
unit:
type: string
description: Unit of measurement
threshold:
type: string
description: Threshold value that was exceeded
cause:
type: string
description: Cause or description of the alarm
severity:
type: integer
description: Severity level (0-100)
minimum: 0
maximum: 100
assignee_id:
type: string
description: ID of the user assigned to this alarm
created_at:
type: string
format: date-time
description: Creation timestamp
readOnly: true
updated_at:
type: string
format: date-time
description: Last update timestamp
readOnly: true
updated_by:
type: string
description: User who last updated the alarm
readOnly: true
assigned_at:
type: string
format: date-time
description: When the alarm was assigned
readOnly: true
assigned_by:
type: string
description: User who assigned the alarm
readOnly: true
acknowledged_at:
type: string
format: date-time
description: When the alarm was acknowledged
readOnly: true
acknowledged_by:
type: string
description: User who acknowledged the alarm
readOnly: true
resolved_at:
type: string
format: date-time
description: When the alarm was resolved
readOnly: true
resolved_by:
type: string
description: User who resolved the alarm
readOnly: true
metadata:
type: object
description: Custom metadata
additionalProperties: true
AlarmsPage:
type: object
properties:
offset:
type: integer
description: Number of items to skip during retrieval
minimum: 0
default: 0
limit:
type: integer
description: Size of the subset to retrieve
minimum: 1
maximum: 1000
default: 10
total:
type: integer
description: Total number of results
minimum: 0
alarms:
type: array
minItems: 0
items:
$ref: '#/components/schemas/Alarm'
required:
- alarms
- total
- offset
- limit
parameters:
DomainID:
name: domainID
description: Domain ID
in: path
required: true
schema:
type: string
AlarmID:
name: alarmID
description: Alarm ID
in: path
required: true
schema:
type: string
Offset:
name: offset
description: Number of items to skip
in: query
required: false
schema:
type: integer
default: 0
minimum: 0
Limit:
name: limit
description: Size of the subset to retrieve
in: query
required: false
schema:
type: integer
default: 10
minimum: 1
maximum: 1000
Order:
name: order
description: Order by field
in: query
required: false
schema:
type: string
enum: [created_at, updated_at]
default: created_at
Dir:
name: dir
description: Sort direction
in: query
required: false
schema:
type: string
enum: [asc, desc]
default: desc
ChannelID:
name: channel_id
description: Filter by channel ID
in: query
required: false
schema:
type: string
ClientID:
name: client_id
description: Filter by client ID
in: query
required: false
schema:
type: string
Subtopic:
name: subtopic
description: Filter by subtopic
in: query
required: false
schema:
type: string
RuleID:
name: rule_id
description: Filter by rule ID
in: query
required: false
schema:
type: string
Status:
name: status
description: Filter by alarm status
in: query
required: false
schema:
type: string
enum: [active, cleared, all]
default: all
AssigneeID:
name: assignee_id
description: Filter by assignee ID
in: query
required: false
schema:
type: string
Severity:
name: severity
description: Filter by severity level
in: query
required: false
schema:
type: integer
minimum: 0
maximum: 100
UpdatedBy:
name: updated_by
description: Filter by user who updated
in: query
required: false
schema:
type: string
AssignedBy:
name: assigned_by
description: Filter by user who assigned
in: query
required: false
schema:
type: string
AcknowledgedBy:
name: acknowledged_by
description: Filter by user who acknowledged
in: query
required: false
schema:
type: string
ResolvedBy:
name: resolved_by
description: Filter by user who resolved
in: query
required: false
schema:
type: string
CreatedFrom:
name: created_from
description: Filter alarms created after this time (RFC3339 format)
in: query
required: false
schema:
type: string
format: date-time
CreatedTo:
name: created_to
description: Filter alarms created before this time (RFC3339 format)
in: query
required: false
schema:
type: string
format: date-time
requestBodies:
AlarmUpdateReq:
description: JSON-formatted document describing the alarm update
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: Alarm status
enum: [active, cleared]
assignee_id:
type: string
description: ID of the user assigned to this alarm
severity:
type: integer
description: Severity level (0-100)
minimum: 0
maximum: 100
metadata:
type: object
description: Custom metadata
additionalProperties: true
responses:
AlarmRes:
description: Alarm data retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/Alarm'
links:
update:
operationId: updateAlarm
parameters:
alarmID: $response.body#/id
domainID: $response.body#/domain_id
delete:
operationId: deleteAlarm
parameters:
alarmID: $response.body#/id
domainID: $response.body#/domain_id
AlarmsPageRes:
description: Alarms page retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/AlarmsPage'
ServiceError:
description: Unexpected server-side error occurred
HealthRes:
description: Service Health Check
content:
application/health+json:
schema:
$ref: "./schemas/health_info.yaml"
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
* Users access: "Authorization: Bearer <user_token>"
+3 -3
View File
@@ -9,11 +9,11 @@ info:
Some useful links:
- [The Magistrala repository](https://github.com/absmach/magistrala)
contact:
email: info@abstractmachines.fr
email: info@absmach.eu
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9013
@@ -24,7 +24,7 @@ tags:
description: Everything about your Configs
externalDocs:
description: Find out more about Configs
url: https://docs.magistrala.abstractmachines.fr/
url: https://docs.magistrala.absmach.eu
paths:
/{domainID}/clients/configs:
+3 -3
View File
@@ -9,11 +9,11 @@ info:
Some useful links:
- [The Magistrala repository](https://github.com/absmach/magistrala)
contact:
email: info@abstractmachines.fr
email: info@absmach.eu
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9014
@@ -26,7 +26,7 @@ tags:
description: Everything about your Notifiers
externalDocs:
description: Find out more about notifiers
url: https://docs.magistrala.abstractmachines.fr/
url: https://docs.magistrala.absmach.eu
paths:
/subscriptions:
+3 -3
View File
@@ -9,11 +9,11 @@ info:
Some useful links:
- [The Magistrala repository](https://github.com/absmach/magistrala)
contact:
email: info@abstractmachines.fr
email: info@absmach.eu
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9003
@@ -30,7 +30,7 @@ tags:
description: Everything about your Readers
externalDocs:
description: Find out more about readers
url: https://docs.magistrala.abstractmachines.fr/
url: https://docs.magistrala.absmach.eu
paths:
/{domainID}/channels/{chanId}/messages:
+1 -1
View File
@@ -6,7 +6,7 @@ info:
title: Magistrala Reports Service API
description: |
HTTP API for managing reports service.
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9017
tags:
+64 -5
View File
@@ -9,11 +9,11 @@ info:
Some useful links:
- [The Magistrala repository](https://github.com/absmach/magistrala)
contact:
email: info@abstractmachines.fr
email: info@absmach.eu
license:
name: Apache 2.0
url: https://github.com/absmach/magistrala/blob/main/LICENSE
version: 0.15.1
version: 0.18.5
servers:
- url: http://localhost:9008
@@ -24,7 +24,7 @@ tags:
description: Everything about your Rules Engine
externalDocs:
description: Find out more about rules engine
url: https://docs.magistrala.abstractmachines.fr/
url: https://docs.magistrala.absmach.eu
paths:
/{domainID}/rules:
@@ -40,7 +40,7 @@ paths:
security:
- bearerAuth: []
requestBody:
- $ref: '#/components/requestBodies/RuleCreateReq'
$ref: '#/components/requestBodies/RuleCreateReq'
responses:
'201':
$ref: '#/components/responses/RuleCreateRes'
@@ -121,7 +121,7 @@ paths:
security:
- bearerAuth: []
requestBody:
$ref: '#/components/schemas/Rule'
$ref: '#/components/requestBodies/RuleUpdateReq'
responses:
'200':
$ref: '#/components/responses/RuleRes'
@@ -469,6 +469,65 @@ components:
- input_topic
- logic
- schedule
RuleUpdateReq:
description: JSON-formatted document describing the rule update
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: Rule name
metadata:
type: object
description: Custom metadata
additionalProperties:
type: string
input_channel:
type: string
description: Input channel for receiving messages
input_topic:
type: string
description: Input topic for receiving messages
logic:
type: object
description: Rule processing logic script
properties:
script:
type: string
description: Script content
output_channel:
type: string
description: Output channel for processed messages
output_topic:
type: string
description: Output topic for processed messages
schedule:
type: object
description: Rule execution schedule
properties:
start_datetime:
type: string
format: date-time
description: When the schedule becomes active
time:
type: string
format: date-time
description: Specific time for the rule to run
recurring:
type: string
description: Schedule recurrence pattern
enum: [None, Daily, Weekly, Monthly]
recurring_period:
type: integer
minimum: 1
description: Controls how many intervals to skip between executions
status:
type: string
description: Rule status
enum: [enabled, disabled]
responses:
RuleCreateRes:
+1 -1
View File
@@ -119,4 +119,4 @@ Setting `SMQ_AUTH_GRPC_CLIENT_CERT` and `SMQ_AUTH_GRPC_CLIENT_KEY` will enable T
## Usage
For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=bootstrap.yaml).
For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.absmach.eu/?urls.primaryName=bootstrap.yaml).
+17 -18
View File
@@ -7,7 +7,6 @@ import (
"context"
"github.com/absmach/magistrala/bootstrap"
api "github.com/absmach/supermq/api/http"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
@@ -16,13 +15,13 @@ import (
)
func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(addReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
@@ -59,13 +58,13 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(updateCertReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
@@ -87,13 +86,13 @@ func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(entityReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
@@ -128,13 +127,13 @@ func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(updateReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
@@ -159,13 +158,13 @@ func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
func updateConnEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(updateConnReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
@@ -184,13 +183,13 @@ func updateConnEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
func listEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(listReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
@@ -234,13 +233,13 @@ func listEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
func removeEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(entityReq)
if err := req.validate(); err != nil {
return removeRes{}, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
@@ -254,7 +253,7 @@ func removeEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader, secure bool) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(bootstrapReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
@@ -270,13 +269,13 @@ func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader, sec
}
func stateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(changeStateReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
+21 -20
View File
@@ -50,7 +50,7 @@ const (
var (
encKey = []byte("1234567891011121")
metadata = map[string]interface{}{"meta": "data"}
metadata = map[string]any{"meta": "data"}
addExternalID = testsutil.GenerateUUID(&testing.T{})
addExternalKey = testsutil.GenerateUUID(&testing.T{})
addClientID = testsutil.GenerateUUID(&testing.T{})
@@ -89,11 +89,11 @@ var (
CACert: "newca",
}
missingIDRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrMissingID.Error(), Msg: apiutil.ErrValidation.Error()})
missingKeyRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrBearerKey.Error(), Msg: apiutil.ErrValidation.Error()})
bsErrorRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrBootstrap.Error()})
extKeyRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrExternalKey.Error()})
extSecKeyRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrExternalKeySecure.Error()})
missingIDRes = toJSON(apiutil.ErrMissingID)
missingKeyRes = toJSON(apiutil.ErrBearerKey)
unknownExternalIDErrorRes = toJSON(svcerr.ErrNotFound)
extKeyRes = toJSON(bootstrap.ErrExternalKey)
extSecKeyRes = toJSON(bootstrap.ErrExternalKeySecure)
)
type testRequest struct {
@@ -180,11 +180,12 @@ func newBootstrapServer() (*httptest.Server, *mocks.Service, *authnmocks.Authent
logger := smqlog.NewMock()
svc := new(mocks.Service)
authn := new(authnmocks.Authentication)
mux := bsapi.MakeHandler(svc, authn, bootstrap.NewConfigReader(encKey), logger, instanceID)
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
mux := bsapi.MakeHandler(svc, am, bootstrap.NewConfigReader(encKey), logger, instanceID)
return httptest.NewServer(mux), svc, authn
}
func toJSON(data interface{}) string {
func toJSON(data any) string {
jsonData, err := json.Marshal(data)
if err != nil {
return ""
@@ -256,7 +257,7 @@ func TestAdd(t *testing.T) {
domainID: domainID,
token: validToken,
contentType: contentType,
status: http.StatusConflict,
status: http.StatusBadRequest,
location: "",
err: svcerr.ErrConflict,
},
@@ -266,7 +267,7 @@ func TestAdd(t *testing.T) {
domainID: domainID,
token: validToken,
contentType: contentType,
status: http.StatusConflict,
status: http.StatusBadRequest,
location: "",
err: svcerr.ErrConflict,
},
@@ -276,7 +277,7 @@ func TestAdd(t *testing.T) {
domainID: domainID,
token: validToken,
contentType: contentType,
status: http.StatusConflict,
status: http.StatusBadRequest,
location: "",
err: svcerr.ErrConflict,
},
@@ -1189,9 +1190,9 @@ func TestBootstrap(t *testing.T) {
externalID: unknown,
externalKey: c.ExternalKey,
status: http.StatusNotFound,
res: bsErrorRes,
res: unknownExternalIDErrorRes,
secure: false,
err: bootstrap.ErrBootstrap,
err: svcerr.ErrNotFound,
},
{
desc: "bootstrap a Client with an empty ID",
@@ -1200,7 +1201,7 @@ func TestBootstrap(t *testing.T) {
status: http.StatusBadRequest,
res: missingIDRes,
secure: false,
err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrMalformedEntity),
err: apiutil.ErrMissingID,
},
{
desc: "bootstrap a Client with unknown key",
@@ -1209,16 +1210,16 @@ func TestBootstrap(t *testing.T) {
status: http.StatusForbidden,
res: extKeyRes,
secure: false,
err: errors.Wrap(bootstrap.ErrExternalKey, errors.New("")),
err: bootstrap.ErrExternalKey,
},
{
desc: "bootstrap a Client with an empty key",
externalID: c.ExternalID,
externalKey: "",
status: http.StatusBadRequest,
status: http.StatusUnauthorized,
res: missingKeyRes,
secure: false,
err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrAuthentication),
err: apiutil.ErrBearerKey,
},
{
desc: "bootstrap known Client",
@@ -1394,9 +1395,9 @@ func TestChangeState(t *testing.T) {
}
type channel struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata any `json:"metadata,omitempty"`
}
type config struct {
+3 -3
View File
@@ -61,9 +61,9 @@ func (res configRes) Empty() bool {
}
type channelRes struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata any `json:"metadata,omitempty"`
}
type viewRes struct {
+23 -25
View File
@@ -11,7 +11,6 @@ import (
"net/url"
"strings"
mgapi "github.com/absmach/magistrala/api"
"github.com/absmach/magistrala/bootstrap"
"github.com/absmach/supermq"
api "github.com/absmach/supermq/api/http"
@@ -41,17 +40,16 @@ var (
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc bootstrap.Service, authn smqauthn.Authentication, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler {
func MakeHandler(svc bootstrap.Service, authn smqauthn.AuthNMiddleware, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, mgapi.EncodeError)),
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
}
r := chi.NewRouter()
r.Route("/{domainID}/clients", func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(api.AuthenticateMiddleware(authn, true))
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
r.Route("/configs", func(r chi.Router) {
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
addEndpoint(svc),
@@ -97,7 +95,7 @@ func MakeHandler(svc bootstrap.Service, authn smqauthn.Authentication, reader bo
})
})
r.With(api.AuthenticateMiddleware(authn, true)).Put("/state/{clientID}", otelhttp.NewHandler(kithttp.NewServer(
r.With(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware()).Put("/state/{clientID}", otelhttp.NewHandler(kithttp.NewServer(
stateEndpoint(svc),
decodeStateRequest,
api.EncodeResponse,
@@ -128,54 +126,54 @@ func MakeHandler(svc bootstrap.Service, authn smqauthn.Authentication, reader bo
return r
}
func decodeAddRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeAddRequest(_ context.Context, r *http.Request) (any, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
return nil, apiutil.ErrUnsupportedContentType
}
req := addReq{
token: apiutil.ExtractBearerToken(r),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
return req, nil
}
func decodeUpdateRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateRequest(_ context.Context, r *http.Request) (any, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
return nil, apiutil.ErrUnsupportedContentType
}
req := updateReq{
id: chi.URLParam(r, "configID"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
return req, nil
}
func decodeUpdateCertRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateCertRequest(_ context.Context, r *http.Request) (any, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
return nil, apiutil.ErrUnsupportedContentType
}
req := updateCertReq{
clientID: chi.URLParam(r, "certID"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
return req, nil
}
func decodeUpdateConnRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateConnRequest(_ context.Context, r *http.Request) (any, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
return nil, apiutil.ErrUnsupportedContentType
}
req := updateConnReq{
@@ -183,13 +181,13 @@ func decodeUpdateConnRequest(_ context.Context, r *http.Request) (interface{}, e
id: chi.URLParam(r, "connID"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
return req, nil
}
func decodeListRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeListRequest(_ context.Context, r *http.Request) (any, error) {
o, err := apiutil.ReadNumQuery[uint64](r, offsetKey, defOffset)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
@@ -214,7 +212,7 @@ func decodeListRequest(_ context.Context, r *http.Request) (interface{}, error)
return req, nil
}
func decodeBootstrapRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeBootstrapRequest(_ context.Context, r *http.Request) (any, error) {
req := bootstrapReq{
id: chi.URLParam(r, "externalID"),
key: apiutil.ExtractClientSecret(r),
@@ -223,9 +221,9 @@ func decodeBootstrapRequest(_ context.Context, r *http.Request) (interface{}, er
return req, nil
}
func decodeStateRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeStateRequest(_ context.Context, r *http.Request) (any, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
return nil, apiutil.ErrUnsupportedContentType
}
req := changeStateReq{
@@ -233,13 +231,13 @@ func decodeStateRequest(_ context.Context, r *http.Request) (interface{}, error)
id: chi.URLParam(r, "clientID"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
return req, nil
}
func decodeEntityRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeEntityRequest(_ context.Context, r *http.Request) (any, error) {
req := entityReq{
id: chi.URLParam(r, "configID"),
}
@@ -247,7 +245,7 @@ func decodeEntityRequest(_ context.Context, r *http.Request) (interface{}, error
return req, nil
}
func encodeSecureRes(_ context.Context, w http.ResponseWriter, response interface{}) error {
func encodeSecureRes(_ context.Context, w http.ResponseWriter, response any) error {
w.Header().Set("Content-Type", byteContentType)
w.WriteHeader(http.StatusOK)
if b, ok := response.([]byte); ok {
+10 -10
View File
@@ -32,16 +32,16 @@ type Config struct {
// Channel represents SuperMQ channel corresponding SuperMQ Client is connected to.
type Channel struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
DomainID string `json:"domain_id"`
Parent string `json:"parent_id,omitempty"`
Description string `json:"description,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
Status clients.Status `json:"status"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
DomainID string `json:"domain_id"`
Parent string `json:"parent_id,omitempty"`
Description string `json:"description,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
Status clients.Status `json:"status"`
}
// Filter is used for the search filters.
+1 -1
View File
@@ -12,7 +12,7 @@ type removeEvent struct {
type updateChannelEvent struct {
id string
name string
metadata map[string]interface{}
metadata map[string]any
updatedAt time.Time
updatedBy string
}
+6 -6
View File
@@ -89,14 +89,14 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
return nil
}
func decodeRemoveClient(event map[string]interface{}) removeEvent {
func decodeRemoveClient(event map[string]any) removeEvent {
return removeEvent{
id: events.Read(event, "id", ""),
}
}
func decodeUpdateChannel(event map[string]interface{}) updateChannelEvent {
metadata := events.Read(event, "metadata", map[string]interface{}{})
func decodeUpdateChannel(event map[string]any) updateChannelEvent {
metadata := events.Read(event, "metadata", map[string]any{})
return updateChannelEvent{
id: events.Read(event, "id", ""),
@@ -107,13 +107,13 @@ func decodeUpdateChannel(event map[string]interface{}) updateChannelEvent {
}
}
func decodeRemoveChannel(event map[string]interface{}) removeEvent {
func decodeRemoveChannel(event map[string]any) removeEvent {
return removeEvent{
id: events.Read(event, "id", ""),
}
}
func decodeConnectClient(event map[string]interface{}) connectionEvent {
func decodeConnectClient(event map[string]any) connectionEvent {
if events.Read(event, "memberKind", "") != memberKind && events.Read(event, "relation", "") != relation {
return connectionEvent{}
}
@@ -124,7 +124,7 @@ func decodeConnectClient(event map[string]interface{}) connectionEvent {
}
}
func decodeDisconnectClient(event map[string]interface{}) connectionEvent {
func decodeDisconnectClient(event map[string]any) connectionEvent {
if events.Read(event, "memberKind", "") != memberKind && events.Read(event, "relation", "") != relation {
return connectionEvent{}
}
+22 -22
View File
@@ -47,8 +47,8 @@ type configEvent struct {
operation string
}
func (ce configEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
func (ce configEvent) Encode() (map[string]any, error) {
val := map[string]any{
"state": ce.State.String(),
"operation": ce.operation,
}
@@ -94,8 +94,8 @@ type removeConfigEvent struct {
client string
}
func (rce removeConfigEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
func (rce removeConfigEvent) Encode() (map[string]any, error) {
return map[string]any{
"client_id": rce.client,
"operation": configRemove,
}, nil
@@ -108,8 +108,8 @@ type listConfigsEvent struct {
partialMatch map[string]string
}
func (rce listConfigsEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
func (rce listConfigsEvent) Encode() (map[string]any, error) {
val := map[string]any{
"offset": rce.offset,
"limit": rce.limit,
"operation": configList,
@@ -130,8 +130,8 @@ type bootstrapEvent struct {
success bool
}
func (be bootstrapEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
func (be bootstrapEvent) Encode() (map[string]any, error) {
val := map[string]any{
"external_id": be.externalID,
"success": be.success,
"operation": clientBootstrap,
@@ -179,8 +179,8 @@ type changeStateEvent struct {
state bootstrap.State
}
func (cse changeStateEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
func (cse changeStateEvent) Encode() (map[string]any, error) {
return map[string]any{
"client_id": cse.mgClient,
"state": cse.state.String(),
"operation": clientStateChange,
@@ -192,8 +192,8 @@ type updateConnectionsEvent struct {
mgChannels []string
}
func (uce updateConnectionsEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
func (uce updateConnectionsEvent) Encode() (map[string]any, error) {
return map[string]any{
"client_id": uce.mgClient,
"channels": uce.mgChannels,
"operation": clientUpdateConnections,
@@ -207,8 +207,8 @@ type updateCertEvent struct {
caCert string
}
func (uce updateCertEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
func (uce updateCertEvent) Encode() (map[string]any, error) {
return map[string]any{
"client_id": uce.clientID,
"client_cert": uce.clientCert,
"client_key": uce.clientKey,
@@ -222,8 +222,8 @@ type removeHandlerEvent struct {
operation string
}
func (rhe removeHandlerEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
func (rhe removeHandlerEvent) Encode() (map[string]any, error) {
return map[string]any{
"config_id": rhe.id,
"operation": rhe.operation,
}, nil
@@ -233,8 +233,8 @@ type updateChannelHandlerEvent struct {
bootstrap.Channel
}
func (uche updateChannelHandlerEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
func (uche updateChannelHandlerEvent) Encode() (map[string]any, error) {
val := map[string]any{
"operation": channelUpdateHandler,
}
@@ -255,8 +255,8 @@ type connectClientEvent struct {
channelID string
}
func (cte connectClientEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
func (cte connectClientEvent) Encode() (map[string]any, error) {
return map[string]any{
"client_id": cte.clientID,
"channel_id": cte.channelID,
"operation": clientConnect,
@@ -268,8 +268,8 @@ type disconnectClientEvent struct {
channelID string
}
func (dte disconnectClientEvent) Encode() (map[string]interface{}, error) {
return map[string]interface{}{
func (dte disconnectClientEvent) Encode() (map[string]any, error) {
return map[string]any{
"client_id": dte.clientID,
"channel_id": dte.channelID,
"operation": clientDisconnect,
+54 -55
View File
@@ -15,7 +15,6 @@ import (
"github.com/absmach/magistrala/bootstrap/events/producer"
"github.com/absmach/magistrala/bootstrap/mocks"
"github.com/absmach/magistrala/internal/testsutil"
"github.com/absmach/supermq/pkg/authn"
smqauthn "github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
@@ -72,7 +71,7 @@ var (
channel = bootstrap.Channel{
ID: testsutil.GenerateUUID(&testing.T{}),
Name: "name",
Metadata: map[string]interface{}{"name": "value"},
Metadata: map[string]any{"name": "value"},
}
config = bootstrap.Config{
@@ -136,7 +135,7 @@ func TestAdd(t *testing.T) {
listErr error
saveErr error
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "create config successfully",
@@ -145,7 +144,7 @@ func TestAdd(t *testing.T) {
id: validID,
domainID: domainID,
channel: config.Channels,
event: map[string]interface{}{
event: map[string]any{
"client_id": "1",
"domain_id": domainID,
"name": config.Name,
@@ -205,7 +204,7 @@ func TestAdd(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
@@ -237,7 +236,7 @@ func TestView(t *testing.T) {
domainID string
retrieveErr error
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "view successfully",
@@ -246,7 +245,7 @@ func TestView(t *testing.T) {
id: validID,
domainID: domainID,
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_id": config.ClientID,
"domain_id": config.DomainID,
"name": config.Name,
@@ -282,7 +281,7 @@ func TestView(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
@@ -329,7 +328,7 @@ func TestUpdate(t *testing.T) {
domainID string
updateErr error
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "update config successfully",
@@ -338,7 +337,7 @@ func TestUpdate(t *testing.T) {
id: validID,
domainID: domainID,
err: nil,
event: map[string]interface{}{
event: map[string]any{
"name": modified.Name,
"content": modified.Content,
"timestamp": time.Now().UnixNano(),
@@ -376,7 +375,7 @@ func TestUpdate(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
@@ -409,7 +408,7 @@ func TestUpdateConnections(t *testing.T) {
listErr error
updateErr error
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "update connections successfully",
@@ -419,7 +418,7 @@ func TestUpdateConnections(t *testing.T) {
domainID: domainID,
connections: []string{config.Channels[0].ID},
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_id": config.ClientID,
"channels": "2",
"timestamp": time.Now().Unix(),
@@ -488,7 +487,7 @@ func TestUpdateConnections(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
@@ -520,7 +519,7 @@ func TestUpdateCert(t *testing.T) {
caCert string
updateErr error
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "update cert successfully",
@@ -532,7 +531,7 @@ func TestUpdateCert(t *testing.T) {
clientKey: "clientKey",
caCert: "caCert",
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_secret": config.ClientSecret,
"client_cert": "clientCert",
"client_key": "clientKey",
@@ -599,7 +598,7 @@ func TestUpdateCert(t *testing.T) {
clientKey: "clientKey",
caCert: "",
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_secret": config.ClientSecret,
"client_cert": "clientCert",
"client_key": "clientKey",
@@ -624,7 +623,7 @@ func TestUpdateCert(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
@@ -667,7 +666,7 @@ func TestList(t *testing.T) {
listObjectsErr error
retrieveErr error
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "list successfully as super admin",
@@ -686,7 +685,7 @@ func TestList(t *testing.T) {
limit: 10,
listObjectsResponse: policysvc.PolicyPage{},
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_id": c.ClientID,
"domain_id": c.DomainID,
"name": c.Name,
@@ -714,7 +713,7 @@ func TestList(t *testing.T) {
limit: 10,
listObjectsResponse: policysvc.PolicyPage{},
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_id": c.ClientID,
"domain_id": c.DomainID,
"name": c.Name,
@@ -742,7 +741,7 @@ func TestList(t *testing.T) {
limit: 10,
listObjectsResponse: policysvc.PolicyPage{},
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_id": c.ClientID,
"domain_id": c.DomainID,
"name": c.Name,
@@ -831,7 +830,7 @@ func TestList(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
@@ -862,7 +861,7 @@ func TestRemove(t *testing.T) {
session smqauthn.Session
removeErr error
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "remove config successfully",
@@ -871,7 +870,7 @@ func TestRemove(t *testing.T) {
userID: validID,
domainID: domainID,
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_id": config.ClientID,
"timestamp": time.Now().Unix(),
"operation": configRemove,
@@ -902,7 +901,7 @@ func TestRemove(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
@@ -925,14 +924,14 @@ func TestBootstrap(t *testing.T) {
externalKey string
err error
retrieveErr error
event map[string]interface{}
event map[string]any
}{
{
desc: "bootstrap successfully",
externalID: config.ExternalID,
externalKey: config.ExternalKey,
err: nil,
event: map[string]interface{}{
event: map[string]any{
"external_id": config.ExternalID,
"success": "1",
"timestamp": time.Now().Unix(),
@@ -945,7 +944,7 @@ func TestBootstrap(t *testing.T) {
externalKey: "external_id",
retrieveErr: bootstrap.ErrBootstrap,
err: bootstrap.ErrBootstrap,
event: map[string]interface{}{
event: map[string]any{
"external_id": "external_id",
"success": "0",
"timestamp": time.Now().Unix(),
@@ -966,7 +965,7 @@ func TestBootstrap(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
@@ -990,14 +989,14 @@ func TestChangeState(t *testing.T) {
token string
session smqauthn.Session
state bootstrap.State
authResponse authn.Session
authResponse smqauthn.Session
authorizeErr error
connectErr error
retrieveErr error
stateErr error
authenticateErr error
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "change state to active",
@@ -1006,9 +1005,9 @@ func TestChangeState(t *testing.T) {
userID: validID,
domainID: domainID,
state: bootstrap.Active,
authResponse: authn.Session{},
authResponse: smqauthn.Session{},
err: nil,
event: map[string]interface{}{
event: map[string]any{
"client_id": config.ClientID,
"state": bootstrap.Active.String(),
"timestamp": time.Now().Unix(),
@@ -1065,7 +1064,7 @@ func TestChangeState(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
@@ -1088,13 +1087,13 @@ func TestUpdateChannelHandler(t *testing.T) {
desc string
channel bootstrap.Channel
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "update channel handler successfully",
channel: channel,
err: nil,
event: map[string]interface{}{
event: map[string]any{
"channel_id": channel.ID,
"metadata": "{\"name\":\"value\"}",
"name": channel.Name,
@@ -1125,7 +1124,7 @@ func TestUpdateChannelHandler(t *testing.T) {
desc: "update channel handler successfully with modified fields",
channel: channel,
err: nil,
event: map[string]interface{}{
event: map[string]any{
"channel_id": channel.ID,
"metadata": "{\"name\":\"value\"}",
"name": channel.Name,
@@ -1148,7 +1147,7 @@ func TestUpdateChannelHandler(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
@@ -1170,13 +1169,13 @@ func TestRemoveChannelHandler(t *testing.T) {
desc string
channelID string
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "remove channel handler successfully",
channelID: channel.ID,
err: nil,
event: map[string]interface{}{
event: map[string]any{
"config_id": channel.ID,
"operation": channelHandlerRemove,
"timestamp": time.Now().UnixNano(),
@@ -1209,7 +1208,7 @@ func TestRemoveChannelHandler(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
@@ -1232,13 +1231,13 @@ func TestRemoveConfigHandler(t *testing.T) {
desc string
configID string
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "remove config handler successfully",
configID: channel.ID,
err: nil,
event: map[string]interface{}{
event: map[string]any{
"config_id": channel.ID,
"operation": configHandlerRemove,
"timestamp": time.Now().UnixNano(),
@@ -1271,7 +1270,7 @@ func TestRemoveConfigHandler(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
@@ -1295,14 +1294,14 @@ func TestConnectClientHandler(t *testing.T) {
channelID string
clientID string
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "connect client handler successfully",
channelID: channel.ID,
clientID: "1",
err: nil,
event: map[string]interface{}{
event: map[string]any{
"channel_id": channel.ID,
"client_id": "1",
"operation": clientConnect,
@@ -1345,7 +1344,7 @@ func TestConnectClientHandler(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
@@ -1369,14 +1368,14 @@ func TestDisconnectClientHandler(t *testing.T) {
channelID string
clientID string
err error
event map[string]interface{}
event map[string]any
}{
{
desc: "disconnect client handler successfully",
channelID: channel.ID,
clientID: "1",
err: nil,
event: map[string]interface{}{
event: map[string]any{
"channel_id": channel.ID,
"client_id": "1",
"operation": clientDisconnect,
@@ -1407,7 +1406,7 @@ func TestDisconnectClientHandler(t *testing.T) {
channelID: channel.ID,
clientID: "1",
err: nil,
event: map[string]interface{}{
event: map[string]any{
"channel_id": channel.ID,
"client_id": "1",
"operation": clientDisconnect,
@@ -1429,7 +1428,7 @@ func TestDisconnectClientHandler(t *testing.T) {
Block: time.Second,
}).Val()
var event map[string]interface{}
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
@@ -1442,7 +1441,7 @@ func TestDisconnectClientHandler(t *testing.T) {
}
}
func test(t *testing.T, expected, actual map[string]interface{}, description string) {
func test(t *testing.T, expected, actual map[string]any, description string) {
if expected != nil && actual != nil {
ts1 := expected["timestamp"].(int64)
ats := actual["timestamp"].(string)
@@ -1466,8 +1465,8 @@ func test(t *testing.T, expected, actual map[string]interface{}, description str
delete(actual, "occurred_at")
}
exchs := expected["channels"].([]interface{})
achs := actual["channels"].([]interface{})
exchs := expected["channels"].([]any)
achs := actual["channels"].([]any)
if exchs != nil && achs != nil {
if assert.Len(t, exchs, len(achs), fmt.Sprintf("%s: got incorrect number of channels\n", description)) {
+4 -5
View File
@@ -9,7 +9,6 @@ import (
"github.com/absmach/magistrala/bootstrap"
smqauthn "github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/authz"
smqauthz "github.com/absmach/supermq/pkg/authz"
"github.com/absmach/supermq/pkg/policies"
)
@@ -23,11 +22,11 @@ var _ bootstrap.Service = (*authorizationMiddleware)(nil)
type authorizationMiddleware struct {
svc bootstrap.Service
authz smqauthz.Authorization
authz authz.Authorization
}
// AuthorizationMiddleware adds authorization to the clients service.
func AuthorizationMiddleware(svc bootstrap.Service, authz smqauthz.Authorization) bootstrap.Service {
func AuthorizationMiddleware(svc bootstrap.Service, authz authz.Authorization) bootstrap.Service {
return &authorizationMiddleware{
svc: svc,
authz: authz,
@@ -128,7 +127,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.SuperMQObject,
}); err != nil {
}, nil); err != nil {
return err
}
return nil
@@ -144,7 +143,7 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, domain, subjTy
ObjectType: objType,
Object: obj,
}
if err := am.authz.Authorize(ctx, req); err != nil {
if err := am.authz.Authorize(ctx, req, nil); err != nil {
return err
}
return nil
+12 -11
View File
@@ -1,10 +1,11 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
@@ -40,23 +41,23 @@ func (_m *ConfigReader) EXPECT() *ConfigReader_Expecter {
}
// ReadConfig provides a mock function for the type ConfigReader
func (_mock *ConfigReader) ReadConfig(config bootstrap.Config, b bool) (interface{}, error) {
func (_mock *ConfigReader) ReadConfig(config bootstrap.Config, b bool) (any, error) {
ret := _mock.Called(config, b)
if len(ret) == 0 {
panic("no return value specified for ReadConfig")
}
var r0 interface{}
var r0 any
var r1 error
if returnFunc, ok := ret.Get(0).(func(bootstrap.Config, bool) (interface{}, error)); ok {
if returnFunc, ok := ret.Get(0).(func(bootstrap.Config, bool) (any, error)); ok {
return returnFunc(config, b)
}
if returnFunc, ok := ret.Get(0).(func(bootstrap.Config, bool) interface{}); ok {
if returnFunc, ok := ret.Get(0).(func(bootstrap.Config, bool) any); ok {
r0 = returnFunc(config, b)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{})
r0 = ret.Get(0).(any)
}
}
if returnFunc, ok := ret.Get(1).(func(bootstrap.Config, bool) error); ok {
@@ -97,12 +98,12 @@ func (_c *ConfigReader_ReadConfig_Call) Run(run func(config bootstrap.Config, b
return _c
}
func (_c *ConfigReader_ReadConfig_Call) Return(ifaceVal interface{}, err error) *ConfigReader_ReadConfig_Call {
_c.Call.Return(ifaceVal, err)
func (_c *ConfigReader_ReadConfig_Call) Return(v any, err error) *ConfigReader_ReadConfig_Call {
_c.Call.Return(v, err)
return _c
}
func (_c *ConfigReader_ReadConfig_Call) RunAndReturn(run func(config bootstrap.Config, b bool) (interface{}, error)) *ConfigReader_ReadConfig_Call {
func (_c *ConfigReader_ReadConfig_Call) RunAndReturn(run func(config bootstrap.Config, b bool) (any, error)) *ConfigReader_ReadConfig_Call {
_c.Call.Return(run)
return _c
}
+4 -3
View File
@@ -1,10 +1,11 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
+4 -3
View File
@@ -1,10 +1,11 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
+6 -6
View File
@@ -104,7 +104,7 @@ func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string
}
if !row.Next() {
return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, sql.ErrNoRows)
return bootstrap.Config{}, repoerr.ErrNotFound
}
if err := row.StructScan(&dbcfg); err != nil {
@@ -205,7 +205,7 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID
}
if !row.Next() {
return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, sql.ErrNoRows)
return bootstrap.Config{}, repoerr.ErrNotFound
}
if err := row.StructScan(&dbcfg); err != nil {
@@ -275,7 +275,7 @@ func (cr configRepository) Update(ctx context.Context, cfg bootstrap.Config) err
}
func (cr configRepository) UpdateCert(ctx context.Context, domainID, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
q := `UPDATE configs SET client_cert = :client_cert, client_key = :client_key, ca_cert = :ca_cert WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id
q := `UPDATE configs SET client_cert = :client_cert, client_key = :client_key, ca_cert = :ca_cert WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id
RETURNING magistrala_client, client_cert, client_key, ca_cert`
dbcfg := dbConfig{
@@ -436,7 +436,7 @@ func (cr configRepository) UpdateChannel(ctx context.Context, c bootstrap.Channe
return errors.Wrap(repoerr.ErrUpdateEntity, err)
}
q := `UPDATE channels SET name = :name, metadata = :metadata, updated_at = :updated_at, updated_by = :updated_by
q := `UPDATE channels SET name = :name, metadata = :metadata, updated_at = :updated_at, updated_by = :updated_by
WHERE magistrala_channel = :magistrala_channel`
if _, err = cr.db.NamedExecContext(ctx, q, dbch); err != nil {
return errors.Wrap(errUpdateChannels, err)
@@ -478,8 +478,8 @@ func (cr configRepository) DisconnectClient(ctx context.Context, channelID, clie
return nil
}
func buildRetrieveQueryParams(domainID string, clientIDs []string, filter bootstrap.Filter) (string, []interface{}) {
params := []interface{}{}
func buildRetrieveQueryParams(domainID string, clientIDs []string, filter bootstrap.Filter) (string, []any) {
params := []any{}
queries := []string{}
if len(clientIDs) != 0 {
+3 -3
View File
@@ -29,8 +29,8 @@ var (
ExternalKey: "external-key",
DomainID: testsutil.GenerateUUID(&testing.T{}),
Channels: []bootstrap.Channel{
{ID: "1", Name: "name 1", Metadata: map[string]interface{}{"meta": 1.0}},
{ID: "2", Name: "name 2", Metadata: map[string]interface{}{"meta": 2.0}},
{ID: "1", Name: "name 1", Metadata: map[string]any{"meta": 1.0}},
{ID: "2", Name: "name 2", Metadata: map[string]any{"meta": 2.0}},
},
Content: "content",
State: bootstrap.Inactive,
@@ -669,7 +669,7 @@ func TestUpdateChannel(t *testing.T) {
update := bootstrap.Channel{
ID: id,
Name: "update name",
Metadata: map[string]interface{}{"update": "metadata update"},
Metadata: map[string]any{"update": "metadata update"},
}
err = repo.UpdateChannel(context.Background(), update)
assert.Nil(t, err, fmt.Sprintf("updating config expected to succeed: %s.\n", err))
+4 -4
View File
@@ -26,9 +26,9 @@ type bootstrapRes struct {
}
type channelRes struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata any `json:"metadata,omitempty"`
}
func (res bootstrapRes) Code() int {
@@ -53,7 +53,7 @@ func NewConfigReader(encKey []byte) ConfigReader {
return reader{encKey: encKey}
}
func (r reader) ReadConfig(cfg Config, secure bool) (interface{}, error) {
func (r reader) ReadConfig(cfg Config, secure bool) (any, error) {
var channels []channelRes
for _, ch := range cfg.Channels {
channels = append(channels, channelRes{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
+5 -5
View File
@@ -18,9 +18,9 @@ import (
)
type readChan struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata any `json:"metadata,omitempty"`
}
type readResp struct {
@@ -59,7 +59,7 @@ func TestReadConfig(t *testing.T) {
{
ID: "smq_id",
Name: "smq_name",
Metadata: map[string]interface{}{"key": "value}"},
Metadata: map[string]any{"key": "value}"},
},
},
Content: "content",
@@ -71,7 +71,7 @@ func TestReadConfig(t *testing.T) {
{
ID: "smq_id",
Name: "smq_name",
Metadata: map[string]interface{}{"key": "value}"},
Metadata: map[string]any{"key": "value}"},
},
},
Content: "content",
+5 -5
View File
@@ -24,19 +24,19 @@ var (
ErrClients = errors.New("failed to receive response from Clients service")
// ErrExternalKey indicates a non-existent bootstrap configuration for given external key.
ErrExternalKey = errors.New("failed to get bootstrap configuration for given external key")
ErrExternalKey = errors.NewAuthZError("failed to get bootstrap configuration for given external key")
// ErrExternalKeySecure indicates error in getting bootstrap configuration for given encrypted external key.
ErrExternalKeySecure = errors.New("failed to get bootstrap configuration for given encrypted external key")
ErrExternalKeySecure = errors.NewAuthZError("failed to get bootstrap configuration for given encrypted external key")
// ErrBootstrap indicates error in getting bootstrap configuration.
ErrBootstrap = errors.New("failed to read bootstrap configuration")
// ErrAddBootstrap indicates error in adding bootstrap configuration.
ErrAddBootstrap = errors.New("failed to add bootstrap configuration")
ErrAddBootstrap = errors.NewServiceError("failed to add bootstrap configuration")
// ErrBootstrapState indicates an invalid bootstrap state.
ErrBootstrapState = errors.New("invalid bootstrap state")
ErrBootstrapState = errors.NewRequestError("invalid bootstrap state")
// ErrNotInSameDomain indicates entities are not in the same domain.
errNotInSameDomain = errors.New("entities are not in the same domain")
@@ -114,7 +114,7 @@ type Service interface {
// is to provide convenient way to generate custom configuration response
// based on the specific Config which will be consumed by the client.
type ConfigReader interface {
ReadConfig(Config, bool) (interface{}, error)
ReadConfig(Config, bool) (any, error)
}
type bootstrapService struct {
+2 -2
View File
@@ -46,7 +46,7 @@ var (
channel = bootstrap.Channel{
ID: testsutil.GenerateUUID(&testing.T{}),
Name: "name",
Metadata: map[string]interface{}{"name": "value"},
Metadata: map[string]any{"name": "value"},
}
config = bootstrap.Config{
@@ -956,7 +956,7 @@ func TestUpdateChannelHandler(t *testing.T) {
ch := bootstrap.Channel{
ID: channel.ID,
Name: "new name",
Metadata: map[string]interface{}{"meta": "new"},
Metadata: map[string]any{"meta": "new"},
}
cases := []struct {
+1 -1
View File
@@ -8,5 +8,5 @@
// SuperMQ Users service.
//
// For more details about tracing instrumentation for SuperMQ messaging refer
// to the documentation at https://docs.supermq.abstractmachines.fr/tracing/.
// to the documentation at https://docs.supermq.absmach.eu/tracing/.
package tracing
+1 -1
View File
@@ -256,7 +256,7 @@ func setConfigValue(key, value string) error {
}
}
configKeyToField := map[string]interface{}{
configKeyToField := map[string]any{
"channels_url": &config.Remotes.ChannelsURL,
"clients_url": &config.Remotes.ClientsURL,
"groups_url": &config.Remotes.GroupsURL,
+1 -1
View File
@@ -43,7 +43,7 @@ var (
LastName string = ""
)
func logJSONCmd(cmd cobra.Command, iList ...interface{}) {
func logJSONCmd(cmd cobra.Command, iList ...any) {
for _, i := range iList {
m, err := json.Marshal(i)
if err != nil {
+79 -8
View File
@@ -15,16 +15,23 @@ import (
"github.com/absmach/magistrala/alarms/brokers"
"github.com/absmach/magistrala/alarms/consumer"
"github.com/absmach/magistrala/alarms/middleware"
"github.com/absmach/magistrala/alarms/operations"
alarmsRepo "github.com/absmach/magistrala/alarms/postgres"
"github.com/absmach/magistrala/pkg/prometheus"
rconsumer "github.com/absmach/magistrala/pkg/re/events/consumer"
rpostgres "github.com/absmach/magistrala/re/postgres"
dpostgres "github.com/absmach/supermq/domains/postgres"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/authn/authsvc"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
"github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/messaging"
brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/postgres"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
@@ -41,14 +48,18 @@ const (
defDB = "alarms"
defSvcHTTPPort = "8050"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
alarmEntity = "alarm"
)
type config struct {
LogLevel string `env:"MG_ALARMS_LOG_LEVEL" envDefault:"info"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
InstanceID string `env:"MG_ALARMS_INSTANCE_ID" envDefault:""`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
LogLevel string `env:"MG_ALARMS_LOG_LEVEL" envDefault:"info"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
InstanceID string `env:"MG_ALARMS_INSTANCE_ID" envDefault:""`
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
ESConsumerName string `env:"MG_ALARMS_EVENT_CONSUMER" envDefault:"alarms"`
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
}
func main() {
@@ -86,7 +97,14 @@ func main() {
logger.Error(err.Error())
}
db, err := postgres.Setup(dbConfig, *alarmsRepo.Migration())
migrations, err := alarmsRepo.Migration()
if err != nil {
logger.Error(fmt.Sprintf("failed to load migrations: %s", err))
exitCode = 1
return
}
db, err := postgres.Setup(dbConfig, *migrations)
if err != nil {
logger.Error(err.Error())
exitCode = 1
@@ -108,6 +126,7 @@ func main() {
exitCode = 1
return
}
am := smqauthn.NewAuthNMiddleware(authn)
defer authnClient.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
@@ -136,11 +155,63 @@ func main() {
logger.Info("AuthZ successfully connected to auth gRPC server " + authzHandler.Secure())
ddatabase := postgres.NewDatabase(db, dbConfig, tracer)
drepo := dpostgres.NewRepository(ddatabase)
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
exitCode = 1
return
}
rdatabase := postgres.NewDatabase(db, dbConfig, tracer)
rrepo := rpostgres.NewRepository(rdatabase)
if err := rconsumer.RulesEventsSubscribe(ctx, rrepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
logger.Error(fmt.Sprintf("failed to subscribe to rules events: %s", err))
exitCode = 1
return
}
idp := uuid.New()
svc := alarms.NewService(idp, repo)
svc = middleware.NewAuthorizationMiddleware(svc, authz)
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
if err != nil {
logger.Error(fmt.Sprintf("failed to parse permissions file: %s", err))
exitCode = 1
return
}
alarmOps, _, err := permConfig.GetEntityPermissions(alarmEntity)
if err != nil {
logger.Error(fmt.Sprintf("failed to get alarm permissions: %s", err))
exitCode = 1
return
}
entitiesOps, err := permissions.NewEntitiesOperations(
permissions.EntitiesPermission{
operations.EntityType: alarmOps,
},
permissions.EntitiesOperationDetails[permissions.Operation]{
operations.EntityType: operations.OperationDetails(),
},
)
if err != nil {
logger.Error(fmt.Sprintf("failed to create entity operations: %s", err))
exitCode = 1
return
}
svc, err = middleware.NewAuthorizationMiddleware(svc, authz, entitiesOps)
if err != nil {
logger.Error(fmt.Sprintf("failed to create authorization middleware: %s", err))
exitCode = 1
return
}
svc = middleware.NewLoggingMiddleware(logger, svc)
counter, latency := prometheus.MakeMetrics("alarms", "api")
svc = middleware.NewMetricsMiddleware(counter, latency, svc)
@@ -152,7 +223,7 @@ func main() {
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpAPI.MakeHandler(svc, logger, idp, cfg.InstanceID, authn), logger)
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpAPI.MakeHandler(svc, logger, idp, cfg.InstanceID, am), logger)
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
+3 -1
View File
@@ -22,6 +22,7 @@ import (
"github.com/absmach/magistrala/bootstrap/tracing"
"github.com/absmach/supermq"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc"
smqauthz "github.com/absmach/supermq/pkg/authz"
authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc"
@@ -148,6 +149,7 @@ func main() {
exitCode = 1
return
}
am := smqauthn.NewAuthNMiddleware(authn)
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
defer authnClient.Close()
@@ -196,7 +198,7 @@ func main() {
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, bootstrap.NewConfigReader([]byte(cfg.EncKey)), logger, cfg.InstanceID), logger)
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, am, bootstrap.NewConfigReader([]byte(cfg.EncKey)), logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, supermq.Version, logger, cancel)
+15 -15
View File
@@ -7,7 +7,7 @@ package main
import (
"log"
"github.com/absmach/magistrala/cli"
certscli "github.com/absmach/certs/cli"
mgcli "github.com/absmach/magistrala/cli"
mgsdk "github.com/absmach/magistrala/pkg/sdk"
smqcli "github.com/absmach/supermq/cli"
@@ -57,7 +57,7 @@ func main() {
groupsCmd := smqcli.NewGroupsCmd()
channelsCmd := smqcli.NewChannelsCmd()
messagesCmd := smqcli.NewMessagesCmd()
certsCmd := smqcli.NewCertsCmd()
certsCmd := certscli.NewCertsCmd()
configCmd := smqcli.NewConfigCmd()
invitationsCmd := smqcli.NewInvitationsCmd()
journalCmd := smqcli.NewJournalCmd()
@@ -173,18 +173,18 @@ func main() {
)
rootCmd.PersistentFlags().StringVarP(
&cli.ConfigPath,
&mgcli.ConfigPath,
"config",
"c",
cli.ConfigPath,
mgcli.ConfigPath,
"Config path",
)
rootCmd.PersistentFlags().BoolVarP(
&cli.RawOutput,
&mgcli.RawOutput,
"raw",
"r",
cli.RawOutput,
mgcli.RawOutput,
"Enables raw output mode for easier parsing of output",
)
rootCmd.PersistentFlags().BoolVarP(
@@ -197,7 +197,7 @@ func main() {
// Client and Channels Flags
rootCmd.PersistentFlags().Uint64VarP(
&cli.Limit,
&mgcli.Limit,
"limit",
"l",
10,
@@ -205,7 +205,7 @@ func main() {
)
rootCmd.PersistentFlags().Uint64VarP(
&cli.Offset,
&mgcli.Offset,
"offset",
"o",
0,
@@ -213,7 +213,7 @@ func main() {
)
rootCmd.PersistentFlags().StringVarP(
&cli.Name,
&mgcli.Name,
"name",
"n",
"",
@@ -221,7 +221,7 @@ func main() {
)
rootCmd.PersistentFlags().StringVarP(
&cli.Identity,
&mgcli.Identity,
"identity",
"I",
"",
@@ -229,7 +229,7 @@ func main() {
)
rootCmd.PersistentFlags().StringVarP(
&cli.Metadata,
&mgcli.Metadata,
"metadata",
"m",
"",
@@ -237,7 +237,7 @@ func main() {
)
rootCmd.PersistentFlags().StringVarP(
&cli.Status,
&mgcli.Status,
"status",
"S",
"",
@@ -245,7 +245,7 @@ func main() {
)
rootCmd.PersistentFlags().StringVarP(
&cli.State,
&mgcli.State,
"state",
"z",
"",
@@ -253,7 +253,7 @@ func main() {
)
rootCmd.PersistentFlags().StringVarP(
&cli.Topic,
&mgcli.Topic,
"topic",
"T",
"",
@@ -261,7 +261,7 @@ func main() {
)
rootCmd.PersistentFlags().StringVarP(
&cli.Contact,
&mgcli.Contact,
"contact",
"C",
"",
+44 -13
View File
@@ -13,14 +13,19 @@ import (
"reflect"
chclient "github.com/absmach/callhome/pkg/client"
csdk "github.com/absmach/certs/sdk"
mgsdk "github.com/absmach/magistrala/pkg/sdk"
"github.com/absmach/magistrala/provision"
httpapi "github.com/absmach/magistrala/provision/api"
"github.com/absmach/magistrala/provision/middleware"
"github.com/absmach/supermq"
"github.com/absmach/supermq/channels"
"github.com/absmach/supermq/clients"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/grpcclient"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
"github.com/absmach/supermq/pkg/uuid"
@@ -29,8 +34,9 @@ import (
)
const (
svcName = "provision"
contentType = "application/json"
svcName = "provision"
contentType = "application/json"
envPrefixAuth = "SMQ_AUTH_GRPC_"
)
var (
@@ -64,6 +70,24 @@ func main() {
}
}
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())
am := smqauthn.NewAuthNMiddleware(authn)
if cfgFromFile, err := loadConfigFromFile(cfg.File); err != nil {
logger.Warn(fmt.Sprintf("Continue with settings from env, failed to load from: %s: %s", cfg.File, err))
} else {
@@ -75,19 +99,26 @@ func main() {
SDKCfg := mgsdk.Config{
UsersURL: cfg.Server.UsersURL,
ChannelsURL: cfg.Server.ChannelsURL,
ClientsURL: cfg.Server.ClientsURL,
BootstrapURL: cfg.Server.MgBSURL,
CertsURL: cfg.Server.MgCertsURL,
CertsURL: cfg.Server.CertsURL,
MsgContentType: contentType,
TLSVerification: cfg.Server.TLS,
}
SDK := mgsdk.NewSDK(SDKCfg)
mgSdk := mgsdk.NewSDK(SDKCfg)
svc := provision.New(cfg, SDK, logger)
svc = httpapi.NewLoggingMiddleware(svc, logger)
csdkConf := csdk.Config{
CertsURL: cfg.Server.CertsURL,
}
httpServerConfig := server.Config{Host: "", Port: cfg.Server.HTTPPort, KeyFile: cfg.Server.ServerKey, CertFile: cfg.Server.ServerCert}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, logger, cfg.InstanceID), logger)
cSdk := csdk.NewSDK(csdkConf)
svc := provision.New(cfg, mgSdk, cSdk, logger)
svc = middleware.NewLogging(svc, logger)
httpServerConfig := server.Config{Host: "", Port: cfg.Server.Port, KeyFile: cfg.Server.ServerKey, CertFile: cfg.Server.ServerCert}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, am, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, supermq.Version, logger, cancel)
@@ -129,7 +160,7 @@ func loadConfig() (provision.Config, error) {
return provision.Config{}, errors.New("Can't auto whitelist if auto config save is off")
}
var content map[string]interface{}
var content map[string]any
if cfg.BSContent != "" {
if err := json.Unmarshal([]byte(cfg.BSContent), &content); err != nil {
return provision.Config{}, errFailedToReadBootstrapContent
@@ -141,23 +172,23 @@ func loadConfig() (provision.Config, error) {
cfg.Channels = []channels.Channel{
{
Name: "control-channel",
Metadata: map[string]interface{}{"type": "control"},
Metadata: map[string]any{"type": "control"},
}, {
Name: "data-channel",
Metadata: map[string]interface{}{"type": "data"},
Metadata: map[string]any{"type": "data"},
},
}
cfg.Clients = []clients.Client{
{
Name: "client",
Metadata: map[string]interface{}{"external_id": "xxxxxx"},
Metadata: map[string]any{"external_id": "xxxxxx"},
},
}
return cfg, nil
}
func mergeConfigs(dst, src interface{}) interface{} {
func mergeConfigs(dst, src any) any {
d := reflect.ValueOf(dst).Elem()
s := reflect.ValueOf(src).Elem()
+153 -15
View File
@@ -20,36 +20,54 @@ import (
"github.com/absmach/magistrala/internal/email"
"github.com/absmach/magistrala/pkg/emailer"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/ticker"
"github.com/absmach/magistrala/re"
httpapi "github.com/absmach/magistrala/re/api"
"github.com/absmach/magistrala/re/events"
"github.com/absmach/magistrala/re/middleware"
"github.com/absmach/magistrala/re/operations"
repg "github.com/absmach/magistrala/re/postgres"
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
"github.com/absmach/supermq"
dpostgres "github.com/absmach/supermq/domains/postgres"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
mgauthz "github.com/absmach/supermq/pkg/authz"
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
"github.com/absmach/supermq/pkg/callout"
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/messaging"
smqbrokers "github.com/absmach/supermq/pkg/messaging/brokers"
brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
"github.com/absmach/supermq/pkg/policies/spicedb"
pgclient "github.com/absmach/supermq/pkg/postgres"
"github.com/absmach/supermq/pkg/roles"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
spicedbdecoder "github.com/absmach/supermq/pkg/spicedb"
"github.com/absmach/supermq/pkg/uuid"
"github.com/authzed/authzed-go/v1"
"github.com/authzed/grpcutil"
"github.com/caarlos0/env/v11"
"github.com/go-chi/chi/v5"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
svcName = "rules_engine"
envPrefixDB = "MG_RE_DB_"
envPrefixHTTP = "MG_RE_HTTP_"
envPrefixCallout = "MG_RE_CALLOUT_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
defDB = "r"
defSvcHTTPPort = "9008"
@@ -63,15 +81,21 @@ const (
const channBuffer = 256
type config struct {
LogLevel string `env:"MG_RE_LOG_LEVEL" envDefault:"info"`
InstanceID string `env:"MG_RE_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"`
CacheURL string `env:"MG_RE_CACHE_URL" envDefault:"redis://localhost:6379/0"`
CacheKeyDuration time.Duration `env:"MG_RE_CACHE_KEY_DURATION" envDefault:"10m"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
LogLevel string `env:"MG_RE_LOG_LEVEL" envDefault:"info"`
InstanceID string `env:"MG_RE_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"`
ESConsumerName string `env:"MG_RE_EVENT_CONSUMER" envDefault:"rules_engine"`
CacheURL string `env:"MG_RE_CACHE_URL" envDefault:"redis://localhost:6379/0"`
CacheKeyDuration time.Duration `env:"MG_RE_CACHE_KEY_DURATION" envDefault:"10m"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
}
func main() {
@@ -108,6 +132,13 @@ func main() {
return
}
callCfg := callout.Config{}
if err := env.ParseWithOptions(&callCfg, env.Options{Prefix: envPrefixCallout}); err != nil {
logger.Error(fmt.Sprintf("failed to parse callout config : %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())
@@ -115,7 +146,14 @@ func main() {
return
}
db, err := pgclient.Setup(dbConfig, *repg.Migration())
migration, err := repg.Migration()
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
db, err := pgclient.Setup(dbConfig, *migration)
if err != nil {
logger.Error(err.Error())
exitCode = 1
@@ -146,6 +184,13 @@ func main() {
return
}
callout, err := callout.New(callCfg)
if err != nil {
logger.Error(fmt.Sprintf("failed to create new callout: %s", err))
exitCode = 1
return
}
msgSub, err := smqbrokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker for mg pubSub: %s", err))
@@ -190,6 +235,8 @@ func main() {
return
}
am := smqauthn.NewAuthNMiddleware(authn)
defer authnClient.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
runInfo := make(chan pkglog.RunInfo, channBuffer)
@@ -218,6 +265,16 @@ func main() {
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
database := pgclient.NewDatabase(db, dbConfig, tracer)
ddatabase := pgclient.NewDatabase(db, dbConfig, tracer)
drepo := dpostgres.NewRepository(ddatabase)
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
exitCode = 1
return
}
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))
@@ -235,7 +292,7 @@ func main() {
readersClient := grpcClient.NewReadersClient(client.Connection(), regrpcCfg.Timeout)
logger.Info("Readers gRPC client successfully connected to readers gRPC server " + client.Secure())
svc, err := newService(database, runInfo, msgSub, writersPub, alarmsPub, authz, ec, logger, readersClient)
svc, err := newService(ctx, cfg, database, runInfo, msgSub, writersPub, alarmsPub, authz, ec, logger, readersClient, callout, tracer)
if err != nil {
logger.Error(fmt.Sprintf("failed to create services: %s", err))
exitCode = 1
@@ -263,7 +320,7 @@ func main() {
mux := chi.NewRouter()
httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, mux, logger, cfg.InstanceID), logger)
httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, am, mux, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, supermq.Version, logger, cancel)
@@ -287,7 +344,7 @@ func main() {
}
}
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) {
func newService(ctx context.Context, cfg config, 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, callout callout.Callout, tracer trace.Tracer) (re.Service, error) {
repo := repg.NewRepository(db)
idp := uuid.New()
@@ -296,12 +353,93 @@ func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, rePubSub mess
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
}
csvc := re.NewService(repo, runInfo, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient)
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger)
if err != nil {
return nil, err
}
logger.Info("Policy service successfully connected to SpiceDB gRPC server")
availableActions, builtInRoles, err := availableActionsAndBuiltInRoles(cfg.SpicedbSchemaFile)
if err != nil {
return nil, fmt.Errorf("failed to get available actions and built-in roles: %w", err)
}
csvc, err := re.NewService(repo, runInfo, policyService, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient, availableActions, builtInRoles)
if err != nil {
return nil, fmt.Errorf("failed to create RE service: %w", err)
}
csvc, err = events.NewEventStoreMiddleware(ctx, csvc, cfg.ESURL)
if err != nil {
return nil, fmt.Errorf("failed to init re event store middleware: %w", err)
}
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
if err != nil {
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
}
ruleOps, ruleRoleOps, err := permConfig.GetEntityPermissions(operations.EntityType)
if err != nil {
return nil, fmt.Errorf("failed to get rule permissions: %w", err)
}
entitiesOps, err := permissions.NewEntitiesOperations(
permissions.EntitiesPermission{
operations.EntityType: ruleOps,
},
permissions.EntitiesOperationDetails[permissions.Operation]{
operations.EntityType: operations.OperationDetails(),
},
)
if err != nil {
return nil, fmt.Errorf("failed to create entities operations: %w", err)
}
roleOps, err := permissions.NewOperations(roles.Operations(), ruleRoleOps)
if err != nil {
return nil, fmt.Errorf("failed to create role operations: %w", err)
}
csvc, err = middleware.AuthorizationMiddleware(csvc, authz, entitiesOps, roleOps)
if err != nil {
return nil, err
}
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps, roleOps)
if err != nil {
return nil, err
}
csvc = middleware.LoggingMiddleware(csvc, logger)
counter, latency := prometheus.MakeMetrics("re", "api")
csvc = middleware.NewMetricsMiddleware(counter, latency, csvc)
csvc = middleware.NewTracingMiddleware(tracer, csvc)
return csvc, nil
}
func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Service, error) {
client, err := authzed.NewClientWithExperimentalAPIs(
fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey),
)
if err != nil {
return nil, err
}
ps := spicedb.NewPolicyService(client, logger)
return ps, nil
}
func availableActionsAndBuiltInRoles(spicedbSchemaFile string) ([]roles.Action, map[roles.BuiltInRoleName][]roles.Action, error) {
availableActions, err := spicedbdecoder.GetActionsFromSchema(spicedbSchemaFile, operations.EntityType)
if err != nil {
return []roles.Action{}, map[roles.BuiltInRoleName][]roles.Action{}, err
}
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
re.BuiltInRoleAdmin: availableActions,
}
return availableActions, builtInRoles, err
}
+139 -7
View File
@@ -19,39 +19,57 @@ import (
"github.com/absmach/magistrala/internal/email"
"github.com/absmach/magistrala/pkg/emailer"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/prometheus"
"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"
"github.com/absmach/magistrala/reports/operations"
repg "github.com/absmach/magistrala/reports/postgres"
"github.com/absmach/supermq"
dpostgres "github.com/absmach/supermq/domains/postgres"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
mgauthz "github.com/absmach/supermq/pkg/authz"
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
"github.com/absmach/supermq/pkg/callout"
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
"github.com/absmach/supermq/pkg/grpcclient"
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/permissions"
"github.com/absmach/supermq/pkg/policies"
"github.com/absmach/supermq/pkg/policies/spicedb"
pgclient "github.com/absmach/supermq/pkg/postgres"
"github.com/absmach/supermq/pkg/roles"
"github.com/absmach/supermq/pkg/server"
httpserver "github.com/absmach/supermq/pkg/server/http"
spicedbdecoder "github.com/absmach/supermq/pkg/spicedb"
"github.com/absmach/supermq/pkg/uuid"
"github.com/authzed/authzed-go/v1"
"github.com/authzed/grpcutil"
"github.com/caarlos0/env/v11"
"github.com/go-chi/chi/v5"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
svcName = "reports"
envPrefixDB = "MG_REPORTS_DB_"
envPrefixHTTP = "MG_REPORTS_HTTP_"
envPrefixCallout = "MG_REPORTS_CALLOUT_"
envPrefixAuth = "SMQ_AUTH_GRPC_"
defDB = "repo"
defSvcHTTPPort = "9017"
envPrefixGrpc = "MG_TIMESCALE_READER_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
templatePath = "template/reports_default_template.html"
reportEntity = "report"
)
// We use a buffered channel to prevent blocking, as logging is an expensive operation.
@@ -66,10 +84,16 @@ type config struct {
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"`
ESConsumerName string `env:"MG_REPORTS_EVENT_CONSUMER" envDefault:"reports"`
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
DefaultTemplatePath string `env:"MG_REPORTS_DEFAULT_TEMPLATE" envDefault:""`
ConverterURL string `env:"MG_PDF_CONVERTER_URL" envDefault:"http://localhost:4000/pdf"`
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
}
func main() {
@@ -130,6 +154,13 @@ func main() {
return
}
callCfg := callout.Config{}
if err := env.ParseWithOptions(&callCfg, env.Options{Prefix: envPrefixCallout}); err != nil {
logger.Error(fmt.Sprintf("failed to parse callout config : %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())
@@ -138,7 +169,15 @@ func main() {
return
}
db, err := pgclient.Setup(dbConfig, *repg.Migration())
migration, err := repg.Migration()
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
db, err := pgclient.Setup(dbConfig, *migration)
if err != nil {
logger.Error(err.Error())
exitCode = 1
@@ -169,6 +208,13 @@ func main() {
return
}
callout, err := callout.New(callCfg)
if err != nil {
logger.Error(fmt.Sprintf("failed to create new callout: %s", 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))
@@ -183,6 +229,7 @@ func main() {
return
}
am := smqauthn.NewAuthNMiddleware(authn)
defer authnClient.Close()
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
@@ -209,6 +256,15 @@ func main() {
defer authzClient.Close()
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
ddatabase := pgclient.NewDatabase(db, dbConfig, tracer)
drepo := dpostgres.NewRepository(ddatabase)
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
exitCode = 1
return
}
database := pgclient.NewDatabase(db, dbConfig, tracer)
regrpcCfg := grpcclient.Config{}
if err := env.ParseWithOptions(&regrpcCfg, env.Options{Prefix: envPrefixGrpc}); err != nil {
@@ -229,7 +285,7 @@ func main() {
runInfo := make(chan pkglog.RunInfo, channBuffer)
svc, err := newService(database, runInfo, authz, ec, logger, readersClient, template, cfg.ConverterURL)
svc, err := newService(cfg, database, runInfo, authz, ec, logger, readersClient, template, callout, tracer)
if err != nil {
logger.Error(fmt.Sprintf("failed to create services: %s", err))
exitCode = 1
@@ -245,7 +301,7 @@ func main() {
mux := chi.NewRouter()
httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, mux, logger, cfg.InstanceID), logger)
httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, am, mux, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, supermq.Version, logger, cancel)
@@ -269,21 +325,97 @@ func main() {
}
}
func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, template reports.ReportTemplate, converterURL string) (reports.Service, error) {
func newService(cfg config, db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, template reports.ReportTemplate, callout callout.Callout, tracer trace.Tracer) (reports.Service, error) {
repo := repg.NewRepository(db)
idp := uuid.New()
emailerClient, err := emailer.New(&ec)
emailClient, 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, template, converterURL)
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger)
if err != nil {
return nil, err
}
logger.Info("Policy service successfully connected to SpiceDB gRPC server")
availableActions, builtInRoles, err := availableActionsAndBuiltInRoles(cfg.SpicedbSchemaFile)
if err != nil {
return nil, fmt.Errorf("failed to get available actions and built-in roles: %w", err)
}
csvc, err := reports.NewService(repo, runInfo, policyService, idp, ticker.NewTicker(time.Second*30), emailClient, readersClient, template, cfg.ConverterURL, availableActions, builtInRoles)
if err != nil {
return nil, fmt.Errorf("failed to create reports service: %w", err)
}
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
if err != nil {
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
}
reportOps, reportRoleOps, err := permConfig.GetEntityPermissions(reportEntity)
if err != nil {
return nil, fmt.Errorf("failed to get report permissions: %w", err)
}
entitiesOps, err := permissions.NewEntitiesOperations(
permissions.EntitiesPermission{
operations.EntityType: reportOps,
},
permissions.EntitiesOperationDetails[permissions.Operation]{
operations.EntityType: operations.OperationDetails(),
},
)
if err != nil {
return nil, fmt.Errorf("failed to create entities operations: %w", err)
}
roleOps, err := permissions.NewOperations(roles.Operations(), reportRoleOps)
if err != nil {
return nil, fmt.Errorf("failed to create role operations: %w", err)
}
csvc, err = middleware.AuthorizationMiddleware(csvc, authz, entitiesOps, roleOps)
if err != nil {
return nil, err
}
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps, roleOps)
if err != nil {
return nil, err
}
csvc = middleware.LoggingMiddleware(csvc, logger)
counter, latency := prometheus.MakeMetrics("reports", "api")
csvc = middleware.NewMetricsMiddleware(counter, latency, csvc)
csvc = middleware.NewTracingMiddleware(tracer, csvc)
return csvc, nil
}
func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Service, error) {
client, err := authzed.NewClientWithExperimentalAPIs(
fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey),
)
if err != nil {
return nil, err
}
ps := spicedb.NewPolicyService(client, logger)
return ps, nil
}
func availableActionsAndBuiltInRoles(spicedbSchemaFile string) ([]roles.Action, map[roles.BuiltInRoleName][]roles.Action, error) {
availableActions, err := spicedbdecoder.GetActionsFromSchema(spicedbSchemaFile, reportEntity)
if err != nil {
return []roles.Action{}, map[roles.BuiltInRoleName][]roles.Action{}, err
}
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
reports.BuiltInRoleAdmin: availableActions,
}
return availableActions, builtInRoles, err
}
@@ -17,10 +17,6 @@
--text-primary: rgb(44, 62, 80);
--text-secondary: rgb(127, 140, 141);
--white: #ffffff;
--header-height: 35mm;
--footer-height: 20mm;
--page-padding: 15mm;
}
* {
@@ -37,40 +33,38 @@
}
.page {
max-width: 210mm;
min-height: 297mm;
padding: var(--page-padding) 10mm;
margin: 5mm auto 0 auto;
width: 210mm;
height: 297mm;
padding: 15mm 10mm;
margin: 0 auto;
background: var(--white);
box-shadow: 0 0 10px rgba(0,0,0,0.1);
position: relative;
page-break-after: always;
display: flex;
flex-direction: column;
}
.page:last-child {
page-break-after: auto;
}
.header {
height: var(--header-height);
min-height: var(--header-height);
max-height: var(--header-height);
position: relative;
flex-shrink: 0;
display: flex;
flex-direction: column;
margin-bottom: 8mm;
}
.header-top-bar {
height: 8px;
background-color: var(--primary-color);
margin: 5px 0 10px 0;
flex-shrink: 0;
margin: 0 0 8px 0;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
flex-shrink: 0;
margin-bottom: 8px;
}
.header-title {
@@ -92,9 +86,8 @@
.header-separator {
height: 2px;
background-color: var(--subtle-color);
margin: 5px 0 10px 0;
margin: 5px 0;
position: relative;
flex-shrink: 0;
}
.header-separator::after {
@@ -111,32 +104,35 @@
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.metrics-section {
margin-bottom: 15px;
flex-shrink: 0;
margin-bottom: 10px;
}
.metrics-section.continuation {
display: none;
}
.metrics-title {
font-size: 16px;
font-weight: bold;
color: var(--secondary-color);
margin-bottom: 10px;
margin-bottom: 8px;
}
.metrics-info {
background-color: var(--alternate-row);
padding: 12px;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
margin-bottom: 8px;
}
.metric-row {
display: flex;
margin-bottom: 8px;
margin-bottom: 6px;
}
.metric-row:last-child {
@@ -162,14 +158,13 @@
font-size: 10px;
font-style: italic;
color: var(--text-secondary);
margin-bottom: 10px;
margin-bottom: 8px;
flex-shrink: 0;
}
.table-container {
flex-grow: 1;
overflow: auto;
min-height: 0;
overflow: hidden;
}
.data-table {
@@ -190,8 +185,6 @@
padding: 8px;
text-align: center;
border-bottom: 2px solid var(--subtle-color);
position: sticky;
top: 0;
}
.data-table td {
@@ -237,23 +230,17 @@
}
.footer {
height: var(--footer-height);
min-height: var(--footer-height);
max-height: var(--footer-height);
border-top: 2px solid var(--subtle-color);
padding-top: 8px;
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: flex-start;
border-top: 2px solid var(--subtle-color);
padding-top: 6px;
margin-top: 8mm;
}
.footer-separator {
height: 1px;
background-color: var(--subtle-color);
margin-bottom: 6px;
margin-bottom: 4px;
position: relative;
flex-shrink: 0;
}
.footer-separator::after {
@@ -270,55 +257,170 @@
display: flex;
justify-content: space-between;
align-items: center;
margin: 0;
padding: 0;
flex-shrink: 0;
}
.footer-generated {
font-size: 8px;
font-style: italic;
color: var(--text-secondary);
margin: 0;
padding: 0;
}
.footer-page {
font-size: 9px;
font-weight: bold;
color: var(--text-primary);
margin: 0;
padding: 0;
}
@media print {
.page {
box-shadow: none;
@page {
size: A4;
margin: 0;
max-width: none;
height: 297mm;
min-height: auto;
page-break-after: always;
}
.page:last-child {
page-break-after: auto;
html, body {
width: 210mm;
height: 297mm;
margin: 0;
padding: 0;
}
.page {
margin: 0;
box-shadow: none;
}
}
</style>
</head>
<body>
{{$totalPages := len .Reports}}
{{if gt (len .Reports) 0}}
{{$firstPageRows := 24}}
{{$continuationPageRows := 32}}
{{$totalPages := 0}}
{{/* Calculate total pages across all reports */}}
{{range $report := .Reports}}
{{$totalMessages := len .Messages}}
{{$reportPages := 1}}
{{if gt $totalMessages $firstPageRows}}
{{$remaining := sub $totalMessages $firstPageRows}}
{{$additionalPages := div $remaining $continuationPageRows}}
{{if gt (mod $remaining $continuationPageRows) 0}}
{{$additionalPages = add $additionalPages 1}}
{{end}}
{{$reportPages = add 1 $additionalPages}}
{{end}}
{{$totalPages = add $totalPages $reportPages}}
{{end}}
{{$globalPage := 0}}
{{range $index, $report := .Reports}}
{{range $reportIndex, $report := .Reports}}
{{$totalMessages := len .Messages}}
{{$pageCount := 1}}
{{if gt $totalMessages $firstPageRows}}
{{$remaining := sub $totalMessages $firstPageRows}}
{{$additionalPages := div $remaining $continuationPageRows}}
{{if gt (mod $remaining $continuationPageRows) 0}}
{{$additionalPages = add $additionalPages 1}}
{{end}}
{{$pageCount = add 1 $additionalPages}}
{{end}}
{{range $pageNum := iterate $pageCount}}
{{$globalPage = add $globalPage 1}}
{{$isFirstPage := eq $pageNum 0}}
{{$startRow := getStartRow $pageNum $firstPageRows $continuationPageRows}}
{{$endRow := getEndRow $pageNum $firstPageRows $continuationPageRows $totalMessages}}
<div class="page">
<div class="header">
<div class="header-top-bar"></div>
<div class="header-content">
<div style="width: 100px;"></div>
<div class="header-title">{{$.Title}}</div>
<div class="header-date">{{$.GeneratedDate}}</div>
<div class="header-date">{{$.GeneratedDate}}{{if $.Timezone}} ({{$.Timezone}}){{end}}</div>
</div>
<div class="header-separator"></div>
</div>
<div class="content-area">
{{if $isFirstPage}}
<div class="metrics-section">
<div class="metrics-title">Metrics</div>
<div class="metrics-info">
<div class="metric-row">
<div class="metric-label">Name:</div>
<div class="metric-value">{{$report.Metric.Name}}</div>
</div>
{{if $report.Metric.ClientID}}
<div class="metric-row">
<div class="metric-label">Device ID:</div>
<div class="metric-value">{{$report.Metric.ClientID}}</div>
</div>
{{end}}
<div class="metric-row">
<div class="metric-label">Channel ID:</div>
<div class="metric-value">{{$report.Metric.ChannelID}}</div>
</div>
</div>
</div>
<div class="record-count">
Total Records: {{$totalMessages}}
</div>
{{else}}
<div class="metrics-section continuation">
<div class="metrics-title">Metrics (continued)</div>
</div>
{{end}}
<div class="table-container">
<div class="table-header-bar"></div>
<table class="data-table">
<thead>
<tr>
<th class="col-time">Time</th>
<th class="col-value">Value</th>
<th class="col-unit">Unit</th>
<th class="col-protocol">Protocol</th>
<th class="col-subtopic">Subtopic</th>
</tr>
</thead>
<tbody>
{{range $msgIndex, $msg := $report.Messages}}
{{if and (ge $msgIndex $startRow) (lt $msgIndex $endRow)}}
<tr>
<td class="col-time">{{formatTime $msg.Time}}</td>
<td class="col-value">{{formatValue $msg}}</td>
<td class="col-unit">{{$msg.Unit}}</td>
<td class="col-protocol">{{$msg.Protocol}}</td>
<td class="col-subtopic">{{$msg.Subtopic}}</td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
</div>
</div>
<div class="footer">
<div class="footer-separator"></div>
<div class="footer-content">
<div class="footer-generated">Generated: {{$.GeneratedTime}}{{if $.Timezone}} ({{$.Timezone}}){{end}}</div>
<div class="footer-page">Page {{$globalPage}} of {{$totalPages}}</div>
</div>
</div>
</div>
{{end}}
{{end}}
{{else}}
<div class="page">
<div class="header">
<div class="header-top-bar"></div>
<div class="header-content">
<div style="width: 100px;"></div>
<div class="header-title">{{.Title}}</div>
<div class="header-date">{{.GeneratedDate}}{{if .Timezone}} ({{.Timezone}}){{end}}</div>
</div>
<div class="header-separator"></div>
</div>
@@ -329,23 +431,17 @@
<div class="metrics-info">
<div class="metric-row">
<div class="metric-label">Name:</div>
<div class="metric-value">{{.Metric.Name}}</div>
<div class="metric-value">No Report</div>
</div>
{{if .Metric.ClientID}}
<div class="metric-row">
<div class="metric-label">Device ID:</div>
<div class="metric-value">{{.Metric.ClientID}}</div>
</div>
{{end}}
<div class="metric-row">
<div class="metric-label">Channel ID:</div>
<div class="metric-value">{{.Metric.ChannelID}}</div>
<div class="metric-value">N/A</div>
</div>
</div>
</div>
<div class="record-count">
Total Records: {{len .Messages}}
Total Records: 0
</div>
<div class="table-container">
@@ -361,15 +457,9 @@
</tr>
</thead>
<tbody>
{{range .Messages}}
<tr>
<td class="col-time">{{formatTime .Time}}</td>
<td class="col-value">{{formatValue .}}</td>
<td class="col-unit">{{.Unit}}</td>
<td class="col-protocol">{{.Protocol}}</td>
<td class="col-subtopic">{{.Subtopic}}</td>
<td colspan="5" style="text-align: center; font-style: italic; color: #888;">No data available</td>
</tr>
{{end}}
</tbody>
</table>
</div>
@@ -378,8 +468,8 @@
<div class="footer">
<div class="footer-separator"></div>
<div class="footer-content">
<div class="footer-generated">Generated: {{$.GeneratedTime}}</div>
<div class="footer-page">Page {{$globalPage}} of {{$totalPages}}</div>
<div class="footer-generated">Generated: {{.GeneratedTime}}{{if .Timezone}} ({{.Timezone}}){{end}}</div>
<div class="footer-page">Page 1 of 1</div>
</div>
</div>
</div>
+2 -2
View File
@@ -13,6 +13,6 @@ For an in-depth explanation of the usage of `consumers`, as well as thorough
understanding of SuperMQ, please check out the [official documentation][doc].
For more information about service capabilities and its usage, please check out
the [API documentation](https://docs.api.supermq.abstractmachines.fr/?urls.primaryName=consumers-notifiers-openapi.yaml).
the [API documentation](https://docs.api.supermq.absmach.eu/?urls.primaryName=consumers-notifiers-openapi.yaml).
[doc]: https://docs.supermq.abstractmachines.fr
[doc]: https://docs.supermq.absmach.eu
+173 -14
View File
@@ -1,23 +1,182 @@
# Notifiers service
# Notifiers
Notifiers service provides a service for sending notifications using Notifiers.
Notifiers service can be configured to use different types of Notifiers to send
different types of notifications such as SMS messages, emails, or push notifications.
Service is extensible so that new implementations of Notifiers can be easily added.
Notifiers **are not standalone services** but rather dependencies used by Notifiers service
for sending notifications over specific protocols.
The Notifiers service manages notification subscriptions and dispatches alerts for incoming messages. It stores subscription records (topic + contact), exposes an HTTP API for CRUD operations, and consumes SuperMQ messages to fan out notifications via notifier implementations (SMTP for email, SMPP for SMS). Notifiers are dependencies used by the service, not standalone services.
## Configuration
The service is configured using the environment variables.
The environment variables needed for service configuration depend on the underlying Notifier.
An example of the service configuration for SMTP Notifier can be found [in SMTP Notifier documentation](smtp/README.md).
Note that any unset variables will be replaced with their
default values.
The service is configured using environment variables. Values shown are from [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env) when available; otherwise defaults come from code or notifier-specific docs.
### SMTP notifier (email)
Used by `consumers/notifiers/smtp` via `internal/email`.
| Variable | Description | Default |
| --- | --- | --- |
| `MG_EMAIL_HOST` | SMTP host | `smtp.mailtrap.io` |
| `MG_EMAIL_PORT` | SMTP port | `2525` |
| `MG_EMAIL_USERNAME` | SMTP username | `18bf7f70705139` |
| `MG_EMAIL_PASSWORD` | SMTP password | `2b0d302e775b1e` |
| `MG_EMAIL_FROM_ADDRESS` | Default from address (used if `from` is empty) | `from@example.com` |
| `MG_EMAIL_FROM_NAME` | Default from name | `Example` |
| `MG_EMAIL_TEMPLATE` | Email template path | `email.tmpl` |
### SMPP notifier (SMS)
#### SMPP transport settings
Defined in `consumers/notifiers/smpp/config.go`.
| Variable | Description | Default |
| --- | --- | --- |
| `MG_SMPP_ADDRESS` | SMPP address in `host:port` format | "" |
| `MG_SMPP_USERNAME` | SMPP username | "" |
| `MG_SMPP_PASSWORD` | SMPP password | "" |
| `MG_SMPP_SYSTEM_TYPE` | SMPP system type | "" |
| `MG_SMPP_SRC_ADDR_TON` | SMPP source address TON | `0` |
| `MG_SMPP_DST_ADDR_TON` | SMPP source address NPI | `0` |
| `MG_SMPP_SRC_ADDR_NPI` | SMPP destination address TON | `0` |
| `MG_SMPP_DST_ADDR_NPI` | SMPP destination address NPI | `0` |
Note: The SMPP env tags are mapped exactly as defined in `consumers/notifiers/smpp/config.go`.
#### SMPP notifier service settings
Defined in `consumers/notifiers/smpp/README.md`.
| Variable | Description | Default |
| --- | --- | --- |
| `MG_SMPP_NOTIFIER_LOG_LEVEL` | Log level for SMPP notifier | `info` |
| `MG_SMPP_NOTIFIER_FROM_ADDRESS` | From address for SMS notifications | "" |
| `MG_SMPP_NOTIFIER_CONFIG_PATH` | Config file path for message broker subjects and payload type | `/config.toml` |
| `MG_SMPP_NOTIFIER_HTTP_HOST` | Service HTTP host | `localhost` |
| `MG_SMPP_NOTIFIER_HTTP_PORT` | Service HTTP port | `9014` |
| `MG_SMPP_NOTIFIER_HTTP_SERVER_CERT` | Service HTTP server certificate path | "" |
| `MG_SMPP_NOTIFIER_HTTP_SERVER_KEY` | Service HTTP server key path | "" |
| `MG_SMPP_NOTIFIER_DB_HOST` | Database host address | `localhost` |
| `MG_SMPP_NOTIFIER_DB_PORT` | Database host port | `5432` |
| `MG_SMPP_NOTIFIER_DB_USER` | Database user | `magistrala` |
| `MG_SMPP_NOTIFIER_DB_PASS` | Database password | `magistrala` |
| `MG_SMPP_NOTIFIER_DB_NAME` | Database name | `subscriptions` |
| `MG_SMPP_NOTIFIER_DB_SSL_MODE` | DB SSL mode (disable, require, verify-ca, verify-full) | `disable` |
| `MG_SMPP_NOTIFIER_DB_SSL_CERT` | DB SSL client cert path | "" |
| `MG_SMPP_NOTIFIER_DB_SSL_KEY` | DB SSL client key path | "" |
| `MG_SMPP_NOTIFIER_DB_SSL_ROOT_CERT` | DB SSL root cert path | "" |
| `SMQ_AUTH_GRPC_URL` | Auth gRPC URL | `localhost:7001` |
| `SMQ_AUTH_GRPC_TIMEOUT` | Auth gRPC timeout | `1s` |
| `MG_AUTH_GRPC_CLIENT_TLS` | Auth client TLS flag | `false` |
| `MG_AUTH_GRPC_CA_CERT` | Auth client CA certs path | "" |
| `SMQ_MESSAGE_BROKER_URL` | Message broker URL | `nats://127.0.0.1:4222` |
| `SMQ_JAEGER_URL` | Jaeger tracing URL | `http://jaeger:14268/api/traces` |
| `SMQ_SEND_TELEMETRY` | Send telemetry to Magistrala call-home server | `true` |
| `MG_SMPP_NOTIFIER_INSTANCE_ID` | SMPP notifier instance ID | "" |
## Features
- **Subscription management**: Create, view, list, and remove notification subscriptions.
- **Topic-based dispatch**: Matches subscriptions by topic and fan-outs to contacts.
- **Multiple notifier backends**: SMTP (email) and SMPP (SMS) implementations are available.
- **Observability**: Exposes `/metrics` and `/health` endpoints.
- **Uniqueness guardrails**: Prevents duplicate subscriptions for the same topic/contact pair.
## Architecture
### Runtime flow
1. Clients register subscriptions through the HTTP API (`topic` + `contact`).
2. The service authenticates the token, assigns an owner ID, and persists the subscription.
3. When a message arrives, the service builds the topic as `channel` or `channel.subtopic`, retrieves matching subscriptions, and gathers contacts.
4. The notifier implementation sends notifications using the configured backend.
### Components
- **HTTP API**: `consumers/notifiers/api` exposes `/subscriptions`, `/health`, and `/metrics`.
- **Service layer**: `consumers/notifiers/service.go` handles authn, ID creation, and notification dispatch.
- **Repository**: `consumers/notifiers/postgres` persists subscriptions and supports filtering.
- **Notifier implementations**: `consumers/notifiers/smtp` (email) and `consumers/notifiers/smpp` (SMS).
- **Email agent**: `internal/email` manages SMTP connectivity and template rendering.
### Subscriptions table
Defined in `consumers/notifiers/postgres/init.go`:
| Column | Type | Description |
| --- | --- | --- |
| `id` | `VARCHAR(254)` | Subscription identifier (primary key) |
| `owner_id` | `VARCHAR(254)` | Owner ID derived from the auth token |
| `contact` | `VARCHAR(254)` | Notification contact (email or phone) |
| `topic` | `TEXT` | Topic to match (`channel` or `channel.subtopic`) |
Constraint: `UNIQUE(topic, contact)`
## Deployment
The Notifiers service is provided as a consumer package. It is typically wired into a notifier-specific binary that provides the HTTP server and message broker subscription. For the SMPP notifier runtime configuration, see `consumers/notifiers/smpp/README.md`.
### Health check
```bash
curl -X GET http://localhost:9014/health \
-H "accept: application/health+json"
```
## Testing
```bash
go test ./consumers/notifiers/...
```
## Usage
Subscriptions service will start consuming messages and sending notifications when a message is received.
The Notifiers service supports the following operations (see `apidocs/openapi/notifiers.yaml`):
[doc]: https://docs.supermq.abstractmachines.fr
| Operation | Method & Path | Description |
| --- | --- | --- |
| `createSubscription` | `POST /subscriptions` | Create a new subscription |
| `listSubscriptions` | `GET /subscriptions` | List subscriptions with filters |
| `viewSubscription` | `GET /subscriptions/{id}` | Retrieve a subscription |
| `removeSubscription` | `DELETE /subscriptions/{id}` | Delete a subscription |
| `health` | `GET /health` | Service health check |
### Example: Create a subscription
```bash
curl -X POST http://localhost:9014/subscriptions \
-H "Authorization: Bearer <your_access_token>" \
-H "Content-Type: application/json" \
-d '{
"topic": "channel.subtopic",
"contact": "user@example.com"
}'
```
### Example: List subscriptions
```bash
curl -X GET "http://localhost:9014/subscriptions?topic=channel.subtopic&contact=user@example.com&limit=20&offset=0" \
-H "Authorization: Bearer <your_access_token>"
```
### Example: View a subscription
```bash
curl -X GET http://localhost:9014/subscriptions/<subscriptionID> \
-H "Authorization: Bearer <your_access_token>"
```
### Example: Remove a subscription
```bash
curl -X DELETE http://localhost:9014/subscriptions/<subscriptionID> \
-H "Authorization: Bearer <your_access_token>"
```
### Example: Health check
```bash
curl -X GET http://localhost:9014/health \
-H "accept: application/health+json"
```
For an in-depth explanation of the Notifiers, see the [official documentation][doc].
[doc]: https://docs.magistrala.absmach.eu/dev-guide/consumers/#notifiers
+4 -4
View File
@@ -13,7 +13,7 @@ import (
)
func createSubscriptionEndpoint(svc notifiers.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(createSubReq)
if err := req.validate(); err != nil {
return createSubRes{}, errors.Wrap(apiutil.ErrValidation, err)
@@ -35,7 +35,7 @@ func createSubscriptionEndpoint(svc notifiers.Service) endpoint.Endpoint {
}
func viewSubscriptionEndpoint(svc notifiers.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(subReq)
if err := req.validate(); err != nil {
return viewSubRes{}, errors.Wrap(apiutil.ErrValidation, err)
@@ -55,7 +55,7 @@ func viewSubscriptionEndpoint(svc notifiers.Service) endpoint.Endpoint {
}
func listSubscriptionsEndpoint(svc notifiers.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(listSubsReq)
if err := req.validate(); err != nil {
return listSubsRes{}, errors.Wrap(apiutil.ErrValidation, err)
@@ -90,7 +90,7 @@ func listSubscriptionsEndpoint(svc notifiers.Service) endpoint.Endpoint {
}
func deleteSubscriptionEndpoint(svc notifiers.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return func(ctx context.Context, request any) (any, error) {
req := request.(subReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
+6 -6
View File
@@ -38,10 +38,10 @@ const (
)
var (
notFoundRes = toJSON(apiutil.ErrorRes{Msg: svcerr.ErrNotFound.Error()})
unauthRes = toJSON(apiutil.ErrorRes{Msg: svcerr.ErrAuthentication.Error()})
invalidRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrInvalidQueryParams.Error(), Msg: apiutil.ErrValidation.Error()})
missingTokRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrBearerToken.Error(), Msg: apiutil.ErrValidation.Error()})
notFoundRes = toJSON(svcerr.ErrNotFound)
unauthRes = toJSON(svcerr.ErrAuthentication)
invalidRes = toJSON(apiutil.ErrInvalidQueryParams)
missingTokRes = toJSON(apiutil.ErrBearerToken)
)
type testRequest struct {
@@ -74,7 +74,7 @@ func newServer() (*httptest.Server, *mocks.Service) {
return httptest.NewServer(mux), svc
}
func toJSON(data interface{}) string {
func toJSON(data any) string {
jsonData, err := json.Marshal(data)
if err != nil {
return ""
@@ -119,7 +119,7 @@ func TestCreate(t *testing.T) {
req: data,
contentType: contentType,
auth: token,
status: http.StatusConflict,
status: http.StatusBadRequest,
location: "",
err: svcerr.ErrConflict,
},
+1 -1
View File
@@ -114,7 +114,7 @@ func (lm *loggingMiddleware) RemoveSubscription(ctx context.Context, token, id s
// ConsumeBlocking logs the consume_blocking request. It logs the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msg interface{}) (err error) {
func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msg any) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
+1 -1
View File
@@ -71,7 +71,7 @@ func (ms *metricsMiddleware) RemoveSubscription(ctx context.Context, token, id s
}
// ConsumeBlocking instruments ConsumeBlocking method with metrics.
func (ms *metricsMiddleware) ConsumeBlocking(ctx context.Context, msg interface{}) error {
func (ms *metricsMiddleware) ConsumeBlocking(ctx context.Context, msg any) error {
defer func(begin time.Time) {
ms.counter.With("method", "consume").Add(1)
ms.latency.With("method", "consume").Observe(time.Since(begin).Seconds())
+5 -5
View File
@@ -81,20 +81,20 @@ func MakeHandler(svc notifiers.Service, logger *slog.Logger, instanceID string)
return mux
}
func decodeCreate(_ context.Context, r *http.Request) (interface{}, error) {
func decodeCreate(_ context.Context, r *http.Request) (any, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
return nil, apiutil.ErrUnsupportedContentType
}
req := createSubReq{token: apiutil.ExtractBearerToken(r)}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
}
return req, nil
}
func decodeSubscription(_ context.Context, r *http.Request) (interface{}, error) {
func decodeSubscription(_ context.Context, r *http.Request) (any, error) {
req := subReq{
id: chi.URLParam(r, "subID"),
token: apiutil.ExtractBearerToken(r),
@@ -103,7 +103,7 @@ func decodeSubscription(_ context.Context, r *http.Request) (interface{}, error)
return req, nil
}
func decodeList(_ context.Context, r *http.Request) (interface{}, error) {
func decodeList(_ context.Context, r *http.Request) (any, error) {
req := listSubsReq{token: apiutil.ExtractBearerToken(r)}
vals := r.URL.Query()[topicKey]
if len(vals) > 0 {
+11 -10
View File
@@ -1,10 +1,11 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
@@ -42,7 +43,7 @@ func (_m *Service) EXPECT() *Service_Expecter {
}
// ConsumeBlocking provides a mock function for the type Service
func (_mock *Service) ConsumeBlocking(ctx context.Context, messages interface{}) error {
func (_mock *Service) ConsumeBlocking(ctx context.Context, messages any) error {
ret := _mock.Called(ctx, messages)
if len(ret) == 0 {
@@ -50,7 +51,7 @@ func (_mock *Service) ConsumeBlocking(ctx context.Context, messages interface{})
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, interface{}) error); ok {
if returnFunc, ok := ret.Get(0).(func(context.Context, any) error); ok {
r0 = returnFunc(ctx, messages)
} else {
r0 = ret.Error(0)
@@ -65,20 +66,20 @@ type Service_ConsumeBlocking_Call struct {
// ConsumeBlocking is a helper method to define mock.On call
// - ctx context.Context
// - messages interface{}
// - messages any
func (_e *Service_Expecter) ConsumeBlocking(ctx interface{}, messages interface{}) *Service_ConsumeBlocking_Call {
return &Service_ConsumeBlocking_Call{Call: _e.mock.On("ConsumeBlocking", ctx, messages)}
}
func (_c *Service_ConsumeBlocking_Call) Run(run func(ctx context.Context, messages interface{})) *Service_ConsumeBlocking_Call {
func (_c *Service_ConsumeBlocking_Call) Run(run func(ctx context.Context, messages any)) *Service_ConsumeBlocking_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(context.Context)
}
var arg1 interface{}
var arg1 any
if args[1] != nil {
arg1 = args[1].(interface{})
arg1 = args[1].(any)
}
run(
arg0,
@@ -93,7 +94,7 @@ func (_c *Service_ConsumeBlocking_Call) Return(err error) *Service_ConsumeBlocki
return _c
}
func (_c *Service_ConsumeBlocking_Call) RunAndReturn(run func(ctx context.Context, messages interface{}) error) *Service_ConsumeBlocking_Call {
func (_c *Service_ConsumeBlocking_Call) RunAndReturn(run func(ctx context.Context, messages any) error) *Service_ConsumeBlocking_Call {
_c.Call.Return(run)
return _c
}
@@ -1,10 +1,11 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
+8 -8
View File
@@ -22,10 +22,10 @@ type database struct {
// Database provides a database interface.
type Database interface {
NamedExecContext(context.Context, string, interface{}) (sql.Result, error)
QueryRowxContext(context.Context, string, ...interface{}) *sqlx.Row
NamedQueryContext(context.Context, string, interface{}) (*sqlx.Rows, error)
GetContext(context.Context, interface{}, string, ...interface{}) error
NamedExecContext(context.Context, string, any) (sql.Result, error)
QueryRowxContext(context.Context, string, ...any) *sqlx.Row
NamedQueryContext(context.Context, string, any) (*sqlx.Rows, error)
GetContext(context.Context, any, string, ...any) error
}
// NewDatabase creates a SubscriptionsDatabase instance.
@@ -36,25 +36,25 @@ func NewDatabase(db *sqlx.DB, tracer trace.Tracer) Database {
}
}
func (dm database) NamedExecContext(ctx context.Context, query string, args interface{}) (sql.Result, error) {
func (dm database) NamedExecContext(ctx context.Context, query string, args any) (sql.Result, error) {
ctx, span := dm.addSpanTags(ctx, "NamedExecContext", query)
defer span.End()
return dm.db.NamedExecContext(ctx, query, args)
}
func (dm database) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row {
func (dm database) QueryRowxContext(ctx context.Context, query string, args ...any) *sqlx.Row {
ctx, span := dm.addSpanTags(ctx, "QueryRowxContext", query)
defer span.End()
return dm.db.QueryRowxContext(ctx, query, args...)
}
func (dm database) NamedQueryContext(ctx context.Context, query string, args interface{}) (*sqlx.Rows, error) {
func (dm database) NamedQueryContext(ctx context.Context, query string, args any) (*sqlx.Rows, error) {
ctx, span := dm.addSpanTags(ctx, "NamedQueryContext", query)
defer span.End()
return dm.db.NamedQueryContext(ctx, query, args)
}
func (dm database) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
func (dm database) GetContext(ctx context.Context, dest any, query string, args ...any) error {
ctx, span := dm.addSpanTags(ctx, "GetContext", query)
defer span.End()
return dm.db.GetContext(ctx, dest, query, args...)
@@ -42,7 +42,7 @@ func (repo subscriptionsRepo) Save(ctx context.Context, sub notifiers.Subscripti
row, err := repo.db.NamedQueryContext(ctx, q, dbSub)
if err != nil {
if pqErr, ok := err.(*pgconn.PgError); ok && pqErr.Code == pgerrcode.UniqueViolation {
return "", errors.Wrap(repoerr.ErrConflict, err)
return "", errors.Wrap(notifiers.ErrSubscriptionsAlreadyExists, err)
}
return "", errors.Wrap(repoerr.ErrCreateEntity, err)
}
@@ -66,7 +66,7 @@ func (repo subscriptionsRepo) Retrieve(ctx context.Context, id string) (notifier
func (repo subscriptionsRepo) RetrieveAll(ctx context.Context, pm notifiers.PageMetadata) (notifiers.Page, error) {
q := `SELECT id, owner_id, contact, topic FROM subscriptions`
args := make(map[string]interface{})
args := make(map[string]any)
if pm.Topic != "" {
args["topic"] = pm.Topic
}
@@ -132,7 +132,7 @@ func (repo subscriptionsRepo) Remove(ctx context.Context, id string) error {
return nil
}
func total(ctx context.Context, db Database, query string, params interface{}) (uint, error) {
func total(ctx context.Context, db Database, query string, params any) (uint, error) {
rows, err := db.NamedQueryContext(ctx, query, params)
if err != nil {
return 0, err
@@ -60,7 +60,7 @@ func TestSave(t *testing.T) {
desc: "save duplicate",
sub: sub2,
id: "",
err: repoerr.ErrConflict,
err: notifiers.ErrSubscriptionsAlreadyExists,
},
}
+8 -4
View File
@@ -15,9 +15,13 @@ import (
"github.com/absmach/supermq/pkg/messaging"
)
// ErrMessage indicates an error converting a message to SuperMQ message.
var ErrMessage = errors.New("failed to convert to SuperMQ message")
var (
// ErrMessage indicates an error converting a message to SuperMQ message.
ErrMessage = errors.New("failed to convert to SuperMQ message")
// ErrSubscriptionsAlreadyExists indicates subscription already exists.
ErrSubscriptionsAlreadyExists = errors.NewRequestError("subscription already exists")
)
var _ consumers.AsyncConsumer = (*notifierService)(nil)
// Service reprents a notification service.
@@ -103,7 +107,7 @@ func (ns *notifierService) RemoveSubscription(ctx context.Context, token, id str
return ns.subs.Remove(ctx, id)
}
func (ns *notifierService) ConsumeBlocking(ctx context.Context, message interface{}) error {
func (ns *notifierService) ConsumeBlocking(ctx context.Context, message any) error {
msg, ok := message.(*messaging.Message)
if !ok {
return ErrMessage
@@ -136,7 +140,7 @@ func (ns *notifierService) ConsumeBlocking(ctx context.Context, message interfac
return nil
}
func (ns *notifierService) ConsumeAsync(ctx context.Context, message interface{}) {
func (ns *notifierService) ConsumeAsync(ctx context.Context, message any) {
msg, ok := message.(*messaging.Message)
if !ok {
ns.errCh <- ErrMessage
+1 -2
View File
@@ -16,7 +16,6 @@ import (
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"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/messaging"
"github.com/absmach/supermq/pkg/uuid"
@@ -66,7 +65,7 @@ func TestCreateSubscription(t *testing.T) {
token: exampleUser1,
sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"},
id: "",
err: repoerr.ErrConflict,
err: notifiers.ErrSubscriptionsAlreadyExists,
authenticateErr: nil,
userID: validID,
},
+1 -1
View File
@@ -47,5 +47,5 @@ default values.
Starting service will start consuming messages and sending SMS when a message is received.
[doc]: https://docs.magistrala.abstractmachines.fr
[doc]: https://docs.magistrala.absmach.eu
+1 -1
View File
@@ -8,5 +8,5 @@
// SuperMQ WebSocket adapter service.
//
// For more details about tracing instrumentation for SuperMQ messaging refer
// to the documentation at https://docs.supermq.abstractmachines.fr/tracing/.
// to the documentation at https://docs.supermq.absmach.eu/tracing/.
package tracing
+2 -2
View File
@@ -66,7 +66,7 @@ func NewBlocking(tracer trace.Tracer, consumerBlock consumers.BlockingConsumer,
}
// ConsumeBlocking traces consume operations for message/s consumed.
func (tm *tracingMiddlewareBlock) ConsumeBlocking(ctx context.Context, messages interface{}) error {
func (tm *tracingMiddlewareBlock) ConsumeBlocking(ctx context.Context, messages any) error {
var span trace.Span
switch m := messages.(type) {
case smqjson.Messages:
@@ -86,7 +86,7 @@ func (tm *tracingMiddlewareBlock) ConsumeBlocking(ctx context.Context, messages
}
// ConsumeAsync traces consume operations for message/s consumed.
func (tm *tracingMiddlewareAsync) ConsumeAsync(ctx context.Context, messages interface{}) {
func (tm *tracingMiddlewareAsync) ConsumeAsync(ctx context.Context, messages any) {
var span trace.Span
switch m := messages.(type) {
case smqjson.Messages:
+263 -11
View File
@@ -1,16 +1,268 @@
# Writers
Writers provide an implementation of various `message writers`.
Message writers are services that normalize (in `SenML` format)
SuperMQ messages and store them in specific data store.
Writers consume messages from the message broker, normalize them (SenML or JSON), and persist them to a storage backend. Magistrala provides two writer services:
Writers are optional services and are treated as plugins. In order to
run writer services, core services must be up and running. For more info
on the platform core services with its dependencies, please check out
the [Docker Compose][compose] file.
- **Postgres writer**: Stores data in PostgreSQL.
- **Timescale writer**: Stores data in TimescaleDB and uses hypertables for time-series workloads.
For an in-depth explanation of the usage of `writers`, as well as thorough
understanding of SuperMQ, please check out the [official documentation][doc].
Writers are optional services and are treated as plugins. Core services and the message broker must be running first. For platform dependencies, see [Docker Compose](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yaml).
[doc]: https://docs.supermq.abstractmachines.fr
[compose]: ../docker/docker-compose.yaml
## Configuration
Values shown are from [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env) and the add-on compose files in `docker/addons/*-writer/docker-compose.yaml`.
### Postgres writer
#### Postgres Service endpoints
| Variable | Description | Default |
| --- | --- | --- |
| `MG_POSTGRES_WRITER_LOG_LEVEL` | Service log level | `debug` |
| `MG_POSTGRES_WRITER_CONFIG_PATH` | Config file path (subjects/transformer) | `/config.toml` |
| `MG_POSTGRES_WRITER_HTTP_HOST` | HTTP host | `postgres-writer` |
| `MG_POSTGRES_WRITER_HTTP_PORT` | HTTP port | `9007` |
| `MG_POSTGRES_WRITER_HTTP_SERVER_CERT` | HTTPS server certificate path | "" |
| `MG_POSTGRES_WRITER_HTTP_SERVER_KEY` | HTTPS server key path | "" |
| `MG_POSTGRES_WRITER_INSTANCE_ID` | Instance ID | "" |
#### Postgres Database
| Variable | Description | Default |
| --- | --- | --- |
| `MG_POSTGRES_HOST` | PostgreSQL host | `postgres` |
| `MG_POSTGRES_PORT` | PostgreSQL port | `5432` |
| `MG_POSTGRES_USER` | PostgreSQL user | `supermq` |
| `MG_POSTGRES_PASS` | PostgreSQL password | `supermq` |
| `MG_POSTGRES_NAME` | PostgreSQL database name | `messages` |
| `MG_POSTGRES_SSL_MODE` | PostgreSQL SSL mode | `disable` |
| `MG_POSTGRES_SSL_CERT` | PostgreSQL SSL client cert | "" |
| `MG_POSTGRES_SSL_KEY` | PostgreSQL SSL client key | "" |
| `MG_POSTGRES_SSL_ROOT_CERT` | PostgreSQL SSL root cert | "" |
#### Postgres Message broker and observability
| Variable | Description | Default |
| --- | --- | --- |
| `SMQ_MESSAGE_BROKER_URL` | Message broker URL | `nats://nats:4222` |
| `SMQ_JAEGER_URL` | Jaeger collector endpoint | `http://jaeger:4318/v1/traces` |
| `SMQ_JAEGER_TRACE_RATIO` | Trace sampling ratio | `1.0` |
| `SMQ_SEND_TELEMETRY` | Send telemetry to Magistrala call-home server | `true` |
### Timescale writer
#### Timescale Service endpoints
| Variable | Description | Default |
| --- | --- | --- |
| `MG_TIMESCALE_WRITER_LOG_LEVEL` | Service log level | `debug` |
| `MG_TIMESCALE_WRITER_CONFIG_PATH` | Config file path (subjects/transformer) | `/config.toml` |
| `MG_TIMESCALE_WRITER_HTTP_HOST` | HTTP host | `timescale-writer` |
| `MG_TIMESCALE_WRITER_HTTP_PORT` | HTTP port | `9012` |
| `MG_TIMESCALE_WRITER_HTTP_SERVER_CERT` | HTTPS server certificate path | "" |
| `MG_TIMESCALE_WRITER_HTTP_SERVER_KEY` | HTTPS server key path | "" |
| `MG_TIMESCALE_WRITER_INSTANCE_ID` | Instance ID | "" |
#### Timescale Database
| Variable | Description | Default |
| --- | --- | --- |
| `MG_TIMESCALE_HOST` | TimescaleDB host | `timescale` |
| `MG_TIMESCALE_PORT` | TimescaleDB port | `5432` |
| `MG_TIMESCALE_USER` | TimescaleDB user | `supermq` |
| `MG_TIMESCALE_PASS` | TimescaleDB password | `supermq` |
| `MG_TIMESCALE_NAME` | TimescaleDB database name | `supermq` |
| `MG_TIMESCALE_SSL_MODE` | TimescaleDB SSL mode | `disable` |
| `MG_TIMESCALE_SSL_CERT` | TimescaleDB SSL client cert | "" |
| `MG_TIMESCALE_SSL_KEY` | TimescaleDB SSL client key | "" |
| `MG_TIMESCALE_SSL_ROOT_CERT` | TimescaleDB SSL root cert | "" |
#### Timescale Message broker and observability
Timescale writer uses the same broker and telemetry variables listed for Postgres writer.
### Writer config file
Both writers read a config file defined by `*_WRITER_CONFIG_PATH`. The default add-on config files are:
- `docker/addons/postgres-writer/config.toml`
- `docker/addons/timescale-writer/config.toml`
The config file controls subscription subjects and, for Postgres, optional transformer settings:
```toml
["subscriber"]
subjects = ["writers.>"]
[transformer]
format = "senml"
content_type = "application/senml+json"
time_fields = [
{ field_name = "seconds_key", field_format = "unix", location = "UTC" },
{ field_name = "millis_key", field_format = "unix_ms", location = "UTC" },
{ field_name = "micros_key", field_format = "unix_us", location = "UTC" },
{ field_name = "nanos_key", field_format = "unix_ns", location = "UTC" }
]
```
NATS uses subject `writers.>` and RabbitMQ uses routing key `writers.#` (both are handled by `consumers/writers/brokers`).
## Features
- **Message persistence**: Stores incoming SenML messages into PostgreSQL or TimescaleDB.
- **JSON payload support**: Saves JSON payloads into dynamically created tables.
- **Broker-backed ingestion**: Consumes from NATS JetStream or RabbitMQ topics.
- **Configurable subscription**: Limits ingestion to specific `writers.*` subjects.
- **Observability**: Exposes `/health` and `/metrics` endpoints, with Jaeger tracing.
## Architecture
### Runtime flow
1. The message broker publishes messages under `writers.*`.
2. The writer loads `config.toml` to select subjects and transformer settings.
3. The consumer converts messages to SenML or JSON payloads.
4. The repository writes records to the target database.
### Components
- **Message broker adapter**: `consumers/writers/brokers` (NATS JetStream or RabbitMQ).
- **Writer services**: `consumers/writers/postgres` and `consumers/writers/timescale`.
- **HTTP API**: `consumers/writers/api` exposes `/health` and `/metrics`.
- **Migrations**: `consumers/writers/*/init.go` defines the schema and indexes.
### PostgreSQL schema (SenML messages)
Defined in `consumers/writers/postgres/init.go`:
| Column | Type | Description |
| --- | --- | --- |
| `id` | `UUID` | Message ID |
| `channel` | `UUID` | Channel ID |
| `subtopic` | `VARCHAR(254)` | Subtopic |
| `publisher` | `UUID` | Publisher (client) ID |
| `protocol` | `TEXT` | Protocol name |
| `name` | `TEXT` | SenML name |
| `unit` | `TEXT` | SenML unit |
| `value` | `FLOAT` | Numeric value |
| `string_value` | `TEXT` | String value |
| `bool_value` | `BOOL` | Boolean value |
| `data_value` | `BYTEA` | Data value |
| `sum` | `FLOAT` | Sum value |
| `time` | `FLOAT` | Measurement time |
| `update_time` | `FLOAT` | Update time |
Primary key: `(time, publisher, subtopic, name)`
### TimescaleDB schema (SenML messages)
Defined in `consumers/writers/timescale/init.go`:
| Column | Type | Description |
| --- | --- | --- |
| `time` | `BIGINT` | Measurement time |
| `channel` | `UUID` | Channel ID |
| `subtopic` | `VARCHAR(254)` | Subtopic |
| `publisher` | `VARCHAR(254)` | Publisher (client) ID |
| `protocol` | `TEXT` | Protocol name |
| `name` | `VARCHAR(254)` | SenML name |
| `unit` | `TEXT` | SenML unit |
| `value` | `FLOAT` | Numeric value |
| `string_value` | `TEXT` | String value |
| `bool_value` | `BOOL` | Boolean value |
| `data_value` | `BYTEA` | Data value |
| `sum` | `FLOAT` | Sum value |
| `update_time` | `FLOAT` | Update time |
Primary key: `(time, channel, subtopic, protocol, publisher, name)`
Timescale writer creates a hypertable on `messages` and adds time-series indexes for common query paths.
### JSON payload tables (dynamic)
If the transformer emits JSON payloads, the writers create a table named after the payload format:
Postgres JSON table:
`id UUID`, `created BIGINT`, `channel VARCHAR(254)`, `subtopic VARCHAR(254)`, `publisher VARCHAR(254)`, `protocol TEXT`, `payload JSONB` (PK: `id`)
Timescale JSON table:
`created BIGINT`, `channel VARCHAR(254)`, `subtopic VARCHAR(254)`, `publisher VARCHAR(254)`, `protocol TEXT`, `payload JSONB` (PK: `created`, `publisher`, `subtopic`)
## Deployment
### Build and run locally
Postgres writer:
```bash
make postgres-writer
MG_POSTGRES_WRITER_LOG_LEVEL=debug \
MG_POSTGRES_WRITER_CONFIG_PATH=./docker/addons/postgres-writer/config.toml \
MG_POSTGRES_WRITER_HTTP_PORT=9007 \
MG_POSTGRES_HOST=localhost \
MG_POSTGRES_PORT=5432 \
MG_POSTGRES_USER=supermq \
MG_POSTGRES_PASS=supermq \
MG_POSTGRES_NAME=messages \
SMQ_MESSAGE_BROKER_URL=nats://localhost:4222 \
SMQ_JAEGER_URL=http://localhost:4318/v1/traces \
./build/postgres-writer
```
Timescale writer:
```bash
make timescale-writer
MG_TIMESCALE_WRITER_LOG_LEVEL=debug \
MG_TIMESCALE_WRITER_CONFIG_PATH=./docker/addons/timescale-writer/config.toml \
MG_TIMESCALE_WRITER_HTTP_PORT=9012 \
MG_TIMESCALE_HOST=localhost \
MG_TIMESCALE_PORT=5432 \
MG_TIMESCALE_USER=supermq \
MG_TIMESCALE_PASS=supermq \
MG_TIMESCALE_NAME=supermq \
SMQ_MESSAGE_BROKER_URL=nats://localhost:4222 \
SMQ_JAEGER_URL=http://localhost:4318/v1/traces \
./build/timescale-writer
```
### Docker Compose
Postgres writer add-on:
```bash
docker compose -f docker/docker-compose.yaml -f docker/addons/postgres-writer/docker-compose.yaml up
```
Timescale writer add-on:
```bash
docker compose -f docker/docker-compose.yaml -f docker/addons/timescale-writer/docker-compose.yaml up
```
### Health check
```bash
curl -X GET http://localhost:9007/health \
-H "accept: application/health+json"
```
## Testing
```bash
go test ./consumers/writers/...
```
## Usage
Writers do not expose a message ingestion API. Messages are written via the message broker. The HTTP API provides only health and metrics endpoints.
| Endpoint | Description |
| --- | --- |
| `GET /health` | Service health check |
| `GET /metrics` | Prometheus metrics |
For an in-depth explanation of Writers, see the [official documentation][doc].
[doc]: https://docs.magistrala.absmach.eu/dev-guide/consumers/
+1 -1
View File
@@ -30,7 +30,7 @@ func LoggingMiddleware(consumer consumers.BlockingConsumer, logger *slog.Logger)
// ConsumeBlocking logs the consume request. It logs the time it took to complete the request.
// If the request fails, it logs the error.
func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) (err error) {
func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msgs any) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
+1 -1
View File
@@ -32,7 +32,7 @@ func MetricsMiddleware(consumer consumers.BlockingConsumer, counter metrics.Coun
}
// ConsumeBlocking instruments ConsumeBlocking method with metrics.
func (mm *metricsMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) error {
func (mm *metricsMiddleware) ConsumeBlocking(ctx context.Context, msgs any) error {
defer func(begin time.Time) {
mm.counter.With("method", "consume").Add(1)
mm.latency.With("method", "consume").Observe(time.Since(begin).Seconds())
+5 -2
View File
@@ -36,7 +36,7 @@ func New(db *sqlx.DB) consumers.BlockingConsumer {
return &postgresRepo{db: db}
}
func (pr postgresRepo) ConsumeBlocking(ctx context.Context, message interface{}) (err error) {
func (pr postgresRepo) ConsumeBlocking(ctx context.Context, message any) (err error) {
switch m := message.(type) {
case smqjson.Messages:
return pr.saveJSON(ctx, m)
@@ -45,7 +45,7 @@ func (pr postgresRepo) ConsumeBlocking(ctx context.Context, message interface{})
}
}
func (pr postgresRepo) saveSenml(ctx context.Context, messages interface{}) (err error) {
func (pr postgresRepo) saveSenml(ctx context.Context, messages any) (err error) {
msgs, ok := messages.([]senml.Message)
if !ok {
return errSaveMessage
@@ -137,6 +137,9 @@ func (pr postgresRepo) insertJSON(ctx context.Context, msgs smqjson.Messages) er
}
if _, err = tx.NamedExec(q, dbmsg); err != nil {
if preErr, ok := err.(*pgconn.PrepareError); ok {
err = preErr.Unwrap()
}
pgErr, ok := err.(*pgconn.PgError)
if ok {
switch pgErr.Code {
+2 -2
View File
@@ -85,12 +85,12 @@ func TestSaveJSON(t *testing.T) {
Created: time.Now().Unix(),
Subtopic: "subtopic/format/some_json",
Protocol: "mqtt",
Payload: map[string]interface{}{
Payload: map[string]any{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_5": map[string]any{
"field_1": "value",
"field_2": 42,
},
+5 -2
View File
@@ -38,7 +38,7 @@ func New(db *sqlx.DB) consumers.BlockingConsumer {
return &timescaleRepo{db: db}
}
func (tr *timescaleRepo) ConsumeBlocking(ctx context.Context, message interface{}) (err error) {
func (tr *timescaleRepo) ConsumeBlocking(ctx context.Context, message any) (err error) {
switch m := message.(type) {
case smqjson.Messages:
return tr.saveJSON(ctx, m)
@@ -51,7 +51,7 @@ func (tr *timescaleRepo) ConsumeBlocking(ctx context.Context, message interface{
}
}
func (tr timescaleRepo) saveSenml(ctx context.Context, messages interface{}) (err error) {
func (tr timescaleRepo) saveSenml(ctx context.Context, messages any) (err error) {
msgs, ok := messages.([]senml.Message)
if !ok {
return errSaveMessage
@@ -139,6 +139,9 @@ func (tr timescaleRepo) insertJSON(ctx context.Context, msgs smqjson.Messages) e
return errors.Wrap(errSaveMessage, err)
}
if _, err = tx.NamedExec(q, dbmsg); err != nil {
if preErr, ok := err.(*pgconn.PrepareError); ok {
err = preErr.Unwrap()
}
pgErr, ok := err.(*pgconn.PgError)
if ok {
switch pgErr.Code {
+2 -2
View File
@@ -85,12 +85,12 @@ func TestSaveJSON(t *testing.T) {
Created: time.Now().Unix(),
Subtopic: "subtopic/format/some_json",
Protocol: "mqtt",
Payload: map[string]interface{}{
Payload: map[string]any{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_5": map[string]any{
"field_1": "value",
"field_2": 42,
},
+143 -5
View File
@@ -52,6 +52,8 @@ SMQ_AUTH_GRPC_TIMEOUT=300s
SMQ_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt}
SMQ_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.key}
SMQ_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}
SMQ_AUTH_ACCESS_TOKEN_DURATION=1h
SMQ_AUTH_REFRESH_TOKEN_DURATION=24h
#### Clients Client Config
SMQ_CLIENTS_URL=http://clients:9006
@@ -85,11 +87,14 @@ SMQ_SPICEDB_DB_PORT=5432
### SpiceDB config
SMQ_SPICEDB_PRE_SHARED_KEY="12345678"
SMQ_SPICEDB_SCHEMA_FILE="/schema.zed"
SMQ_SPICEDB_SCHEMA_FILE="/schemas/combined-schema.zed"
SMQ_SPICEDB_HOST=supermq-spicedb
SMQ_SPICEDB_PORT=50051
SMQ_SPICEDB_DATASTORE_ENGINE=postgres
### Permissions
SMQ_PERMISSIONS_FILE=/schemas/permission.yaml
### UI
SMQ_UI_PATH_PREFIX=/ui
@@ -111,6 +116,16 @@ MG_RE_DB_SSL_ROOT_CERT=
MG_RE_INSTANCE_ID=
MG_RE_EMAIL_TEMPLATE=re.tmpl
#### RE Callout
MG_RE_CALLOUT_URLS=""
MG_RE_CALLOUT_METHOD="POST"
MG_RE_CALLOUT_TLS_VERIFICATION="false"
MG_RE_CALLOUT_TIMEOUT="10s"
MG_RE_CALLOUT_CA_CERT=""
MG_RE_CALLOUT_CERT=""
MG_RE_CALLOUT_KEY=""
MG_RE_CALLOUT_OPERATIONS=""
MG_EMAIL_HOST=smtp.mailtrap.io
MG_EMAIL_PORT=2525
MG_EMAIL_USERNAME=18bf7f70705139
@@ -135,6 +150,7 @@ MG_ALARMS_DB_SSL_CERT=
MG_ALARMS_DB_SSL_KEY=
MG_ALARMS_DB_SSL_ROOT_CERT=
MG_ALARMS_INSTANCE_ID=
MG_ALARMS_EVENT_CONSUMER=alarms
### REPORTS
MG_REPORTS_LOG_LEVEL=debug
@@ -159,6 +175,96 @@ MG_PDF_CONVERTER_URL=http://pdf-generator:3000/forms/chromium/convert/html
### Certs
SMQ_ADDONS_CERTS_PATH_PREFIX=./
## CERTS
AM_CERTS_LOG_LEVEL=debug
AM_CERTS_DB_HOST=certs-db
AM_CERTS_DB_PORT=5432
AM_CERTS_DB_USER=absmach
AM_CERTS_DB_PASS=absmach
AM_CERTS_DB=certs
AM_CERTS_DB_SSL_MODE=disable
AM_CERTS_DB_SSL_CERT=
AM_CERTS_DB_SSL_KEY=
AM_CERTS_DB_SSL_ROOT_CERT=
AM_CERTS_DB_MAX_CONNECTIONS=100
AM_CERTS_HTTP_HOST=certs
AM_CERTS_HTTP_PORT=9010
AM_CERTS_HTTP_SERVER_CERT=
AM_CERTS_HTTP_SERVER_KEY=
AM_CERTS_GRPC_HOST=certs
AM_CERTS_GRPC_PORT=7012
AM_CERTS_GRPC_SERVER_CERT=
AM_CERTS_GRPC_SERVER_KEY=
AM_CERTS_GRPC_SERVER_CA_CERTS=
AM_CERTS_GRPC_SERVER_CA_KEY=
AM_CERTS_GRPC_CLIENT_CA_CERTS=
AM_CERTS_GRPC_URL=${AM_CERTS_GRPC_HOST}:${AM_CERTS_GRPC_PORT}
AM_CERTS_GRPC_TIMEOUT=
AM_CERTS_GRPC_CLIENT_CERT=
AM_CERTS_GRPC_CLIENT_KEY=
AM_CERTS_GRPC_CLIENT_TLS=
AM_CERTS_GRPC_CA_CERTS=
AM_CERTS_INSTANCE_ID=
AM_CERTS_RELEASE_TAG=latest
# WARNING: This is a development/testing secret only.
# NEVER use this weak secret in production! Generate a strong random secret for production deployments.
AM_CERTS_SECRET=12345678
## OpenBao PKI Config
AM_CERTS_OPENBAO_HOST=http://certs-openbao:8200
AM_CERTS_OPENBAO_APP_ROLE=absmach
AM_CERTS_OPENBAO_APP_SECRET=absmach
AM_CERTS_OPENBAO_SECRET_ID_TTL=720h
AM_CERTS_OPENBAO_NAMESPACE=
AM_CERTS_OPENBAO_PKI_PATH=pki
AM_CERTS_OPENBAO_ROLE=absmach
AM_CERTS_SERVICE_TOKEN_PATH=/openbao/service_token
AM_CERTS_SECRET_ID_PATH=/openbao/secret_id
AM_CERTS_SECRET_RENEW_THRESHOLD=24h
AM_CERTS_SECRET_CHECK_INTERVAL=1h
AM_CERTS_OPENBAO_PKI_CA_CN=Abstract Machines Certificate Authority
AM_CERTS_OPENBAO_PKI_CA_OU=Abstract Machines
AM_CERTS_OPENBAO_PKI_CA_O=AbstractMachines
AM_CERTS_OPENBAO_PKI_CA_C=FRANCE
AM_CERTS_OPENBAO_PKI_CA_L=PARIS
AM_CERTS_OPENBAO_PKI_CA_ST=PARIS
AM_CERTS_OPENBAO_PKI_CA_ADDR=5 Av. Anatole
AM_CERTS_OPENBAO_PKI_CA_PO=75007
AM_CERTS_OPENBAO_PKI_CA_DNS_NAMES=localhost
AM_CERTS_OPENBAO_PKI_CA_IP_ADDRESSES=127.0.0.1,::1
AM_CERTS_OPENBAO_PKI_CA_URI_SANS=
AM_CERTS_OPENBAO_PKI_CA_EMAIL_ADDRESSES=info@abstractmachines.rs
AM_CERTS_OPENBAO_UNSEAL_KEY_1=
AM_CERTS_OPENBAO_UNSEAL_KEY_2=
AM_CERTS_OPENBAO_UNSEAL_KEY_3=
AM_CERTS_OPENBAO_ROOT_TOKEN=
## Jaeger
AM_JAEGER_PORT=6831
AM_JAEGER_FRONTEND=16686
AM_JAEGER_URL=http://jaeger:4318/v1/traces
AM_JAEGER_TRACE_RATIO=1.0
AM_JAEGER_COLLECTOR_OTLP_ENABLED=true
AM_JAEGER_OLTP_HTTP_PORT=4318
AM_JAEGER_MEMORY_MAX_TRACES=5000
#### Auth Client Config
AM_AUTH_URL=auth:9001
AM_AUTH_GRPC_URL=auth:7001
AM_AUTH_GRPC_TIMEOUT=300s
AM_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt}
AM_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.key}
AM_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}
AM_AUTH_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}
#### Domains Client Config
AM_DOMAINS_URL=domains:9003
AM_DOMAINS_GRPC_URL=domains:7003
AM_DOMAINS_GRPC_TIMEOUT=300s
AM_DOMAINS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/domains-grpc-client.crt}
AM_DOMAINS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/domains-grpc-client.key}
AM_DOMAINS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}
## Addon Services
### Bootstrap
MG_BOOTSTRAP_LOG_LEVEL=debug
@@ -186,13 +292,14 @@ MG_PROVISION_HTTP_PORT=9016
MG_PROVISION_ENV_CLIENTS_TLS=false
MG_PROVISION_SERVER_CERT=
MG_PROVISION_SERVER_KEY=
MG_PROVISION_USERS_LOCATION=http://users:9002
MG_PROVISION_CLIENTS_LOCATION=http://clients:9006
MG_PROVISION_USERS_URL=http://users:9002
MG_PROVISION_CHANNELS_URL=http://channels:9005
MG_PROVISION_CLIENTS_URL=http://clients:9006
MG_PROVISION_CERTS_URL=http://certs:9019
MG_PROVISION_USER=
MG_PROVISION_USERNAME=
MG_PROVISION_PASS=
MG_PROVISION_API_KEY=
MG_PROVISION_CERTS_SVC_URL=http://certs:9019
MG_PROVISION_X509_PROVISIONING=false
MG_PROVISION_BS_SVC_URL=http://bootstrap:9013
MG_PROVISION_BS_CONFIG_PROVISIONING=true
@@ -301,6 +408,19 @@ MG_UI_BACKEND_INSTANCE_ID=
MG_UI_VERIFICATION_TLS=false
MG_UI_CONTENT_TYPE=application/senml+json
# Object storage for images
# See docker/seaweedfs/s3.json.
MG_BACKEND_OBJECT_STORAGE_REGION=fra1
MG_BACKEND_OBJECT_STORAGE_BUCKET=mg-ui-images
MG_BACKEND_OBJECT_STORAGE_ENDPOINT=http://seaweedfs-s3:8333
MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE=true
MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT=http://localhost:8333
MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY=localKey
MG_BACKEND_OBJECT_STORAGE_SECRET_KEY=localSecret
MG_BACKEND_OBJECT_STORAGE_WRITE_TTL=1m
MG_BACKEND_OBJECT_STORAGE_READ_TTL=15m
MG_BACKEND_OBJECT_STORAGE_TTL=15m
#### Auth GRPC Client Config
MG_AUTH_GRPC_URL=auth:7001
MG_AUTH_GRPC_TIMEOUT=300s
@@ -348,15 +468,33 @@ MG_UI_STRIPE_RETURN_URL=http://localhost:3000
NEXTAUTH_SECRET=4WdW0Z0tAOyQ/ZAI3YLVV/wNu+yUZXBLDDQ3AGrgfJ4=
NEXTAUTH_URL=http://localhost:3000
MG_HOST_URL=http://localhost:3000
MG_UI_IMAGE_URL=http://localhost:9097
MG_UI_IMAGE_URL=http://ui-backend:9097
MG_UI_BASEURL=http://localhost:3000
#Customer support email variables
MG_SUPPORT_EMAIL=
MG_SUPPORT_EMAIL_PASS=
## SMTP Variables
MG_UI_SMTP_HOST=host.docker.internal
MG_UI_SMTP_PORT=2525
MG_UI_SMTP_SECURE=
MG_UI_SUPPORT_FROM=from@example.com
# Message cli variables
MG_UI_CLI_MQTT_HOST=localhost
MG_UI_CLI_MQTT_PORT=8883
MG_UI_CLI_WS_URL=ws://localhost:8186
MG_UI_CLI_COAP_HOST=0.0.0.0
MG_UI_CLI_COAP_PORT=5684
MG_UI_CLI_HTTP_URL=http://localhost:8008
MG_UI_CLI_CA_FILE=/etc/ssl/certs/ca-certificates.crt
# Docker image tag
MG_RELEASE_TAG=latest
# Allow unverified user
SMQ_ALLOW_UNVERIFIED_USER=true
# Set to yes to accept the EULA for the UI services. To view the EULA visit: https://github.com/absmach/eula
MG_UI_DOCKER_ACCEPT_EULA=no
+1 -1
View File
@@ -1,7 +1,7 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
FROM golang:1.24.5-alpine AS builder
FROM golang:1.26-alpine3.22 AS builder
ARG SVC
ARG GOARCH
ARG GOARM
+4 -4
View File
@@ -33,7 +33,7 @@ Events store: This is used by Magistrala services to store events for distribute
This is the same as MESSAGE_BROKER. This can either be 'NATS' or 'RabbitMQ' or 'Redis'. If Redis is used as an events store, then RabbitMQ or NATS is used as a message broker.
The current deployment strategy for Magistrala in `docker/docker-compose.yaml` is to use VerneMQ as a MQTT_BROKER and NATS as a MESSAGE_BROKER and EVENTS_STORE.
The current deployment strategy for Magistrala in [docker/docker-compose.yaml](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yaml) is to use VerneMQ as a MQTT_BROKER and NATS as a MESSAGE_BROKER and EVENTS_STORE.
Therefore, the following combinations are possible:
@@ -46,7 +46,7 @@ Therefore, the following combinations are possible:
- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS
- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis
For Message brokers other than NATS, you would need to build the docker images with RabbitMQ as the build tag and change the `docker/.env`. For example, to use RabbitMQ as a message broker:
For Message brokers other than NATS, you would need to build the docker images with RabbitMQ as the build tag and change the [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env). For example, to use RabbitMQ as a message broker:
```bash
MG_MESSAGE_BROKER_TYPE=rabbitmq make dockers
@@ -70,7 +70,7 @@ MG_ES_TYPE=redis
MG_ES_URL=${MG_REDIS_URL}
```
For MQTT broker other than VerneMQ, you would need to change the `docker/.env`. For example, to use NATS as a MQTT broker:
For MQTT broker other than VerneMQ, you would need to change the [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env). For example, to use NATS as a MQTT broker:
```env
MG_MQTT_BROKER_TYPE=nats
@@ -121,7 +121,7 @@ services:
## Nginx Configuration
Nginx is the entry point for all traffic to Magistrala.
By using environment variables file at `docker/.env` you can modify the below given Nginx directive.
By using environment variables file at [docker/.env](https://github.com/absmach/magistrala/blob/main/docker/.env) you can modify the below given Nginx directive.
`SMQ_NGINX_SERVER_NAME` environmental variable is used to configure nginx directive `server_name`. If environmental variable `SMQ_NGINX_SERVER_NAME` is empty then default value `localhost` will set to `server_name`.
@@ -8,6 +8,7 @@
networks:
magistrala-base-net:
driver: bridge
volumes:
magistrala-bootstrap-db-volume:
@@ -71,6 +72,7 @@ services:
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
networks:
- magistrala-base-net
volumes:
@@ -1002,7 +1002,7 @@
"uid": "PBFA97CFB590B2093"
},
"exemplar": true,
"expr": "ws_adapter_api_request_count{}",
"expr": "http_adapter_api_request_count{}",
"interval": "",
"legendFormat": "{{method}}",
"refId": "A"
@@ -1107,7 +1107,7 @@
},
"editorMode": "code",
"exemplar": false,
"expr": "label_replace(label_replace(label_replace(ws_adapter_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")",
"expr": "label_replace(label_replace(label_replace(http_adapter_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")",
"format": "time_series",
"instant": false,
"interval": "",
+3 -3
View File
@@ -55,10 +55,10 @@
type = "plain"
workers = 10
[[things]]
name = "thing"
[[clients]]
name = "client"
[things.metadata]
[clients.metadata]
external_id = "xxxxxx"
[[channels]]
+11 -3
View File
@@ -8,6 +8,7 @@
networks:
magistrala-base-net:
driver: bridge
services:
provision:
@@ -25,13 +26,14 @@ services:
MG_PROVISION_ENV_CLIENTS_TLS: ${MG_PROVISION_ENV_CLIENTS_TLS}
MG_PROVISION_SERVER_CERT: ${MG_PROVISION_SERVER_CERT}
MG_PROVISION_SERVER_KEY: ${MG_PROVISION_SERVER_KEY}
MG_PROVISION_USERS_LOCATION: ${MG_PROVISION_USERS_LOCATION}
MG_PROVISION_THINGS_LOCATION: ${MG_PROVISION_THINGS_LOCATION}
MG_PROVISION_USERS_URL: ${MG_PROVISION_USERS_URL}
MG_PROVISION_CHANNELS_URL: ${MG_PROVISION_CHANNELS_URL}
MG_PROVISION_CLIENTS_URL: ${MG_PROVISION_CLIENTS_URL}
MG_PROVISION_USER: ${MG_PROVISION_USER}
MG_PROVISION_USERNAME: ${MG_PROVISION_USERNAME}
MG_PROVISION_PASS: ${MG_PROVISION_PASS}
MG_PROVISION_API_KEY: ${MG_PROVISION_API_KEY}
MG_PROVISION_CERTS_SVC_URL: ${MG_PROVISION_CERTS_SVC_URL}
MG_PROVISION_CERTS_URL: ${MG_PROVISION_CERTS_URL}
MG_PROVISION_X509_PROVISIONING: ${MG_PROVISION_X509_PROVISIONING}
MG_PROVISION_BS_SVC_URL: ${MG_PROVISION_BS_SVC_URL}
MG_PROVISION_BS_CONFIG_PROVISIONING: ${MG_PROVISION_BS_CONFIG_PROVISIONING}
@@ -40,6 +42,12 @@ services:
MG_PROVISION_CERTS_HOURS_VALID: ${MG_PROVISION_CERTS_HOURS_VALID}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
MG_PROVISION_INSTANCE_ID: ${MG_PROVISION_INSTANCE_ID}
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_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
volumes:
- ./configs:/configs
- ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key
+18
View File
@@ -0,0 +1,18 @@
{
"identities": [
{
"name": "magistrala",
"credentials": [
{
"accessKey": "localKey",
"secretKey": "localSecret"
}
],
"actions": [
"Admin",
"Read",
"Write"
]
}
]
}
+135 -29
View File
@@ -46,7 +46,6 @@ services:
MG_READER_URL: ${MG_READER_URL}
MG_BACKEND_URL: ${MG_UI_BACKEND_URL}
MG_JOURNAL_URL: ${MG_JOURNAL_URL}
MG_BILLING_URL: ${MG_BILLING_URL}
MG_ALARMS_URL: ${MG_ALARMS_URL}
MG_RE_URL: ${MG_RE_URL}
MG_REPORTS_URL: ${MG_REPORTS_URL}
@@ -66,6 +65,20 @@ services:
MG_UI_DOCKER_ACCEPT_EULA: ${MG_UI_DOCKER_ACCEPT_EULA}
MG_SUPPORT_EMAIL: ${MG_SUPPORT_EMAIL}
MG_SUPPORT_EMAIL_PASS: ${MG_SUPPORT_EMAIL_PASS}
MG_UI_CLI_MQTT_HOST: ${MG_UI_CLI_MQTT_HOST}
MG_UI_CLI_WS_URL: ${MG_UI_CLI_WS_URL}
MG_UI_CLI_COAP_HOST: ${MG_UI_CLI_COAP_HOST}
MG_UI_CLI_COAP_PORT: ${MG_UI_CLI_COAP_PORT}
MG_UI_CLI_HTTP_URL: ${MG_UI_CLI_HTTP_URL}
MG_UI_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
MG_ACCESS_TOKEN_EXPIRY: ${SMQ_AUTH_ACCESS_TOKEN_DURATION}
MG_REFRESH_TOKEN_EXPIRY: ${SMQ_AUTH_REFRESH_TOKEN_DURATION}
MG_UI_SMTP_HOST: ${MG_UI_SMTP_HOST}
MG_UI_SMTP_PORT: ${MG_UI_SMTP_PORT}
MG_UI_SMTP_SECURE: ${MG_UI_SMTP_SECURE}
MG_UI_SUPPORT_FROM: ${MG_UI_SUPPORT_FROM}
ui-backend:
image: ghcr.io/absmach/magistrala/ui-backend:latest
@@ -110,61 +123,72 @@ services:
MG_TIMESCALE_READER_GRPC_CLIENT_CERT: ${MG_TIMESCALE_READER_GRPC_CLIENT_CERT:+/readers-grpc-client.crt}
MG_TIMESCALE_READER_GRPC_CLIENT_KEY: ${MG_TIMESCALE_READER_GRPC_CLIENT_KEY:+/readers-grpc-client.key}
MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS: ${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS:+/readers-grpc-server-ca.crt}
MG_BACKEND_OBJECT_STORAGE_REGION: ${MG_BACKEND_OBJECT_STORAGE_REGION}
MG_BACKEND_OBJECT_STORAGE_BUCKET: ${MG_BACKEND_OBJECT_STORAGE_BUCKET}
MG_BACKEND_OBJECT_STORAGE_ENDPOINT: ${MG_BACKEND_OBJECT_STORAGE_ENDPOINT}
MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE: ${MG_BACKEND_OBJECT_STORAGE_USE_PATH_STYLE}
MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT: ${MG_BACKEND_OBJECT_STORAGE_PRESIGN_ENDPOINT}
MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY: ${MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY}
MG_BACKEND_OBJECT_STORAGE_SECRET_KEY: ${MG_BACKEND_OBJECT_STORAGE_SECRET_KEY}
MG_BACKEND_OBJECT_STORAGE_TTL: ${MG_BACKEND_OBJECT_STORAGE_TTL}
MG_BACKEND_OBJECT_STORAGE_READ_TTL: ${MG_BACKEND_OBJECT_STORAGE_READ_TTL}
depends_on:
- ui-backend-db
ui-backend-db:
condition: service_healthy
seaweedfs-s3:
condition: service_started
volumes:
# Auth gRPC client certificates
- type: bind
source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
target: /auth-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
target: /auth-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
# Channels gRPC client certificates
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt}
target: /channels-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key}
target: /channels-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /channels-grpc-server-ca${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt}
target: /channels-grpc-server-ca.crt
bind:
create_host_path: true
# Reader gRPC client certificates
- type: bind
source: ${MG_TIMESCALE_READER_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /readers-grpc-client${MG_TIMESCALE_READER_GRPC_CLIENT_CERT:+.crt}
target: /readers-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${MG_TIMESCALE_READER_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /readers-grpc-client${MG_TIMESCALE_READER_GRPC_CLIENT_KEY:+.key}
target: /readers-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs}
target: /readers-grpc-server-ca${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS:+.crt}
target: /readers-grpc-server-ca.crt
bind:
create_host_path: true
ui-backend-db:
image: postgres:16.2-alpine
image: docker.io/postgres:18.0-alpine3.22
container_name: magistrala-ui-backend-db
restart: on-failure
command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}"
@@ -179,9 +203,58 @@ services:
- magistrala-base-net
volumes:
- magistrala-ui-backend-db-volume:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 3s
retries: 60
seaweedfs-s3:
image: chrislusf/seaweedfs:4.16
container_name: magistrala-seaweedfs-s3
command: server -s3 -s3.config=/etc/seaweedfs/s3.json -dir=/data
ports:
- "8333:8333" # S3 endpoint
- "9333:9333" # master UI
- "19333:19333" # volume server
- "8888:8888" # filer UI
volumes:
- ./data/seaweedfs:/data
- ./configs/seaweedfs-s3.json:/etc/seaweedfs/s3.json:ro
networks:
- magistrala-base-net
seaweedfs-init:
image: amazon/aws-cli
container_name: magistrala-seaweedfs-init
entrypoint: /bin/sh
depends_on:
- seaweedfs-s3
command:
- -c
- |
echo "[INIT] Waiting 20s for SeaweedFS S3 to be ready...";
sleep 20;
OUT=$(aws --endpoint-url http://seaweedfs-s3:8333 s3api create-bucket --bucket $${BUCKET} 2>&1);
EXIT=$$?;
if [ $$EXIT -eq 0 ]; then
echo "[INIT] Bucket $${BUCKET} created successfully.";
elif echo "$$OUT" | grep -q 'BucketAlreadyOwnedByYou\|BucketAlreadyExists'; then
echo "[INIT] Bucket $${BUCKET} already exists, skipping.";
else
echo "[INIT] Failed to create bucket $${BUCKET}: $$OUT" >&2;
exit 1;
fi
networks:
- magistrala-base-net
environment:
BUCKET: ${MG_BACKEND_OBJECT_STORAGE_BUCKET}
AWS_ACCESS_KEY_ID: ${MG_BACKEND_OBJECT_STORAGE_ACCESS_KEY}
AWS_SECRET_ACCESS_KEY: ${MG_BACKEND_OBJECT_STORAGE_SECRET_KEY}
AWS_DEFAULT_REGION: ${MG_BACKEND_OBJECT_STORAGE_REGION}
AWS_EC2_METADATA_DISABLED: "true"
re-db:
image: postgres:16.2-alpine
image: docker.io/postgres:18.0-alpine3.22
container_name: magistrala-re-db
restart: on-failure
command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}"
@@ -201,6 +274,7 @@ services:
container_name: magistrala-re
depends_on:
- re-db
- spicedb-migrate
restart: on-failure
environment:
MG_RE_LOG_LEVEL: ${MG_RE_LOG_LEVEL}
@@ -217,7 +291,16 @@ services:
MG_RE_DB_SSL_CERT: ${MG_RE_DB_SSL_CERT}
MG_RE_DB_SSL_KEY: ${MG_RE_DB_SSL_KEY}
MG_RE_DB_SSL_ROOT_CERT: ${MG_RE_DB_SSL_ROOT_CERT}
MG_RE_CALLOUT_URLS: ${MG_RE_CALLOUT_URLS}
MG_RE_CALLOUT_METHOD: ${MG_RE_CALLOUT_METHOD}
MG_RE_CALLOUT_TLS_VERIFICATION: ${MG_RE_CALLOUT_TLS_VERIFICATION}
MG_RE_CALLOUT_TIMEOUT: ${MG_RE_CALLOUT_TIMEOUT}
MG_RE_CALLOUT_CA_CERT: ${MG_RE_CALLOUT_CA_CERT}
MG_RE_CALLOUT_CERT: ${MG_RE_CALLOUT_CERT}
MG_RE_CALLOUT_KEY: ${MG_RE_CALLOUT_KEY}
MG_RE_CALLOUT_OPERATIONS: ${MG_RE_CALLOUT_OPERATIONS}
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
@@ -229,6 +312,8 @@ services:
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
MG_RE_INSTANCE_ID: ${MG_RE_INSTANCE_ID}
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
@@ -247,31 +332,34 @@ services:
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}
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
ports:
- ${MG_RE_HTTP_PORT}:${MG_RE_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
- ./templates/${MG_RE_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}
target: /auth-grpc-client.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}
target: /auth-grpc-client.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}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
alarms-db:
image: postgres:16.2-alpine
image: docker.io/postgres:18.0-alpine3.22
container_name: magistrala-alarms-db
restart: on-failure
command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}"
@@ -291,6 +379,7 @@ services:
container_name: magistrala-alarms
depends_on:
- alarms-db
- spicedb-migrate
restart: on-failure
environment:
MG_ALARMS_LOG_LEVEL: ${MG_ALARMS_LOG_LEVEL}
@@ -308,6 +397,7 @@ services:
MG_ALARMS_DB_SSL_KEY: ${MG_ALARMS_DB_SSL_KEY}
MG_ALARMS_DB_SSL_ROOT_CERT: ${MG_ALARMS_DB_SSL_ROOT_CERT}
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL}
@@ -320,46 +410,55 @@ services:
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}
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
MG_ALARMS_INSTANCE_ID: ${MG_ALARMS_INSTANCE_ID}
MG_ALARMS_EVENT_CONSUMER: ${MG_ALARMS_EVENT_CONSUMER}
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
ports:
- ${MG_ALARMS_HTTP_PORT}:${MG_ALARMS_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
# 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}
target: /auth-grpc-client.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}
target: /auth-grpc-client.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}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_CERT:+.crt}
target: /domains-grpc-client.crt
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /domains-grpc-client${SMQ_DOMAINS_GRPC_CLIENT_KEY:+.key}
target: /domains-grpc-client.key
bind:
create_host_path: true
- type: bind
source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
target: /domains-grpc-server-ca.crt
bind:
create_host_path: true
reports-db:
image: postgres:16.2-alpine
image: docker.io/postgres:18.0-alpine3.22
container_name: magistrala-reports-db
restart: on-failure
command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}"
@@ -379,6 +478,7 @@ services:
container_name: magistrala-reports
depends_on:
- reports-db
- spicedb-migrate
restart: on-failure
environment:
MG_REPORTS_LOG_LEVEL: ${MG_REPORTS_LOG_LEVEL}
@@ -398,6 +498,7 @@ services:
MG_REPORTS_DEFAULT_TEMPLATE: ${MG_REPORTS_DEFAULT_TEMPLATE}
MG_PDF_CONVERTER_URL: ${MG_PDF_CONVERTER_URL}
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
SMQ_ES_URL: ${SMQ_ES_URL}
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
@@ -409,6 +510,8 @@ services:
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE}
SMQ_PERMISSIONS_FILE: ${SMQ_PERMISSIONS_FILE}
MG_REPORTS_INSTANCE_ID: ${MG_RE_INSTANCE_ID}
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
@@ -427,31 +530,34 @@ services:
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}
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
ports:
- ${MG_REPORTS_HTTP_PORT}:${MG_REPORTS_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./permission.yaml:${SMQ_PERMISSIONS_FILE}
- ./spicedb/combined-schema.zed:${SMQ_SPICEDB_SCHEMA_FILE}
- ./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}
target: /auth-grpc-client.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}
target: /auth-grpc-client.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}
target: /auth-grpc-server-ca.crt
bind:
create_host_path: true
pdf-generator:
image: gotenberg/gotenberg:${MG_RELEASE_TAG}
image: gotenberg/gotenberg:8.25.1
container_name: magistrala-pdf
ports:
- "4000:3000"
+1 -2
View File
@@ -23,7 +23,6 @@ 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
${MG_REPORTS_HTTP_PORT}' </etc/nginx/nginx.conf.template >/etc/nginx/nginx.conf
exec nginx -g "daemon off;"

Some files were not shown because too many files have changed in this diff Show More