├── .circleci
└── config.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── agent
├── .gitignore
├── Dockerfile
├── Makefile
├── oktopus-mqtt-obuspa.txt
├── oktopus-stomp-obuspa.txt
└── oktopus-websockets-obuspa.txt
├── backend
└── services
│ ├── acs
│ ├── .env
│ ├── .gitignore
│ ├── build
│ │ ├── Dockerfile
│ │ └── Makefile
│ ├── cmd
│ │ └── acs
│ │ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ └── internal
│ │ ├── auth
│ │ └── auth.go
│ │ ├── bridge
│ │ └── bridge.go
│ │ ├── config
│ │ └── config.go
│ │ ├── cwmp
│ │ └── cwmp.go
│ │ ├── nats
│ │ └── nats.go
│ │ └── server
│ │ ├── handler
│ │ ├── cwmp.go
│ │ ├── handler.go
│ │ └── status.go
│ │ └── server.go
│ ├── bulkdata
│ └── http
│ │ ├── .gitignore
│ │ ├── build
│ │ ├── Dockerfile
│ │ └── Makefile
│ │ ├── cmd
│ │ └── http-bulk-collector
│ │ │ └── main.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── internal
│ │ ├── api
│ │ ├── api.go
│ │ ├── cors
│ │ │ └── cors.go
│ │ ├── handler
│ │ │ ├── data.go
│ │ │ ├── handler.go
│ │ │ └── healthcheck.go
│ │ └── middleware
│ │ │ └── middleware.go
│ │ ├── bridge
│ │ └── bridge.go
│ │ ├── config
│ │ └── config.go
│ │ └── nats
│ │ └── nats.go
│ ├── controller
│ ├── .env
│ ├── .gitignore
│ ├── build
│ │ ├── Dockerfile
│ │ └── Makefile
│ ├── cmd
│ │ └── controller
│ │ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ └── internal
│ │ ├── api
│ │ ├── api.go
│ │ ├── auth
│ │ │ └── auth.go
│ │ ├── cors
│ │ │ └── cors.go
│ │ ├── cwmp.go
│ │ ├── device.go
│ │ ├── fwupdate.go
│ │ ├── info.go
│ │ ├── middleware
│ │ │ └── middleware.go
│ │ ├── user.go
│ │ ├── usp.go
│ │ ├── utils.go
│ │ └── wifi.go
│ │ ├── bridge
│ │ └── bridge.go
│ │ ├── config
│ │ └── config.go
│ │ ├── cwmp
│ │ └── cwmp.go
│ │ ├── db
│ │ ├── db.go
│ │ ├── template.go
│ │ └── user.go
│ │ ├── entity
│ │ ├── device.go
│ │ ├── msg.go
│ │ ├── mtp.go
│ │ └── status.go
│ │ ├── nats
│ │ └── nats.go
│ │ ├── usp
│ │ ├── usp_msg
│ │ │ ├── usp-msg-1-3.pb.go
│ │ │ └── usp-msg-1-3.proto
│ │ ├── usp_record
│ │ │ ├── usp-record-1-3.pb.go
│ │ │ └── usp-record-1-3.proto
│ │ └── usp_utils
│ │ │ └── utils.go
│ │ └── utils
│ │ └── utils.go
│ ├── mtp
│ ├── adapter
│ │ ├── .env
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── build
│ │ │ ├── Dockerfile
│ │ │ └── Makefile
│ │ ├── cmd
│ │ │ └── adapter
│ │ │ │ └── main.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── internal
│ │ │ ├── config
│ │ │ └── config.go
│ │ │ ├── cwmp
│ │ │ └── cwmp.go
│ │ │ ├── db
│ │ │ ├── db.go
│ │ │ ├── device.go
│ │ │ ├── info.go
│ │ │ └── status.go
│ │ │ ├── events
│ │ │ ├── cwmp_handler
│ │ │ │ ├── handler.go
│ │ │ │ ├── info.go
│ │ │ │ └── status.go
│ │ │ ├── events.go
│ │ │ └── usp_handler
│ │ │ │ ├── handler.go
│ │ │ │ ├── info.go
│ │ │ │ └── status.go
│ │ │ ├── nats
│ │ │ └── nats.go
│ │ │ ├── reqs
│ │ │ └── reqs.go
│ │ │ └── usp
│ │ │ ├── usp.go
│ │ │ ├── usp_msg
│ │ │ ├── usp-msg-1-2.pb.go
│ │ │ └── usp-msg-1-2.proto
│ │ │ └── usp_record
│ │ │ ├── usp-record-1-2.pb.go
│ │ │ └── usp-record-1-2.proto
│ ├── mqtt-adapter
│ │ ├── .env
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── build
│ │ │ ├── Dockerfile
│ │ │ └── Makefile
│ │ ├── cmd
│ │ │ └── mqtt-adapter
│ │ │ │ └── main.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── internal
│ │ │ ├── bridge
│ │ │ └── bridge.go
│ │ │ ├── config
│ │ │ └── config.go
│ │ │ └── nats
│ │ │ └── nats.go
│ ├── mqtt
│ │ ├── .env
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── build
│ │ │ ├── Dockerfile
│ │ │ └── Makefile
│ │ ├── cmd
│ │ │ └── mqtt
│ │ │ │ └── main.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── internal
│ │ │ ├── config
│ │ │ └── config.go
│ │ │ ├── listeners
│ │ │ ├── http
│ │ │ │ └── http.go
│ │ │ ├── listeners.go
│ │ │ ├── mqtt
│ │ │ │ ├── hook.go
│ │ │ │ └── mqtt.go
│ │ │ └── ws
│ │ │ │ └── ws.go
│ │ │ └── nats
│ │ │ └── nats.go
│ ├── stomp-adapter
│ │ ├── .env
│ │ ├── .gitignore
│ │ ├── build
│ │ │ ├── Dockerfile
│ │ │ └── Makefile
│ │ ├── cmd
│ │ │ └── stomp-adapter
│ │ │ │ └── main.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── internal
│ │ │ ├── bridge
│ │ │ └── bridge.go
│ │ │ ├── config
│ │ │ └── config.go
│ │ │ ├── nats
│ │ │ └── nats.go
│ │ │ └── stomp
│ │ │ ├── .gitattributes
│ │ │ ├── .gitignore
│ │ │ ├── AUTHORS.md
│ │ │ ├── LICENSE.txt
│ │ │ ├── README.md
│ │ │ ├── ack.go
│ │ │ ├── breaking_changes.md
│ │ │ ├── conn.go
│ │ │ ├── conn_options.go
│ │ │ ├── errors.go
│ │ │ ├── frame
│ │ │ ├── ack.go
│ │ │ ├── command.go
│ │ │ ├── encode.go
│ │ │ ├── errors.go
│ │ │ ├── frame.go
│ │ │ ├── header.go
│ │ │ ├── heartbeat.go
│ │ │ ├── reader.go
│ │ │ └── writer.go
│ │ │ ├── id.go
│ │ │ ├── internal
│ │ │ └── log
│ │ │ │ └── stdlogger.go
│ │ │ ├── logger.go
│ │ │ ├── message.go
│ │ │ ├── send_options.go
│ │ │ ├── stomp.go
│ │ │ ├── subscribe_options.go
│ │ │ ├── subscription.go
│ │ │ ├── transaction.go
│ │ │ ├── validator.go
│ │ │ └── version.go
│ ├── stomp
│ │ ├── .env
│ │ ├── .gitignore
│ │ ├── AUTHORS.md
│ │ ├── LICENSE.txt
│ │ ├── README.md
│ │ ├── ack.go
│ │ ├── breaking_changes.md
│ │ ├── build
│ │ │ ├── Dockerfile
│ │ │ └── Makefile
│ │ ├── cmd
│ │ │ └── stomp
│ │ │ │ └── main.go
│ │ ├── conn.go
│ │ ├── conn_options.go
│ │ ├── conn_test.go
│ │ ├── errors.go
│ │ ├── example_test.go
│ │ ├── examples
│ │ │ └── client_test
│ │ │ │ └── main.go
│ │ ├── frame
│ │ │ ├── ack.go
│ │ │ ├── command.go
│ │ │ ├── encode.go
│ │ │ ├── encode_test.go
│ │ │ ├── errors.go
│ │ │ ├── frame.go
│ │ │ ├── frame_test.go
│ │ │ ├── header.go
│ │ │ ├── header_test.go
│ │ │ ├── heartbeat.go
│ │ │ ├── heartbeat_test.go
│ │ │ ├── reader.go
│ │ │ ├── reader_test.go
│ │ │ ├── writer.go
│ │ │ └── writer_test.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── id.go
│ │ ├── id_test.go
│ │ ├── internal
│ │ │ └── log
│ │ │ │ └── stdlogger.go
│ │ ├── logger.go
│ │ ├── message.go
│ │ ├── send_options.go
│ │ ├── server
│ │ │ ├── client
│ │ │ │ ├── channel_test.go
│ │ │ │ ├── client.go
│ │ │ │ ├── client_test.go
│ │ │ │ ├── config.go
│ │ │ │ ├── conn.go
│ │ │ │ ├── errors.go
│ │ │ │ ├── frame.go
│ │ │ │ ├── frame_test.go
│ │ │ │ ├── request.go
│ │ │ │ ├── subscription.go
│ │ │ │ ├── subscription_list.go
│ │ │ │ ├── subscription_list_test.go
│ │ │ │ ├── tx_store.go
│ │ │ │ ├── tx_store_test.go
│ │ │ │ ├── util.go
│ │ │ │ └── util_test.go
│ │ │ ├── processor.go
│ │ │ ├── queue
│ │ │ │ ├── manager.go
│ │ │ │ ├── manager_test.go
│ │ │ │ ├── memory_queue.go
│ │ │ │ ├── memory_queue_test.go
│ │ │ │ ├── queue.go
│ │ │ │ ├── queue_test.go
│ │ │ │ └── storage.go
│ │ │ ├── queue_storage.go
│ │ │ ├── server.go
│ │ │ ├── server_test.go
│ │ │ └── topic
│ │ │ │ ├── manager.go
│ │ │ │ ├── manager_test.go
│ │ │ │ ├── subscription.go
│ │ │ │ ├── testing_test.go
│ │ │ │ ├── topic.go
│ │ │ │ └── topic_test.go
│ │ ├── stomp.go
│ │ ├── stomp_test.go
│ │ ├── stompd
│ │ │ ├── main.go
│ │ │ ├── signals.go
│ │ │ ├── signals_unix.go
│ │ │ ├── signals_windows.go
│ │ │ └── stompd
│ │ ├── subscribe_options.go
│ │ ├── subscription.go
│ │ ├── testutil
│ │ │ ├── fake_conn.go
│ │ │ ├── fake_conn_test.go
│ │ │ ├── mock_logger.go
│ │ │ └── testutil.go
│ │ ├── transaction.go
│ │ ├── validator.go
│ │ ├── version.go
│ │ └── version_test.go
│ ├── ws-adapter
│ │ ├── .env
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── build
│ │ │ ├── Dockerfile
│ │ │ └── Makefile
│ │ ├── cmd
│ │ │ └── ws-adapter
│ │ │ │ └── main.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── internal
│ │ │ ├── bridge
│ │ │ └── bridge.go
│ │ │ ├── config
│ │ │ └── config.go
│ │ │ ├── nats
│ │ │ └── nats.go
│ │ │ └── usp
│ │ │ ├── usp.go
│ │ │ ├── usp_msg
│ │ │ ├── usp-msg-1-3.pb.go
│ │ │ └── usp-msg-1-3.proto
│ │ │ └── usp_record
│ │ │ ├── usp-record-1-3.pb.go
│ │ │ └── usp-record-1-3.proto
│ └── ws
│ │ ├── .env
│ │ ├── .gitignore
│ │ ├── build
│ │ ├── Dockerfile
│ │ └── Makefile
│ │ ├── cmd
│ │ └── ws
│ │ │ └── main.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── internal
│ │ ├── config
│ │ └── config.go
│ │ ├── nats
│ │ └── nats.go
│ │ ├── usp_record
│ │ ├── usp-record-1-3.pb.go
│ │ └── usp-record-1-3.proto
│ │ └── ws
│ │ ├── handler
│ │ ├── client.go
│ │ └── hub.go
│ │ └── ws.go
│ └── utils
│ ├── file-server
│ ├── .env
│ ├── .gitignore
│ ├── build
│ │ ├── Dockerfile
│ │ └── Makefile
│ ├── go.mod
│ ├── go.sum
│ └── main.go
│ └── socketio
│ ├── .env
│ ├── .gitignore
│ ├── build
│ ├── Dockerfile
│ └── Makefile
│ ├── package-lock.json
│ ├── package.json
│ └── server.js
├── build
└── Makefile
├── deploy
├── compose
│ ├── .env.acs
│ ├── .env.adapter
│ ├── .env.controller
│ ├── .env.file-server
│ ├── .env.mqtt
│ ├── .env.mqtt-adapter
│ ├── .env.nats
│ ├── .env.socketio
│ ├── .env.stomp-adapter
│ ├── .env.ws
│ ├── .env.ws-adapter
│ ├── .gitignore
│ ├── docker-compose.yaml
│ ├── firmwares
│ │ └── .gitkeep
│ ├── images
│ │ └── logo.png
│ ├── mongo_data
│ │ └── .gitkeep
│ ├── nats_config
│ │ ├── cert.pem
│ │ ├── key.pem
│ │ ├── nats.cfg
│ │ └── rootCA.pem
│ ├── nats_data
│ │ └── .gitkeep
│ ├── nginx.conf
│ ├── portainer_data
│ │ └── .gitkeep
│ ├── run.sh
│ └── stop.sh
└── kubernetes
│ ├── README.md
│ ├── adapter.yaml
│ ├── controller.yaml
│ ├── frontend.yaml
│ ├── ingress.yaml
│ ├── mongodb.yaml
│ ├── mqtt-adapter.yaml
│ ├── mqtt.yaml
│ ├── socketio.yaml
│ ├── ws-adapter.yaml
│ └── ws.yaml
└── frontend
├── .env
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── README.md
├── build
├── .dockerignore
├── Dockerfile
└── Makefile
├── jsconfig.json
├── next.config.js
├── package-lock.json
├── package.json
├── public
├── assets
│ ├── avatars
│ │ └── default-avatar.png
│ ├── errors
│ │ ├── error-401.png
│ │ ├── error-404.png
│ │ └── error-500.png
│ ├── general
│ │ └── github-mark.png
│ ├── logo.png
│ └── mtp
│ │ ├── boot-stomp.svg
│ │ └── websocket.svg
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
└── manifest.json
└── src
├── components
├── chart.js
├── logo.js
├── scrollbar.js
├── severity-pill.js
└── visually-hidden-input.js
├── contexts
├── auth-context.js
├── backend-context.js
├── error-context.js
└── socketio-context.js
├── guards
└── auth-guard.js
├── hocs
└── with-auth-guard.js
├── hooks
├── use-auth.js
├── use-mocked-user.js
├── use-nprogress.js
├── use-popover.js
└── use-selection.js
├── layouts
├── auth
│ └── layout.js
└── dashboard
│ ├── account-popover.js
│ ├── config.js
│ ├── layout.js
│ ├── side-nav-item.js
│ ├── side-nav.js
│ └── top-nav.js
├── pages
├── 403.js
├── 404.js
├── _app.js
├── _document.js
├── access-control
│ └── users.js
├── account.js
├── auth
│ ├── login.js
│ └── register.js
├── chat.js
├── chat
│ └── room.js
├── companies.js
├── credentials.js
├── devices.js
├── devices
│ ├── cwmp
│ │ └── [...id].js
│ └── usp
│ │ └── [...id].js
├── index.js
└── settings.js
├── sections
├── account
│ ├── account-profile-details.js
│ └── account-profile.js
├── companies
│ ├── companies-search.js
│ └── company-card.js
├── credentials
│ └── credentials-table.js
├── customer
│ ├── customers-search.js
│ └── customers-table.js
├── devices
│ ├── cwmp
│ │ ├── devices-rpc.js
│ │ └── devices-wifi.js
│ └── usp
│ │ ├── devices-discovery.js
│ │ └── devices-rpc.js
├── overview
│ ├── overview-budget.js
│ ├── overview-latest-orders.js
│ ├── overview-latest-products.js
│ ├── overview-sales.js
│ ├── overview-tasks-progress.js
│ ├── overview-total-customers.js
│ ├── overview-total-profit.js
│ └── overview-traffic.js
└── settings
│ ├── color-theme.js
│ ├── settings-notifications.js
│ └── settings-password.js
├── theme
├── colors.js
├── create-components.js
├── create-palette.js
├── create-shadows.js
├── create-typography.js
└── index.js
└── utils
├── apply-pagination.js
├── create-emotion-cache.js
├── create-resource-id.js
├── get-initials.js
├── map.css
└── mapStyles.json
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | jobs:
3 | release:
4 | docker:
5 | - image: cimg/base:2022.09
6 | auth:
7 | username: $DOCKERHUB_USERNAME
8 | password: $DOCKERHUB_PASSWORD
9 | steps:
10 | - checkout
11 | - setup_remote_docker
12 | - run:
13 | name: Build and Push application Docker image
14 | command: |
15 | echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin
16 | cd build && make release
17 | workflows:
18 | release:
19 | jobs:
20 | - release:
21 | filters:
22 | branches:
23 | only: main
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.exe
2 | *.exe~
3 | *.dll
4 | *.so
5 | *.dylib
6 | main
7 | *.test
8 | *.out
9 | go.work
10 | *.log
11 | *.db
12 | *.crt
13 | *.key
14 | *.csr
15 | *.db.new
16 | *.pwd
17 | *.acl
18 | .idea
19 | .vscode
20 | main
21 | mochi
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://dl.circleci.com/status-badge/redirect/gh/OktopUSP/oktopus/tree/main)
2 |
3 |
4 |
5 |
6 | Oktopus is an Open Source USP Controller and CWMP compatible multi-vendor management platform for CPEs and IoTs. Any device that follows the TR-369 or TR-069 protocol can be managed. Our main objective is to unify device management into a single software with rich insights and configuration capabilites, increasing the quality of experience and services provided by a CSP or ISP company at the same that it reduces costs of technical support, operations, and maintenance.
7 |
8 |
9 | Documentation
10 | Official Website
11 | Official Documentation
12 |
--------------------------------------------------------------------------------
/agent/.gitignore:
--------------------------------------------------------------------------------
1 | obuspa/
2 | *.local
--------------------------------------------------------------------------------
/agent/Dockerfile:
--------------------------------------------------------------------------------
1 | #
2 | # file Dockerfile
3 | #
4 | # Base docker image with all required dependencies for OB-USP-A
5 | #
6 | # Based on Ubuntu 22.10 (Kinetic Kudu), which provides libmosquitto 2.0.11 and libwebsockets 4.1.6
7 | # This image includes some basic compilation tools (automake, autoconf)
8 | #
9 | # One-liner execution line (straightforward build for OB-USP-A execution):
10 | # > docker build -f Dockerfile -t obuspa:latest .
11 | #
12 | # Multi-stage builds execution lines (to tag build stages):
13 | # 1) Create the build environment image:
14 | # > docker build -f Dockerfile -t obuspa:build-env --target build-env .
15 | # 2) Create the OB-USP-A image, then build the application
16 | # > docker build -f Dockerfile -t obuspa:latest --target runner .
17 | #
18 | FROM ubuntu:lunar AS build-env
19 |
20 | # Install dependencies
21 | RUN apt-get update && apt-get -y install \
22 | libssl-dev \
23 | libcurl4-openssl-dev\
24 | libsqlite3-dev \
25 | libz-dev \
26 | autoconf \
27 | automake \
28 | libtool \
29 | libmosquitto-dev \
30 | pkg-config \
31 | make \
32 | git \
33 | cmake \
34 | g++ \
35 | && apt-get clean
36 |
37 | RUN git clone https://github.com/warmcat/libwebsockets.git /tmp/libwebsockets && \
38 | cd /tmp/libwebsockets && \
39 | mkdir build && \
40 | cd build && \
41 | cmake .. && \
42 | make && \
43 | make install && \
44 | ldconfig
45 |
46 | FROM build-env AS runner
47 |
48 | ENV MAKE_JOBS=8
49 | ENV OBUSPA_ARGS="-v4"
50 |
51 | # Copy in all of the code
52 | # Then compile, as root.
53 | COPY obuspa /obuspa
54 | RUN cd /obuspa/ && \
55 | autoreconf -fi && \
56 | ./configure && \
57 | make -j${MAKE_JOBS} && \
58 | make install
59 |
60 | # Then delete the code
61 | # that's no longer needed
62 | RUN rm -rf /obuspa
63 |
64 | # Run obuspa with args expanded
65 | CMD obuspa ${OBUSPA_ARGS}
66 |
--------------------------------------------------------------------------------
/agent/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: help build release
2 |
3 | DOCKER_USER ?= oktopusp
4 | DOCKER_APP ?= obuspa
5 | DOCKER_TAG ?= $(shell git log --format="%h" -n 1)
6 | GIT_REPO ?= https://github.com/BroadbandForum/obuspa
7 |
8 | .DEFAULT_GOAL := help
9 |
10 | help:
11 | @echo "Makefile arguments:"
12 | @echo ""
13 | @echo "DOCKER_USER - docker user to build image"
14 | @echo "DOCKER_APP - docker image name"
15 | @echo "DOCKER_TAG - docker image tag"
16 | @echo ""
17 | @echo "Makefile commands:"
18 | @echo ""
19 | @echo "build - docker image build"
20 | @echo "release - tag image as latest and push to registry"
21 |
22 | build:
23 | if [ -d "obuspa" ]; then \
24 | git -C obuspa pull; \
25 | else \
26 | git clone ${GIT_REPO} ; \
27 | fi
28 | @docker build -t ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} -f Dockerfile .
29 |
30 | release: build
31 | @docker push ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG}
32 | @docker tag ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} ${DOCKER_USER}/${DOCKER_APP}:latest
33 | @docker push ${DOCKER_USER}/${DOCKER_APP}:latest
--------------------------------------------------------------------------------
/backend/services/acs/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/backend/services/acs/.env
--------------------------------------------------------------------------------
/backend/services/acs/.gitignore:
--------------------------------------------------------------------------------
1 | *.local
--------------------------------------------------------------------------------
/backend/services/acs/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o acs cmd/acs/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/acs /
8 | ENTRYPOINT ["/acs"]
--------------------------------------------------------------------------------
/backend/services/acs/cmd/acs/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "oktopUSP/backend/services/acs/internal/bridge"
5 | "oktopUSP/backend/services/acs/internal/config"
6 | "oktopUSP/backend/services/acs/internal/nats"
7 | "oktopUSP/backend/services/acs/internal/server"
8 | "oktopUSP/backend/services/acs/internal/server/handler"
9 | )
10 |
11 | func main() {
12 |
13 | c := config.NewConfig()
14 |
15 | natsActions := nats.StartNatsClient(c.Nats)
16 |
17 | h := handler.NewHandler(natsActions.Publish, natsActions.Subscribe, c.Acs)
18 |
19 | b := bridge.NewBridge(
20 | natsActions.Publish,
21 | natsActions.Subscribe,
22 | h,
23 | &c.Acs,
24 | )
25 | b.StartBridge()
26 |
27 | server.Run(c.Acs, natsActions, h)
28 | }
29 |
--------------------------------------------------------------------------------
/backend/services/acs/go.mod:
--------------------------------------------------------------------------------
1 | module oktopUSP/backend/services/acs
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/google/uuid v1.6.0
9 | github.com/joho/godotenv v1.5.1
10 | github.com/nats-io/nats.go v1.35.0
11 | github.com/oleiade/lane v1.0.1
12 | golang.org/x/sys v0.33.0
13 | )
14 |
15 | require (
16 | github.com/klauspost/compress v1.17.8 // indirect
17 | github.com/nats-io/nkeys v0.4.7 // indirect
18 | github.com/nats-io/nuid v1.0.1 // indirect
19 | golang.org/x/crypto v0.38.0 // indirect
20 | golang.org/x/text v0.25.0 // indirect
21 | )
22 |
--------------------------------------------------------------------------------
/backend/services/acs/go.sum:
--------------------------------------------------------------------------------
1 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
3 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
4 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
5 | github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
6 | github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
7 | github.com/nats-io/nats.go v1.35.0 h1:XFNqNM7v5B+MQMKqVGAyHwYhyKb48jrenXNxIU20ULk=
8 | github.com/nats-io/nats.go v1.35.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
9 | github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
10 | github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
11 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
12 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
13 | github.com/oleiade/lane v1.0.1 h1:hXofkn7GEOubzTwNpeL9MaNy8WxolCYb9cInAIeqShU=
14 | github.com/oleiade/lane v1.0.1/go.mod h1:IyTkraa4maLfjq/GmHR+Dxb4kCMtEGeb+qmhlrQ5Mk4=
15 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
16 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
17 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
18 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
19 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
20 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
21 |
--------------------------------------------------------------------------------
/backend/services/acs/internal/server/handler/handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "oktopUSP/backend/services/acs/internal/config"
6 | "time"
7 |
8 | "github.com/nats-io/nats.go"
9 | "github.com/oleiade/lane"
10 | )
11 |
12 | const Version = "1.0.0"
13 |
14 | type Request struct {
15 | Id string
16 | User string
17 | Password string
18 | CwmpMsg []byte
19 | Time time.Time
20 | Callback chan []byte
21 | }
22 |
23 | type CPE struct {
24 | SerialNumber string
25 | Manufacturer string
26 | OUI string
27 | ConnectionRequestURL string
28 | SoftwareVersion string
29 | ExternalIPAddress string
30 | Queue *lane.Queue
31 | Waiting *Request
32 | HardwareVersion string
33 | LastConnection time.Time
34 | DataModel string
35 | Username string
36 | Password string
37 | }
38 |
39 | type Message struct {
40 | SerialNumber string
41 | Message string
42 | }
43 |
44 | type WsMessage struct {
45 | Cmd string
46 | }
47 |
48 | type NatsSendMessage struct {
49 | MsgType string
50 | Data json.RawMessage
51 | }
52 |
53 | type MsgCPEs struct {
54 | CPES map[string]CPE
55 | }
56 |
57 | type Handler struct {
58 | pub func(string, []byte) error
59 | sub func(string, func(*nats.Msg)) error
60 | Cpes map[string]CPE
61 | acsConfig config.Acs
62 | }
63 |
64 | const (
65 | NATS_CWMP_SUBJECT_PREFIX = "cwmp.v1."
66 | NATS_CWMP_ADAPTER_SUBJECT_PREFIX = "cwmp-adapter.v1."
67 | NATS_ADAPTER_SUBJECT_PREFIX = "adapter.v1."
68 | )
69 |
70 | func NewHandler(
71 | pub func(string, []byte) error,
72 | sub func(string, func(*nats.Msg)) error,
73 | cAcs config.Acs,
74 | ) *Handler {
75 | return &Handler{
76 | pub: pub,
77 | sub: sub,
78 | Cpes: make(map[string]CPE),
79 | acsConfig: cAcs,
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/backend/services/acs/internal/server/handler/status.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "log"
5 | "time"
6 | )
7 |
8 | func (h *Handler) HandleCpeStatus() {
9 | for {
10 | for cpe := range h.Cpes {
11 | if cpe == "" {
12 | continue
13 | }
14 | if h.acsConfig.DebugMode {
15 | log.Println("Checking CPE " + cpe + " status")
16 | }
17 | if time.Since(h.Cpes[cpe].LastConnection) > h.acsConfig.KeepAliveInterval {
18 | log.Printf("LastConnection: %s, KeepAliveInterval: %s", h.Cpes[cpe].LastConnection, h.acsConfig.KeepAliveInterval)
19 | log.Println("CPE", cpe, "is offline")
20 | h.pub("cwmp.v1."+cpe+".status", []byte("0"))
21 | delete(h.Cpes, cpe)
22 | break
23 | }
24 | }
25 | time.Sleep(10 * time.Second)
26 | }
27 | }
--------------------------------------------------------------------------------
/backend/services/acs/internal/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "oktopUSP/backend/services/acs/internal/config"
7 | "oktopUSP/backend/services/acs/internal/nats"
8 | "oktopUSP/backend/services/acs/internal/server/handler"
9 | "os"
10 | )
11 |
12 | func Run(c config.Acs, natsActions nats.NatsActions, h *handler.Handler) {
13 |
14 | http.HandleFunc(c.Route, h.CwmpHandler)
15 | go h.HandleCpeStatus()
16 |
17 | log.Printf("ACS running at %s%s", c.Port, c.Route)
18 |
19 | err := http.ListenAndServe(c.Port, nil)
20 | if err != nil {
21 | log.Fatal(err)
22 | os.Exit(1)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/.gitignore:
--------------------------------------------------------------------------------
1 | /.env.local
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22@sha256:82e07063a1ac3ee59e6f38b1222e32ce88469e4431ff6496cc40fb9a0fc18229 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o http-bulk-collector cmd/http-bulk-collector/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/http-bulk-collector /
8 | ENTRYPOINT ["/bulkdata"]
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/cmd/http-bulk-collector/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/oktopUSP/backend/services/bulkdata/internal/api"
10 | "github.com/oktopUSP/backend/services/bulkdata/internal/bridge"
11 | "github.com/oktopUSP/backend/services/bulkdata/internal/config"
12 | "github.com/oktopUSP/backend/services/bulkdata/internal/nats"
13 | )
14 |
15 | func main() {
16 | done := make(chan os.Signal, 1)
17 |
18 | signal.Notify(done, syscall.SIGINT)
19 |
20 | c := config.NewConfig()
21 |
22 | pub, sub := nats.StartNatsClient(c.Nats)
23 |
24 | b := bridge.NewBridge(pub, sub)
25 |
26 | server := api.NewApi(c.RestApi, b)
27 |
28 | server.StartApi()
29 |
30 | <-done
31 | log.Println("bulk data collector is saying adios ...")
32 | }
33 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/oktopUSP/backend/services/bulkdata
2 |
3 | go 1.22.3
4 | toolchain go1.24.1
5 |
6 | require (
7 | github.com/gorilla/mux v1.8.1
8 | github.com/joho/godotenv v1.5.1
9 | github.com/nats-io/nats.go v1.34.1
10 | github.com/rs/cors v1.11.0
11 | )
12 |
13 | require (
14 | github.com/klauspost/compress v1.17.2 // indirect
15 | github.com/nats-io/nkeys v0.4.7 // indirect
16 | github.com/nats-io/nuid v1.0.1 // indirect
17 | golang.org/x/crypto v0.35.0 // indirect
18 | golang.org/x/sys v0.30.0 // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
2 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
3 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
4 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
5 | github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
6 | github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
7 | github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4=
8 | github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
9 | github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
10 | github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
11 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
12 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
13 | github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
14 | github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
15 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
16 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
17 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
18 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
19 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/internal/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/gorilla/mux"
9 | "github.com/oktopUSP/backend/services/bulkdata/internal/api/cors"
10 | "github.com/oktopUSP/backend/services/bulkdata/internal/api/handler"
11 | "github.com/oktopUSP/backend/services/bulkdata/internal/api/middleware"
12 | "github.com/oktopUSP/backend/services/bulkdata/internal/bridge"
13 | "github.com/oktopUSP/backend/services/bulkdata/internal/config"
14 | )
15 |
16 | type Api struct {
17 | port string
18 | handler handler.Handler
19 | }
20 |
21 | const REQUEST_TIMEOUT = time.Second * 30
22 |
23 | func NewApi(c config.RestApi, b *bridge.Bridge) Api {
24 | return Api{
25 | port: c.Port,
26 | handler: handler.NewHandler(c.Ctx, b),
27 | }
28 | }
29 |
30 | func (a *Api) StartApi() {
31 | r := mux.NewRouter()
32 | r.HandleFunc("/healthcheck", a.handler.Healthcheck).Methods("GET")
33 | r.HandleFunc("/", a.handler.Data).Methods("POST")
34 |
35 | /* ----- Middleware for requests which requires user to be authenticated ---- */
36 | r.Use(func(handler http.Handler) http.Handler {
37 | return middleware.Middleware(handler)
38 | })
39 | /* -------------------------------------------------------------------------- */
40 |
41 | corsOpts := cors.GetCorsConfig()
42 |
43 | srv := &http.Server{
44 | Addr: "0.0.0.0:" + a.port,
45 | WriteTimeout: time.Second * 60,
46 | ReadTimeout: time.Second * 60,
47 | IdleTimeout: time.Second * 60,
48 | Handler: corsOpts.Handler(r),
49 | }
50 |
51 | go func() {
52 | if err := srv.ListenAndServe(); err != nil {
53 | log.Println(err)
54 | }
55 | }()
56 | log.Println("Running Bulk Data Collector HTTP Server at port", a.port)
57 | }
58 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/internal/api/cors/cors.go:
--------------------------------------------------------------------------------
1 | package cors
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "os"
7 | "strings"
8 |
9 | "github.com/rs/cors"
10 | )
11 |
12 | func GetCorsConfig() cors.Cors {
13 | allowedOrigins := getCorsEnvConfig()
14 | log.Println("API CORS - AllowedOrigins:", allowedOrigins)
15 | return *cors.New(cors.Options{
16 | AllowedOrigins: allowedOrigins,
17 | AllowedMethods: []string{
18 | http.MethodGet,
19 | http.MethodPost,
20 | http.MethodPut,
21 | http.MethodPatch,
22 | http.MethodDelete,
23 | http.MethodOptions,
24 | http.MethodHead,
25 | },
26 |
27 | AllowedHeaders: []string{
28 | "*", //or you can your header key values which you are using in your application
29 | },
30 | })
31 | }
32 |
33 | func getCorsEnvConfig() []string {
34 | val, _ := os.LookupEnv("REST_API_CORS")
35 | if val == "" {
36 | return []string{"*"}
37 | }
38 | return strings.Split(val, ",")
39 | }
40 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/internal/api/handler/data.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "net/http"
7 | )
8 |
9 | func (h *Handler) Data(w http.ResponseWriter, r *http.Request) {
10 |
11 | oui := r.URL.Query().Get("oui")
12 | pc := r.URL.Query().Get("pc")
13 | sn := r.URL.Query().Get("sn")
14 | eid := r.URL.Query().Get("eid")
15 |
16 | log.Println("oui: ", oui)
17 | log.Println("pc: ", pc)
18 | log.Println("sn: ", sn)
19 | log.Println("eid: ", eid)
20 |
21 | var body map[string]interface{}
22 |
23 | err := json.NewDecoder(r.Body).Decode(&body)
24 | if err != nil {
25 | log.Println(err)
26 | w.WriteHeader(http.StatusBadRequest)
27 | return
28 | }
29 |
30 | log.Println("Body: ", body)
31 | }
32 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/internal/api/handler/handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/oktopUSP/backend/services/bulkdata/internal/bridge"
7 | )
8 |
9 | type Handler struct {
10 | ctx context.Context
11 | b *bridge.Bridge
12 | }
13 |
14 | func NewHandler(ctx context.Context, b *bridge.Bridge) Handler {
15 | return Handler{
16 | ctx: ctx,
17 | b: b,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/internal/api/handler/healthcheck.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import "net/http"
4 |
5 | func (h *Handler) Healthcheck(w http.ResponseWriter, r *http.Request) {
6 | w.Write([]byte("I'm Alive"))
7 | }
8 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/internal/api/middleware/middleware.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | func Middleware(next http.Handler) http.Handler {
8 | return http.HandlerFunc(
9 | func(w http.ResponseWriter, r *http.Request) {
10 | w.Header().Set("Content-Type", "application/json")
11 |
12 | // tokenString := r.Header.Get("Authorization")
13 | // if tokenString == "" {
14 | // w.WriteHeader(http.StatusUnauthorized)
15 | // return
16 | // }
17 | // email, err := auth.ValidateToken(tokenString)
18 | // if err != nil {
19 | // w.WriteHeader(http.StatusUnauthorized)
20 | // return
21 | // }
22 | //ctx := context.WithValue(r.Context(), "email", email)
23 | next.ServeHTTP(w, r.WithContext(r.Context()))
24 | },
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/backend/services/bulkdata/http/internal/bridge/bridge.go:
--------------------------------------------------------------------------------
1 | package bridge
2 |
3 | import (
4 | "github.com/nats-io/nats.go"
5 | )
6 |
7 | type (
8 | Publisher func(string, []byte) error
9 | Subscriber func(string, func(*nats.Msg)) error
10 | )
11 |
12 | type Bridge struct {
13 | pub Publisher
14 | sub Subscriber
15 | }
16 |
17 | const BULK_DATA_SUBJECT = "bulk"
18 |
19 | func NewBridge(p Publisher, s Subscriber) *Bridge {
20 | return &Bridge{
21 | pub: p,
22 | sub: s,
23 | }
24 | }
25 |
26 | func (b *Bridge) SendDeviceData(deviceId string, payload []byte) error {
27 | return b.pub("oi", payload)
28 | }
29 |
--------------------------------------------------------------------------------
/backend/services/controller/.env:
--------------------------------------------------------------------------------
1 | # --------------------------------- database --------------------------------- #
2 | MONGO_URI="" # example: mongodb://localhost:27017
3 | # ---------------------------------------------------------------------------- #
4 |
5 | # ----------------------------------- mqtt ----------------------------------- #
6 | DEVICES_STATUS_TOPIC=""
7 | DEVICE_PUB_TOPIC=""
8 | BROKER_ADDR=""
9 | BROKER_PORT=""
10 | BROKER_TLS=""
11 | BROKER_USERNAME=""
12 | BROKER_PASSWORD=""
13 | BROKER_CLIENTID=""
14 | BROKER_QOS=""
15 | MQTT_DISABLE=""
16 | # ---------------------------------------------------------------------------- #
17 |
18 | # --------------------------------- api rest --------------------------------- #
19 | SECRET_API_KEY="secretkey" # !IMPORTANT: Change this to your own secret key, and don't share.
20 | REST_API_PORT=""
21 | REST_API_CORS="" # addresses must be separated by commas example: "http://localhost:3000,http://myapp.com"
22 | # ---------------------------------------------------------------------------- #
23 |
24 | # ----------------------------------- stomp ---------------------------------- #
25 | STOMP_ADDR="" # example: localhost:61613
26 | STOMP_USERNAME=""
27 | STOMP_PASSWORD=""
28 | STOMP_DISABLE=""
29 | # ---------------------------------------------------------------------------- #
30 |
31 | # -------------------------------- websockets -------------------------------- #
32 | WS_ADDR=""
33 | WS_PORT=""
34 | WS_TOKEN=""
35 | WS_TLS=""
36 | WS_AUTH=""
37 | WS_ROUTE=""
38 | WS_DISABLE=""
39 | WS_SKIP_VERIFY=""
40 | # ---------------------------------------------------------------------------- #
--------------------------------------------------------------------------------
/backend/services/controller/.gitignore:
--------------------------------------------------------------------------------
1 | /.env.local
2 | run.prod.sh
3 | /tmp
--------------------------------------------------------------------------------
/backend/services/controller/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o controller cmd/controller/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/controller /
8 | ENTRYPOINT ["/controller"]
--------------------------------------------------------------------------------
/backend/services/controller/cmd/controller/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/leandrofars/oktopus/internal/api"
10 | "github.com/leandrofars/oktopus/internal/bridge"
11 | "github.com/leandrofars/oktopus/internal/config"
12 | "github.com/leandrofars/oktopus/internal/db"
13 | "github.com/leandrofars/oktopus/internal/nats"
14 | )
15 |
16 | func main() {
17 | done := make(chan os.Signal, 1)
18 |
19 | signal.Notify(done, syscall.SIGINT)
20 |
21 | c := config.NewConfig()
22 |
23 | js, nc, kv := nats.StartNatsClient(c.Nats)
24 |
25 | bridge := bridge.NewBridge(js, nc)
26 |
27 | db := db.NewDatabase(c.Mongo.Ctx, c.Mongo.Uri)
28 |
29 | api := api.NewApi(c, js, nc, bridge, db, kv)
30 | api.StartApi()
31 |
32 | <-done
33 |
34 | log.Println("rest api is shutting down...")
35 | }
36 |
--------------------------------------------------------------------------------
/backend/services/controller/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/leandrofars/oktopus
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/golang-jwt/jwt/v5 v5.2.2
9 | github.com/google/uuid v1.3.0
10 | github.com/gorilla/mux v1.8.0
11 | github.com/joho/godotenv v1.5.1
12 | github.com/nats-io/nats.go v1.33.1
13 | github.com/rs/cors v1.11.0
14 | go.mongodb.org/mongo-driver v1.11.3
15 | golang.org/x/crypto v0.36.0
16 | golang.org/x/net v0.38.0
17 | google.golang.org/protobuf v1.33.0
18 | )
19 |
20 | require (
21 | github.com/golang/snappy v0.0.1 // indirect
22 | github.com/klauspost/compress v1.17.2 // indirect
23 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
24 | github.com/nats-io/nkeys v0.4.7 // indirect
25 | github.com/nats-io/nuid v1.0.1 // indirect
26 | github.com/pkg/errors v0.9.1 // indirect
27 | github.com/stretchr/testify v1.7.0 // indirect
28 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
29 | github.com/xdg-go/scram v1.1.1 // indirect
30 | github.com/xdg-go/stringprep v1.0.3 // indirect
31 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
32 | golang.org/x/sync v0.12.0 // indirect
33 | golang.org/x/sys v0.31.0 // indirect
34 | golang.org/x/text v0.23.0 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/api/auth/auth.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "os"
8 | "time"
9 |
10 | "github.com/golang-jwt/jwt/v5"
11 | )
12 |
13 | func getJwtKey() []byte {
14 | jwtKey, ok := os.LookupEnv("SECRET_API_KEY")
15 | if !ok || jwtKey == "" {
16 | return []byte("supersecretkey")
17 | }
18 | return []byte(jwtKey)
19 | }
20 |
21 | type JWTClaim struct {
22 | Username string `json:"username"`
23 | Email string `json:"email"`
24 | jwt.RegisteredClaims
25 | }
26 |
27 | func GenerateJWT(email string, username string) (tokenString string, err error) {
28 | expirationTime := time.Now().Add(24 * time.Hour)
29 | claims := &JWTClaim{
30 | username,
31 | email,
32 | jwt.RegisteredClaims{
33 | ExpiresAt: jwt.NewNumericDate(expirationTime),
34 | Issuer: "Oktopus",
35 | },
36 | }
37 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
38 | tokenString, err = token.SignedString(getJwtKey())
39 | return
40 | }
41 |
42 | func ValidateToken(signedToken string) (email string, err error) {
43 | token, err := jwt.ParseWithClaims(
44 | signedToken,
45 | &JWTClaim{},
46 | func(token *jwt.Token) (interface{}, error) {
47 | // Don't forget to validate the alg is what you expect:
48 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
49 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
50 | }
51 |
52 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
53 | return getJwtKey(), nil
54 | },
55 | )
56 | if err != nil {
57 | log.Println(err)
58 | return
59 | }
60 |
61 | claims, ok := token.Claims.(*JWTClaim)
62 | if !ok {
63 | err = errors.New("couldn't parse claims")
64 | return
65 | }
66 |
67 | email = claims.Email
68 |
69 | return
70 | }
71 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/api/cors/cors.go:
--------------------------------------------------------------------------------
1 | package cors
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "os"
7 | "strings"
8 |
9 | "github.com/rs/cors"
10 | )
11 |
12 | func GetCorsConfig() cors.Cors {
13 | allowedOrigins := getCorsEnvConfig()
14 | log.Println("API CORS - AllowedOrigins:", allowedOrigins)
15 | return *cors.New(cors.Options{
16 | AllowedOrigins: allowedOrigins,
17 | AllowedMethods: []string{
18 | http.MethodGet,
19 | http.MethodPost,
20 | http.MethodPut,
21 | http.MethodPatch,
22 | http.MethodDelete,
23 | http.MethodOptions,
24 | http.MethodHead,
25 | },
26 |
27 | AllowedHeaders: []string{
28 | "*", //or you can your header key values which you are using in your application
29 | },
30 | })
31 | }
32 |
33 | func getCorsEnvConfig() []string {
34 | val, _ := os.LookupEnv("REST_API_CORS")
35 | if val == "" {
36 | return []string{"*"}
37 | }
38 | return strings.Split(val, ",")
39 | }
40 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/api/middleware/middleware.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/leandrofars/oktopus/internal/api/auth"
5 | "golang.org/x/net/context"
6 | "net/http"
7 | )
8 |
9 | func Middleware(next http.Handler) http.Handler {
10 | return http.HandlerFunc(
11 | func(w http.ResponseWriter, r *http.Request) {
12 | w.Header().Set("Content-Type", "application/json")
13 |
14 | tokenString := r.Header.Get("Authorization")
15 | if tokenString == "" {
16 | w.WriteHeader(http.StatusUnauthorized)
17 | return
18 | }
19 | email, err := auth.ValidateToken(tokenString)
20 | if err != nil {
21 | w.WriteHeader(http.StatusUnauthorized)
22 | return
23 | }
24 | ctx := context.WithValue(r.Context(), "email", email)
25 | next.ServeHTTP(w, r.WithContext(ctx))
26 | },
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | "go.mongodb.org/mongo-driver/bson"
8 | "go.mongodb.org/mongo-driver/mongo"
9 | "go.mongodb.org/mongo-driver/mongo/options"
10 | )
11 |
12 | type Database struct {
13 | client *mongo.Client
14 | users *mongo.Collection
15 | template *mongo.Collection
16 | ctx context.Context
17 | }
18 |
19 | func NewDatabase(ctx context.Context, mongoUri string) Database {
20 | var db Database
21 |
22 | clientOptions := options.Client().ApplyURI(mongoUri)
23 | client, err := mongo.Connect(ctx, clientOptions)
24 | if err != nil {
25 | log.Fatal(err)
26 | }
27 | db.client = client
28 |
29 | log.Println("Trying to ping Mongo database...")
30 | err = client.Ping(ctx, nil)
31 | if err != nil {
32 | log.Fatal("Couldn't connect to MongoDB --> ", err)
33 | }
34 |
35 | log.Println("Connected to MongoDB-->", mongoUri)
36 |
37 | db.users = client.Database("account-mngr").Collection("users")
38 | indexField := bson.M{"email": 1}
39 | _, err = db.users.Indexes().CreateOne(ctx, mongo.IndexModel{
40 | Keys: indexField,
41 | Options: options.Index().SetUnique(true),
42 | })
43 | if err != nil {
44 | log.Fatalln(err)
45 | }
46 |
47 | db.template = client.Database("general").Collection("templates")
48 | indexField = bson.M{"name": 1}
49 | _, err = db.template.Indexes().CreateOne(ctx, mongo.IndexModel{
50 | Keys: indexField,
51 | Options: options.Index().SetUnique(true),
52 | })
53 | if err != nil {
54 | log.Fatalln(err)
55 | }
56 |
57 | db.ctx = ctx
58 |
59 | return db
60 | }
61 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/entity/device.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | type Device struct {
4 | SN string
5 | Model string
6 | Customer string
7 | Vendor string
8 | Version string
9 | ProductClass string
10 | Alias string
11 | Status Status
12 | Mqtt Status
13 | Stomp Status
14 | Websockets Status
15 | Cwmp Status
16 | }
17 |
18 | type VendorsCount struct {
19 | Vendor string `bson:"_id" json:"vendor"`
20 | Count int `bson:"count" json:"count"`
21 | }
22 |
23 | type ProductClassCount struct {
24 | ProductClass string `bson:"_id" json:"productClass"`
25 | Count int `bson:"count" json:"count"`
26 | }
27 |
28 | type StatusCount struct {
29 | Status int `bson:"_id" json:"status"`
30 | Count int `bson:"count" json:"count"`
31 | }
32 |
33 | type DevicesList struct {
34 | Devices []Device `json:"devices" bson:"documents"`
35 | Total int64 `json:"total"`
36 | }
37 |
38 | type FilterOptions struct {
39 | Models []string `json:"models"`
40 | ProductClasses []string `json:"productClasses"`
41 | Vendors []string `json:"vendors"`
42 | Versions []string `json:"versions"`
43 | }
44 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/entity/msg.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import "time"
4 |
5 | type DataType interface {
6 | []map[string]interface{} | *string | Device | int64 | []Device | []VendorsCount | []ProductClassCount | []StatusCount | time.Duration | []byte | []string | FilterOptions | DevicesList
7 | }
8 |
9 | type MsgAnswer[T DataType] struct {
10 | Code int
11 | Msg T
12 | }
13 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/entity/mtp.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | const (
4 | Mqtt = "mqtt"
5 | Websockets = "ws"
6 | Stomp = "stomp"
7 | )
8 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/entity/status.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | type Status uint8
4 |
5 | const (
6 | Offline Status = iota
7 | Associating
8 | Online
9 | )
10 |
--------------------------------------------------------------------------------
/backend/services/controller/internal/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "log"
7 | )
8 |
9 | func Marshall(data any) []byte {
10 | fmtData, err := json.Marshal(data)
11 | if err != nil {
12 | log.Printf("Error to marshall message into json: %q", err)
13 | return []byte(err.Error())
14 | }
15 | return fmtData
16 | }
17 |
18 | func MarshallEncoder(data any, w io.Writer) {
19 | err := json.NewEncoder(w).Encode(data)
20 | if err != nil {
21 | log.Printf("Error to encode message into json: %q", err)
22 | }
23 | }
24 |
25 | func MarshallDecoder(data any, r io.Reader) error {
26 | err := json.NewDecoder(r).Decode(data)
27 | if err != nil {
28 | log.Printf("Error to decode message into json: %q", err)
29 | }
30 |
31 | return err
32 | }
33 |
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/backend/services/mtp/adapter/.env
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/.gitignore:
--------------------------------------------------------------------------------
1 | *.local
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/README.md:
--------------------------------------------------------------------------------
1 | - Abstracts all other mtps and cwmp/usp existence
2 | - Saves devices info and status
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o adapter cmd/adapter/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/adapter /
8 | ENTRYPOINT ["/adapter"]
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/cmd/adapter/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/config"
10 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db"
11 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events"
12 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events/cwmp_handler"
13 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events/usp_handler"
14 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/nats"
15 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/reqs"
16 | )
17 |
18 | func main() {
19 | done := make(chan os.Signal, 1)
20 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
21 |
22 | c := config.NewConfig()
23 |
24 | js, nc := nats.StartNatsClient(c.Nats, c.Controller)
25 |
26 | db := db.NewDatabase(c.Mongo.Ctx, c.Mongo.Uri)
27 |
28 | usp_handler := usp_handler.NewHandler(nc, js, db, c.Controller.ControllerId)
29 | cwmp_handler := cwmp_handler.NewHandler(nc, js, db, c.Controller.ControllerId)
30 |
31 | events.StartEventsListener(c.Nats.Ctx, js, usp_handler, cwmp_handler)
32 |
33 | reqs.StartRequestsListener(c.Nats.Ctx, nc, db)
34 |
35 | <-done
36 |
37 | log.Println("mtp adapter is shutting down...")
38 | }
39 |
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/OktopUSP/oktopus/backend/services/mtp/adapter
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/google/uuid v1.6.0
9 | github.com/joho/godotenv v1.5.1
10 | github.com/nats-io/nats.go v1.35.0
11 | go.mongodb.org/mongo-driver v1.15.0
12 | google.golang.org/protobuf v1.34.1
13 | )
14 |
15 | require (
16 | github.com/golang/snappy v0.0.4 // indirect
17 | github.com/klauspost/compress v1.17.8 // indirect
18 | github.com/montanaflynn/stats v0.7.1 // indirect
19 | github.com/nats-io/nkeys v0.4.7 // indirect
20 | github.com/nats-io/nuid v1.0.1 // indirect
21 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
22 | github.com/xdg-go/scram v1.1.2 // indirect
23 | github.com/xdg-go/stringprep v1.0.4 // indirect
24 | github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect
25 | golang.org/x/crypto v0.38.0 // indirect
26 | golang.org/x/sync v0.14.0 // indirect
27 | golang.org/x/sys v0.33.0 // indirect
28 | golang.org/x/text v0.25.0 // indirect
29 | )
30 |
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/internal/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "context"
5 | "log"
6 | "sync"
7 |
8 | "go.mongodb.org/mongo-driver/bson"
9 | "go.mongodb.org/mongo-driver/mongo"
10 | "go.mongodb.org/mongo-driver/mongo/options"
11 | )
12 |
13 | type Database struct {
14 | client *mongo.Client
15 | devices *mongo.Collection
16 | ctx context.Context
17 | m *sync.Mutex
18 | }
19 |
20 | func NewDatabase(ctx context.Context, mongoUri string) Database {
21 | var db Database
22 |
23 | clientOptions := options.Client().ApplyURI(mongoUri)
24 | client, err := mongo.Connect(ctx, clientOptions)
25 | if err != nil {
26 | log.Fatal(err)
27 | }
28 | db.client = client
29 |
30 | log.Println("Trying to ping Mongo database...")
31 | err = client.Ping(ctx, nil)
32 | if err != nil {
33 | log.Fatal("Couldn't connect to MongoDB --> ", err)
34 | }
35 |
36 | log.Println("Connected to MongoDB-->", mongoUri)
37 |
38 | devices := client.Database("adapter").Collection("devices")
39 | createIndexes(ctx, devices)
40 | //resetDeviceStatus(ctx, devices)
41 |
42 | db.devices = devices
43 | db.ctx = ctx
44 | db.m = &sync.Mutex{}
45 |
46 | return db
47 | }
48 |
49 | // func resetDeviceStatus(ctx context.Context, devices *mongo.Collection) {
50 | // _, err := devices.UpdateMany(ctx, bson.D{{}}, bson.D{
51 | // {
52 | // "$set", bson.D{
53 | // {"mqtt", 0},
54 | // {"stomp", 0},
55 | // {"websockets", 0},
56 | // {"status", 0},
57 | // },
58 | // },
59 | // })
60 | // if err != nil {
61 | // log.Fatalln("ERROR to reset device status in database:", err)
62 | // }
63 | // }
64 |
65 | func createIndexes(ctx context.Context, devices *mongo.Collection) {
66 | indexField := bson.M{"sn": 1}
67 | _, err := devices.Indexes().CreateOne(ctx, mongo.IndexModel{
68 | Keys: indexField,
69 | Options: options.Index().SetUnique(true),
70 | })
71 | if err != nil {
72 | log.Println("ERROR to create index in database:", err)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/internal/db/status.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "log"
5 |
6 | "go.mongodb.org/mongo-driver/bson"
7 | "go.mongodb.org/mongo-driver/bson/primitive"
8 | "go.mongodb.org/mongo-driver/mongo"
9 | )
10 |
11 | func (d *Database) UpdateStatus(sn string, status Status, mtp MTP) error {
12 | var result Device
13 |
14 | d.m.Lock()
15 | defer d.m.Unlock()
16 | err := d.devices.FindOne(d.ctx, bson.D{{"sn", sn}}, nil).Decode(&result)
17 | if err != nil {
18 | log.Println(err)
19 | }
20 |
21 | //TODO: abolish this logic, find another approach, microservices design maybe?
22 | /*
23 | In case the device status is online, we must check if the mtp
24 | changing is going to affect the global status. In case it does,
25 | we must update the global status accordingly.
26 | */
27 |
28 | /*
29 | mix the existent device status to the updated one
30 | */
31 | switch mtp {
32 | case MQTT:
33 | result.Mqtt = status
34 | case STOMP:
35 | result.Stomp = status
36 | case WEBSOCKETS:
37 | result.Websockets = status
38 | case CWMP:
39 | result.Cwmp = status
40 | }
41 |
42 | /*
43 | check if the global status needs update
44 | */
45 | var globalStatus primitive.E
46 | if result.Mqtt == Offline && result.Stomp == Offline && result.Websockets == Offline && result.Cwmp == Offline {
47 | globalStatus = primitive.E{"status", Offline}
48 | }
49 | if result.Mqtt == Online || result.Stomp == Online || result.Websockets == Online || result.Cwmp == Online {
50 | globalStatus = primitive.E{"status", Online}
51 | }
52 |
53 | _, err = d.devices.UpdateOne(d.ctx, bson.D{{"sn", sn}}, bson.D{
54 | {
55 | "$set", bson.D{
56 | {mtp.String(), status},
57 | globalStatus,
58 | },
59 | },
60 | })
61 |
62 | if err != nil {
63 | if err == mongo.ErrNoDocuments {
64 | log.Printf("Device %s is not mapped into database", sn)
65 | return nil
66 | }
67 | log.Println(err)
68 | }
69 | log.Printf("%s is now offline.", sn)
70 | return err
71 | }
72 |
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/internal/events/cwmp_handler/handler.go:
--------------------------------------------------------------------------------
1 | package cwmp_handler
2 |
3 | import (
4 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db"
5 | "github.com/nats-io/nats.go"
6 | "github.com/nats-io/nats.go/jetstream"
7 | )
8 |
9 | const (
10 | OFFLINE = iota
11 | ONLINE
12 | )
13 |
14 | type Handler struct {
15 | nc *nats.Conn
16 | js jetstream.JetStream
17 | db db.Database
18 | cid string
19 | }
20 |
21 | func NewHandler(nc *nats.Conn, js jetstream.JetStream, d db.Database, cid string) Handler {
22 | return Handler{
23 | nc: nc,
24 | js: js,
25 | db: d,
26 | cid: cid,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go:
--------------------------------------------------------------------------------
1 | package cwmp_handler
2 |
3 | import (
4 | "encoding/json"
5 | "encoding/xml"
6 | "log"
7 |
8 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/cwmp"
9 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db"
10 | )
11 |
12 | func (h *Handler) HandleDeviceInfo(device string, data []byte, ack func()) {
13 | defer ack()
14 | log.Printf("Device %s info", device)
15 | deviceInfo := parseDeviceInfoMsg(data)
16 | if deviceExists, _ := h.db.DeviceExists(deviceInfo.SN); !deviceExists {
17 | fmtDeviceInfo, _ := json.Marshal(deviceInfo)
18 | h.nc.Publish("device.v1.new", fmtDeviceInfo)
19 | }
20 | err := h.db.CreateDevice(deviceInfo)
21 | if err != nil {
22 | log.Printf("Failed to create device: %v", err)
23 | }
24 | }
25 |
26 | func parseDeviceInfoMsg(data []byte) db.Device {
27 |
28 | var inform cwmp.CWMPInform
29 | err := xml.Unmarshal(data, &inform)
30 | if err != nil {
31 | log.Println("Error unmarshalling xml:", err)
32 | }
33 |
34 | var device db.Device
35 |
36 | device.Vendor = inform.DeviceId.Manufacturer
37 | device.Model = ""
38 | device.Version = inform.GetSoftwareVersion()
39 | device.ProductClass = inform.DeviceId.ProductClass
40 | device.SN = inform.DeviceId.SerialNumber
41 | device.Cwmp = db.Online
42 | device.Status = db.Online
43 |
44 | return device
45 | }
46 |
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/internal/events/cwmp_handler/status.go:
--------------------------------------------------------------------------------
1 | package cwmp_handler
2 |
3 | import (
4 | "log"
5 | "strconv"
6 |
7 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db"
8 | )
9 |
10 | func (h *Handler) HandleDeviceStatus(device, subject string, data []byte, ack func()) {
11 | defer ack()
12 | payload, err := strconv.Atoi(string(data))
13 | if err != nil {
14 | log.Printf("Status subject payload message error %q", err)
15 | }
16 |
17 | switch payload {
18 | case OFFLINE:
19 | h.deviceOffline(device)
20 | default:
21 | ignoreMsg(subject, "status", data)
22 | }
23 | }
24 |
25 | func (h *Handler) deviceOffline(device string) {
26 | log.Printf("Device %s is offline", device)
27 |
28 | err := h.db.UpdateStatus(device, db.Offline, db.CWMP)
29 | if err != nil {
30 | log.Fatal(err)
31 | }
32 | }
33 |
34 | func ignoreMsg(subject, ctx string, data []byte) {
35 | log.Printf("Unknown message of %s received, subject: %s, payload: %s. Ignored...", ctx, subject, string(data))
36 | }
37 |
--------------------------------------------------------------------------------
/backend/services/mtp/adapter/internal/events/usp_handler/handler.go:
--------------------------------------------------------------------------------
1 | package usp_handler
2 |
3 | import (
4 | "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db"
5 | "github.com/nats-io/nats.go"
6 | "github.com/nats-io/nats.go/jetstream"
7 | )
8 |
9 | const (
10 | OFFLINE = iota
11 | ONLINE
12 | )
13 |
14 | type Handler struct {
15 | nc *nats.Conn
16 | js jetstream.JetStream
17 | db db.Database
18 | cid string
19 | }
20 |
21 | func NewHandler(nc *nats.Conn, js jetstream.JetStream, d db.Database, cid string) Handler {
22 | return Handler{
23 | nc: nc,
24 | js: js,
25 | db: d,
26 | cid: cid,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt-adapter/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/backend/services/mtp/mqtt-adapter/.env
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | .env.local
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt-adapter/README.md:
--------------------------------------------------------------------------------
1 | - acts as a bridge between mqtt server and controller
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt-adapter/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o mqtt-adapter cmd/mqtt-adapter/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/mqtt-adapter /
8 | ENTRYPOINT ["/mqtt-adapter"]
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt-adapter/cmd/mqtt-adapter/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/OktopUSP/oktopus/backend/services/mqtt-adapter/internal/bridge"
10 | "github.com/OktopUSP/oktopus/backend/services/mqtt-adapter/internal/config"
11 | "github.com/OktopUSP/oktopus/backend/services/mqtt-adapter/internal/nats"
12 | )
13 |
14 | func main() {
15 |
16 | done := make(chan os.Signal, 1)
17 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
18 |
19 | c := config.NewConfig()
20 |
21 | kv, publisher, subscriber := nats.StartNatsClient(c.Nats)
22 |
23 | bridge := bridge.NewBridge(publisher, subscriber, c.Mqtt.Ctx, c.Mqtt, kv)
24 |
25 | if c.Mqtt.Url != "" {
26 | bridge.StartBridge(c.Mqtt.Url, c.Mqtt.ClientId)
27 | }
28 |
29 | if c.Mqtt.UrlForTls != "" {
30 | bridge.StartBridge(c.Mqtt.UrlForTls, c.Mqtt.ClientId+"-tls")
31 | }
32 |
33 | <-done
34 |
35 | log.Println("mqtt adapter is shutting down...")
36 | }
37 |
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt-adapter/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/OktopUSP/oktopus/backend/services/mqtt-adapter
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/eclipse/paho.golang v0.21.0
9 | github.com/google/uuid v1.6.0
10 | github.com/joho/godotenv v1.5.1
11 | github.com/nats-io/nats.go v1.35.0
12 | golang.org/x/sys v0.33.0
13 | )
14 |
15 | require (
16 | github.com/gorilla/websocket v1.5.1 // indirect
17 | github.com/klauspost/compress v1.17.8 // indirect
18 | github.com/nats-io/nkeys v0.4.7 // indirect
19 | github.com/nats-io/nuid v1.0.1 // indirect
20 | golang.org/x/crypto v0.38.0 // indirect
21 | golang.org/x/net v0.40.0 // indirect
22 | golang.org/x/text v0.25.0 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/backend/services/mtp/mqtt/.env
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.db
3 | auth.prod.json
4 | run.prod.sh
5 | mochi
6 | *.pem
7 | *.crt
8 | *.key
9 | *.local
10 | ./mqtt
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/README.md:
--------------------------------------------------------------------------------
1 | - This MQTT broker runs with https://github.com/mochi-mqtt/server
2 | - It's compatible with MQTTv5 and MQTTv3.1.1
3 | - At authentication, the device is only allowed to subscribe and publish to topics that finish with their username
4 |
5 | For more info access https://github.com/mochi-mqtt/server and/or execute "go run cmd/mqtt/main.go --help"
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o mqtt cmd/mqtt/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/mqtt /
8 | ENTRYPOINT ["/mqtt"]
9 |
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/cmd/mqtt/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "broker/internal/config"
5 | "broker/internal/listeners"
6 | "log"
7 | "os"
8 | "os/signal"
9 | "syscall"
10 | )
11 |
12 | func main() {
13 |
14 | done := make(chan os.Signal, 1)
15 |
16 | conf := config.NewConfig()
17 |
18 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
19 |
20 | go listeners.StartServers(conf)
21 |
22 | if conf.WsEnable {
23 | log.Printf("websocket is running at port %s", conf.WsPort)
24 | }
25 |
26 | if conf.HttpEnable {
27 | log.Printf("http is running at port %s", conf.HttpPort)
28 | }
29 |
30 | log.Printf("mqtt is running at port %s", conf.MqttPort)
31 |
32 | <-done
33 |
34 | log.Println("server is shutting down...")
35 | }
36 |
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/go.mod:
--------------------------------------------------------------------------------
1 | module broker
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/go-redis/redis/v8 v8.11.5
9 | github.com/google/uuid v1.6.0
10 | github.com/joho/godotenv v1.5.1
11 | github.com/mochi-co/mqtt/v2 v2.2.16
12 | github.com/nats-io/nats.go v1.34.1
13 | github.com/rs/zerolog v1.29.1
14 | )
15 |
16 | require (
17 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
18 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
19 | github.com/gorilla/websocket v1.5.0 // indirect
20 | github.com/klauspost/compress v1.17.2 // indirect
21 | github.com/mattn/go-colorable v0.1.12 // indirect
22 | github.com/mattn/go-isatty v0.0.14 // indirect
23 | github.com/nats-io/nkeys v0.4.7 // indirect
24 | github.com/nats-io/nuid v1.0.1 // indirect
25 | github.com/rs/xid v1.4.0 // indirect
26 | golang.org/x/crypto v0.38.0 // indirect
27 | golang.org/x/sys v0.33.0 // indirect
28 | golang.org/x/text v0.25.0 // indirect
29 | gopkg.in/yaml.v3 v3.0.1 // indirect
30 | )
31 |
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/internal/listeners/http/http.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/google/uuid"
7 | "github.com/mochi-co/mqtt/v2"
8 | "github.com/mochi-co/mqtt/v2/listeners"
9 | )
10 |
11 | type Http struct {
12 | HttpPort string
13 | }
14 |
15 | func (h *Http) Start(server *mqtt.Server) {
16 | stats := listeners.NewHTTPStats(uuid.NewString(), h.HttpPort, nil, server.Info)
17 | err := server.AddListener(stats)
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/internal/listeners/listeners.go:
--------------------------------------------------------------------------------
1 | package listeners
2 |
3 | import (
4 | "broker/internal/config"
5 | "broker/internal/listeners/http"
6 | broker "broker/internal/listeners/mqtt"
7 | "broker/internal/listeners/ws"
8 | "sync"
9 |
10 | "github.com/mochi-co/mqtt/v2"
11 | "github.com/rs/zerolog"
12 | )
13 |
14 | func StartServers(c config.Config) {
15 |
16 | server := mqtt.New(&mqtt.Options{})
17 |
18 | var wg sync.WaitGroup
19 | wg.Add(3)
20 |
21 | go func() {
22 | mqttServer := newMqttServer(c)
23 | mqttServer.Start(server)
24 | wg.Done()
25 | }()
26 |
27 | go func() {
28 | if c.WsEnable {
29 | wsServer := newWsServer(c)
30 | wsServer.Start(server)
31 | }
32 | wg.Done()
33 | }()
34 |
35 | go func() {
36 | if c.HttpEnable {
37 | httpServer := newHttpServer(c)
38 | httpServer.Start(server)
39 | }
40 | wg.Done()
41 | }()
42 |
43 | server.Log.Level(zerolog.Level(c.LogLevel))
44 |
45 | wg.Wait()
46 |
47 | err := server.Serve()
48 | if err != nil {
49 | server.Log.Fatal().Err(err).Msg("server error")
50 | }
51 | }
52 |
53 | func newMqttServer(c config.Config) *broker.Mqtt {
54 | return &broker.Mqtt{
55 | Port: c.MqttPort,
56 | TlsPort: c.MqttTlsPort,
57 | NoTls: c.NoTls,
58 | Tls: c.Tls,
59 | Fullchain: c.Fullchain,
60 | Privkey: c.Privkey,
61 | AuthEnable: c.AuthEnable,
62 | Redis: broker.Redis{
63 | RedisEnable: c.RedisEnable,
64 | RedisAddr: c.RedisAddr,
65 | RedisPassword: c.RedisPassword,
66 | },
67 | LogLevel: c.LogLevel,
68 | Nats: c.Nats,
69 | }
70 | }
71 |
72 | func newWsServer(c config.Config) *ws.Ws {
73 | return &ws.Ws{
74 | WsPort: c.WsPort,
75 | }
76 | }
77 |
78 | func newHttpServer(c config.Config) *http.Http {
79 | return &http.Http{
80 | HttpPort: c.HttpPort,
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/backend/services/mtp/mqtt/internal/listeners/ws/ws.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/google/uuid"
7 | "github.com/mochi-co/mqtt/v2"
8 | "github.com/mochi-co/mqtt/v2/listeners"
9 | )
10 |
11 | type Ws struct {
12 | WsPort string
13 | }
14 |
15 | func (w *Ws) Start(server *mqtt.Server) {
16 | ws := listeners.NewWebsocket(uuid.NewString(), w.WsPort, nil)
17 | err := server.AddListener(ws)
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/backend/services/mtp/stomp-adapter/.env
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | .env.local
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o stomp-adapter cmd/stomp-adapter/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/stomp-adapter /
8 | ENTRYPOINT ["/stomp-adapter"]
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/cmd/stomp-adapter/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/bridge"
10 | "github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/config"
11 | "github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/nats"
12 | )
13 |
14 | func main() {
15 |
16 | done := make(chan os.Signal, 1)
17 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
18 |
19 | c := config.NewConfig()
20 |
21 | _, publisher, subscriber := nats.StartNatsClient(c.Nats)
22 |
23 | bridge := bridge.NewBridge(publisher, subscriber, c.Nats.Ctx, c.Stomp)
24 | bridge.StartBridge()
25 |
26 | <-done
27 |
28 | log.Println("stomp adapter is shutting down...")
29 | }
30 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/joho/godotenv v1.5.1
9 | github.com/nats-io/nats.go v1.35.0
10 | golang.org/x/sys v0.33.0
11 | )
12 |
13 | require (
14 | github.com/klauspost/compress v1.17.8 // indirect
15 | github.com/nats-io/nkeys v0.4.7 // indirect
16 | github.com/nats-io/nuid v1.0.1 // indirect
17 | golang.org/x/crypto v0.38.0 // indirect
18 | golang.org/x/text v0.25.0 // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/go.sum:
--------------------------------------------------------------------------------
1 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
2 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
3 | github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
4 | github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
5 | github.com/nats-io/nats.go v1.35.0 h1:XFNqNM7v5B+MQMKqVGAyHwYhyKb48jrenXNxIU20ULk=
6 | github.com/nats-io/nats.go v1.35.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
7 | github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
8 | github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
9 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
10 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
11 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
12 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
13 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
14 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
15 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
16 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
17 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/.gitattributes:
--------------------------------------------------------------------------------
1 | # Explicitly specify which files are text.
2 | # Text files will be normalized (crlf -> lf)
3 | *.go text
4 | *.txt text
5 | *.md text
6 | *.html text
7 | *.css text
8 | *.js text
9 |
10 | # Binary files
11 | *.png binary
12 | *.jpg binary
13 | *.jpeg binary
14 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 |
24 | .idea
25 |
26 | # used for generating documentation during development
27 | doc.txt
28 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/AUTHORS.md:
--------------------------------------------------------------------------------
1 | ## Authors
2 |
3 | * John Jeffery
4 | * Hiram Jerónimo Pérez
5 | * Alessandro Siragusa
6 | * DaytonG
7 | * Erik Benoist
8 | * Evan Borgstrom
9 | * Fernando Ribeiro
10 | * Fredrik Rubensson
11 | * Laurent Luce
12 | * Oliver, Jonathan
13 | * Paul P. Komkoff
14 | * Raphael Tiersch
15 | * Tom Lee
16 | * Tony Le
17 | * Voronkov Artem
18 | * Whit Marbut
19 | * hanjm
20 | * yang.zhang4
21 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/README.md:
--------------------------------------------------------------------------------
1 | # stomp
2 |
3 | Go language implementation of a STOMP client library.
4 |
5 | 
6 | [](https://pkg.go.dev/github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/go-stomp/stomp/v3)
7 |
8 | Features:
9 |
10 | * Supports STOMP Specifications Versions 1.0, 1.1, 1.2 (https://stomp.github.io/)
11 | * Protocol negotiation to select the latest mutually supported protocol
12 | * Heart beating for testing the underlying network connection
13 | * Tested against RabbitMQ v3.0.1
14 |
15 | ## Usage Instructions
16 |
17 | ```
18 | go get github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/go-stomp/stomp/v3
19 | ```
20 |
21 | For API documentation, see https://pkg.go.dev/github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/go-stomp/stomp/v3
22 |
23 |
24 | Breaking changes between this previous version and the current version are
25 | documented in [breaking_changes.md](breaking_changes.md).
26 |
27 |
28 | ## License
29 | Copyright 2012 - Present The go-stomp authors
30 |
31 | Licensed under the Apache License, Version 2.0 (the "License");
32 | you may not use this file except in compliance with the License.
33 | You may obtain a copy of the License at
34 |
35 | http://www.apache.org/licenses/LICENSE-2.0
36 |
37 | Unless required by applicable law or agreed to in writing, software
38 | distributed under the License is distributed on an "AS IS" BASIS,
39 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40 | See the License for the specific language governing permissions and
41 | limitations under the License.
42 |
43 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/ack.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import "github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/stomp/frame"
4 |
5 | // The AckMode type is an enumeration of the acknowledgement modes for a
6 | // STOMP subscription.
7 | type AckMode int
8 |
9 | // String returns the string representation of the AckMode value.
10 | func (a AckMode) String() string {
11 | switch a {
12 | case AckAuto:
13 | return frame.AckAuto
14 | case AckClient:
15 | return frame.AckClient
16 | case AckClientIndividual:
17 | return frame.AckClientIndividual
18 | }
19 | panic("invalid AckMode value")
20 | }
21 |
22 | // ShouldAck returns true if this AckMode is an acknowledgement
23 | // mode which requires acknowledgement. Returns true for all values
24 | // except AckAuto, which returns false.
25 | func (a AckMode) ShouldAck() bool {
26 | switch a {
27 | case AckAuto:
28 | return false
29 | case AckClient, AckClientIndividual:
30 | return true
31 | }
32 | panic("invalid AckMode value")
33 | }
34 |
35 | const (
36 | // No acknowledgement is required, the server assumes that the client
37 | // received the message.
38 | AckAuto AckMode = iota
39 |
40 | // Client acknowledges messages. When a client acknowledges a message,
41 | // any previously received messages are also acknowledged.
42 | AckClient
43 |
44 | // Client acknowledges message. Each message is acknowledged individually.
45 | AckClientIndividual
46 | )
47 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/frame/ack.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | // Valid values for the "ack" header entry.
4 | const (
5 | AckAuto = "auto" // Client does not send ACK
6 | AckClient = "client" // Client sends ACK/NACK
7 | AckClientIndividual = "client-individual" // Client sends ACK/NACK for individual messages
8 | )
9 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/frame/command.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | // STOMP frame commands. Used upper case naming
4 | // convention to avoid clashing with STOMP header names.
5 | const (
6 | // Connect commands.
7 | CONNECT = "CONNECT"
8 | STOMP = "STOMP"
9 | CONNECTED = "CONNECTED"
10 |
11 | // Client commands.
12 | SEND = "SEND"
13 | SUBSCRIBE = "SUBSCRIBE"
14 | UNSUBSCRIBE = "UNSUBSCRIBE"
15 | ACK = "ACK"
16 | NACK = "NACK"
17 | BEGIN = "BEGIN"
18 | COMMIT = "COMMIT"
19 | ABORT = "ABORT"
20 | DISCONNECT = "DISCONNECT"
21 |
22 | // Server commands.
23 | MESSAGE = "MESSAGE"
24 | RECEIPT = "RECEIPT"
25 | ERROR = "ERROR"
26 | )
27 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/frame/encode.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "strings"
5 | "unsafe"
6 | )
7 |
8 | var (
9 | replacerForEncodeValue = strings.NewReplacer(
10 | "\\", "\\\\",
11 | "\r", "\\r",
12 | "\n", "\\n",
13 | ":", "\\c",
14 | )
15 | replacerForUnencodeValue = strings.NewReplacer(
16 | "\\r", "\r",
17 | "\\n", "\n",
18 | "\\c", ":",
19 | "\\\\", "\\",
20 | )
21 | )
22 |
23 | // Reduce one allocation on copying bytes to string
24 | func bytesToString(b []byte) string {
25 | /* #nosec G103 */
26 | return *(*string)(unsafe.Pointer(&b))
27 | }
28 |
29 | // Unencodes a header value using STOMP value encoding
30 | // TODO: return error if invalid sequences found (eg "\t")
31 | func unencodeValue(b []byte) (string, error) {
32 | s := replacerForUnencodeValue.Replace(bytesToString(b))
33 | return s, nil
34 | }
35 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/frame/errors.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | var (
8 | ErrInvalidHeartBeat = errors.New("invalid heart-beat")
9 | )
10 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/frame/frame.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package frame provides functionality for manipulating STOMP frames.
3 | */
4 | package frame
5 |
6 | // A Frame represents a STOMP frame. A frame consists of a command
7 | // followed by a collection of header entries, and then an optional
8 | // body.
9 | type Frame struct {
10 | Command string
11 | Header *Header
12 | Body []byte
13 | }
14 |
15 | // New creates a new STOMP frame with the specified command and headers.
16 | // The headers should contain an even number of entries. Each even index is
17 | // the header name, and the odd indexes are the assocated header values.
18 | func New(command string, headers ...string) *Frame {
19 | f := &Frame{Command: command, Header: &Header{}}
20 | for index := 0; index < len(headers); index += 2 {
21 | f.Header.Add(headers[index], headers[index+1])
22 | }
23 | return f
24 | }
25 |
26 | // Clone creates a deep copy of the frame and its header. The cloned
27 | // frame shares the body with the original frame.
28 | func (f *Frame) Clone() *Frame {
29 | fc := &Frame{Command: f.Command}
30 | if f.Header != nil {
31 | fc.Header = f.Header.Clone()
32 | }
33 | if f.Body != nil {
34 | fc.Body = make([]byte, len(f.Body))
35 | copy(fc.Body, f.Body)
36 | }
37 | return fc
38 | }
39 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/frame/heartbeat.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "math"
5 | "regexp"
6 | "strconv"
7 | "strings"
8 | "time"
9 | )
10 |
11 | var (
12 | // Regexp for heart-beat header value
13 | heartBeatRegexp = regexp.MustCompile("^[0-9]+,[0-9]+$")
14 | )
15 |
16 | const (
17 | // Maximum number of milliseconds that can be represented
18 | // in a time.Duration.
19 | maxMilliseconds = math.MaxInt64 / int64(time.Millisecond)
20 | )
21 |
22 | // ParseHeartBeat parses the value of a STOMP heart-beat entry and
23 | // returns two time durations. Returns an error if the heart-beat
24 | // value is not in the correct format, or if the time durations are
25 | // too big to be represented by the time.Duration type.
26 | func ParseHeartBeat(heartBeat string) (time.Duration, time.Duration, error) {
27 | if !heartBeatRegexp.MatchString(heartBeat) {
28 | return 0, 0, ErrInvalidHeartBeat
29 | }
30 | slice := strings.Split(heartBeat, ",")
31 | value1, err := strconv.ParseInt(slice[0], 10, 64)
32 | if err != nil {
33 | return 0, 0, ErrInvalidHeartBeat
34 | }
35 | value2, err := strconv.ParseInt(slice[1], 10, 64)
36 | if err != nil {
37 | return 0, 0, ErrInvalidHeartBeat
38 | }
39 | if value1 > maxMilliseconds || value2 > maxMilliseconds {
40 | return 0, 0, ErrInvalidHeartBeat
41 | }
42 | return time.Duration(value1) * time.Millisecond,
43 | time.Duration(value2) * time.Millisecond, nil
44 | }
45 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/id.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "strconv"
5 | "sync/atomic"
6 | )
7 |
8 | var _lastId uint64
9 |
10 | // allocateId returns a unique number for the current
11 | // process. Starts at one and increases. Used for
12 | // allocating subscription ids, receipt ids,
13 | // transaction ids, etc.
14 | func allocateId() string {
15 | id := atomic.AddUint64(&_lastId, 1)
16 | return strconv.FormatUint(id, 10)
17 | }
18 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/internal/log/stdlogger.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "fmt"
5 | stdlog "log"
6 | )
7 |
8 | var (
9 | debugPrefix = "DEBUG: "
10 | infoPrefix = "INFO: "
11 | warnPrefix = "WARN: "
12 | errorPrefix = "ERROR: "
13 | )
14 |
15 | func logf(prefix string, format string, value ...interface{}) {
16 | _ = stdlog.Output(3, fmt.Sprintf(prefix+format+"\n", value...))
17 | }
18 |
19 | type StdLogger struct{}
20 |
21 | func (s StdLogger) Debugf(format string, value ...interface{}) {
22 | logf(debugPrefix, format, value...)
23 | }
24 |
25 | func (s StdLogger) Debug(message string) {
26 | logf(debugPrefix, "%s", message)
27 | }
28 |
29 | func (s StdLogger) Infof(format string, value ...interface{}) {
30 | logf(infoPrefix, format, value...)
31 | }
32 |
33 | func (s StdLogger) Info(message string) {
34 | logf(infoPrefix, "%s", message)
35 | }
36 |
37 | func (s StdLogger) Warningf(format string, value ...interface{}) {
38 | logf(warnPrefix, format, value...)
39 | }
40 |
41 | func (s StdLogger) Warning(message string) {
42 | logf(warnPrefix, "%s", message)
43 | }
44 |
45 | func (s StdLogger) Errorf(format string, value ...interface{}) {
46 | logf(errorPrefix, format, value...)
47 | }
48 |
49 | func (s StdLogger) Error(message string) {
50 | logf(errorPrefix, "%s", message)
51 | }
52 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/logger.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | type Logger interface {
4 | Debugf(format string, value ...interface{})
5 | Infof(format string, value ...interface{})
6 | Warningf(format string, value ...interface{})
7 | Errorf(format string, value ...interface{})
8 |
9 | Debug(message string)
10 | Info(message string)
11 | Warning(message string)
12 | Error(message string)
13 | }
14 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/send_options.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/stomp/frame"
5 | )
6 |
7 | // SendOpt contains options for for the Conn.Send and Transaction.Send functions.
8 | var SendOpt struct {
9 | // Receipt specifies that the client should request acknowledgement
10 | // from the server before the send operation successfully completes.
11 | Receipt func(*frame.Frame) error
12 |
13 | // NoContentLength specifies that the SEND frame should not include
14 | // a content-length header entry. By default the content-length header
15 | // entry is always included, but some message brokers assign special
16 | // meaning to STOMP frames that do not contain a content-length
17 | // header entry. (In particular ActiveMQ interprets STOMP frames
18 | // with no content-length as being a text message)
19 | NoContentLength func(*frame.Frame) error
20 |
21 | // Header provides the opportunity to include custom header entries
22 | // in the SEND frame that the client sends to the server. This option
23 | // can be specified multiple times if multiple custom header entries
24 | // are required.
25 | Header func(key, value string) func(*frame.Frame) error
26 | }
27 |
28 | func init() {
29 | SendOpt.Receipt = func(f *frame.Frame) error {
30 | if f.Command != frame.SEND {
31 | return ErrInvalidCommand
32 | }
33 | id := allocateId()
34 | f.Header.Set(frame.Receipt, id)
35 | return nil
36 | }
37 |
38 | SendOpt.NoContentLength = func(f *frame.Frame) error {
39 | if f.Command != frame.SEND {
40 | return ErrInvalidCommand
41 | }
42 | f.Header.Del(frame.ContentLength)
43 | return nil
44 | }
45 |
46 | SendOpt.Header = func(key, value string) func(*frame.Frame) error {
47 | return func(f *frame.Frame) error {
48 | if f.Command != frame.SEND {
49 | return ErrInvalidCommand
50 | }
51 | f.Header.Add(key, value)
52 | return nil
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/stomp.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package stomp provides operations that allow communication with a message broker that supports the STOMP protocol.
3 | STOMP is the Streaming Text-Oriented Messaging Protocol. See http://stomp.github.com/ for more details.
4 |
5 | This package provides support for all STOMP protocol features in the STOMP protocol specifications,
6 | versions 1.0, 1.1 and 1.2. These features including protocol negotiation, heart-beating, value encoding,
7 | and graceful shutdown.
8 |
9 | Connecting to a STOMP server is achieved using the stomp.Dial function, or the stomp.Connect function. See
10 | the examples section for a summary of how to use these functions. Both functions return a stomp.Conn object
11 | for subsequent interaction with the STOMP server.
12 |
13 | Once a connection (stomp.Conn) is created, it can be used to send messages to the STOMP server, or create
14 | subscriptions for receiving messages from the STOMP server. Transactions can be created to send multiple
15 | messages and/ or acknowledge multiple received messages from the server in one, atomic transaction. The
16 | examples section has examples of using subscriptions and transactions.
17 |
18 | The client program can instruct the stomp.Conn to gracefully disconnect from the STOMP server using the
19 | Disconnect method. This will perform a graceful shutdown sequence as specified in the STOMP specification.
20 |
21 | Source code and other details for the project are available at GitHub:
22 |
23 | https://github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/go-stomp/stomp
24 | */
25 | package stomp
26 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/subscribe_options.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/stomp/frame"
5 | )
6 |
7 | // SubscribeOpt contains options for for the Conn.Subscribe function.
8 | var SubscribeOpt struct {
9 | // Id provides the opportunity to specify the value of the "id" header
10 | // entry in the STOMP SUBSCRIBE frame.
11 | //
12 | // If the client program does specify the value for "id",
13 | // it is responsible for choosing a unique value.
14 | Id func(id string) func(*frame.Frame) error
15 |
16 | // Header provides the opportunity to include custom header entries
17 | // in the SUBSCRIBE frame that the client sends to the server.
18 | Header func(key, value string) func(*frame.Frame) error
19 | }
20 |
21 | func init() {
22 | SubscribeOpt.Id = func(id string) func(*frame.Frame) error {
23 | return func(f *frame.Frame) error {
24 | if f.Command != frame.SUBSCRIBE {
25 | return ErrInvalidCommand
26 | }
27 | f.Header.Set(frame.Id, id)
28 | return nil
29 | }
30 | }
31 |
32 | SubscribeOpt.Header = func(key, value string) func(*frame.Frame) error {
33 | return func(f *frame.Frame) error {
34 | if f.Command != frame.SUBSCRIBE &&
35 | f.Command != frame.UNSUBSCRIBE {
36 | return ErrInvalidCommand
37 | }
38 | f.Header.Add(key, value)
39 | return nil
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/validator.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/stomp/frame"
5 | )
6 |
7 | // Validator is an interface for validating STOMP frames.
8 | type Validator interface {
9 | // Validate returns nil if the frame is valid, or an error if not valid.
10 | Validate(f *frame.Frame) error
11 | }
12 |
13 | func NewValidator(version Version) Validator {
14 | return validatorNull{}
15 | }
16 |
17 | type validatorNull struct{}
18 |
19 | func (v validatorNull) Validate(f *frame.Frame) error {
20 | return nil
21 | }
22 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp-adapter/internal/stomp/version.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | // Version is the STOMP protocol version.
4 | type Version string
5 |
6 | const (
7 | V10 Version = "1.0"
8 | V11 Version = "1.1"
9 | V12 Version = "1.2"
10 | )
11 |
12 | // String returns a string representation of the STOMP version.
13 | func (v Version) String() string {
14 | return string(v)
15 | }
16 |
17 | // CheckSupported is used to determine whether a particular STOMP
18 | // version is supported by this library. Returns nil if the version is
19 | // supported, or ErrUnsupportedVersion if not supported.
20 | func (v Version) CheckSupported() error {
21 | switch v {
22 | case V10, V11, V12:
23 | return nil
24 | }
25 | return ErrUnsupportedVersion
26 | }
27 |
28 | // SupportsNack indicates whether this version of the STOMP protocol
29 | // supports use of the NACK command.
30 | func (v Version) SupportsNack() bool {
31 | switch v {
32 | case V10:
33 | return false
34 | case V11, V12:
35 | return true
36 | }
37 |
38 | // unknown version
39 | return false
40 | }
41 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/.env:
--------------------------------------------------------------------------------
1 | STOMP_USERNAME=""
2 | STOMP_PASSWORD=""
3 | # if both variables above are empty, the STOMP server will run without auth
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/.gitignore:
--------------------------------------------------------------------------------
1 | .env.local
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/AUTHORS.md:
--------------------------------------------------------------------------------
1 | ## Authors
2 |
3 | * John Jeffery
4 | * Hiram Jerónimo Pérez
5 | * Alessandro Siragusa
6 | * DaytonG
7 | * Erik Benoist
8 | * Evan Borgstrom
9 | * Fernando Ribeiro
10 | * Fredrik Rubensson
11 | * Laurent Luce
12 | * Oliver, Jonathan
13 | * Paul P. Komkoff
14 | * Raphael Tiersch
15 | * Tom Lee
16 | * Tony Le
17 | * Voronkov Artem
18 | * Whit Marbut
19 | * hanjm
20 | * yang.zhang4
21 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/README.md:
--------------------------------------------------------------------------------
1 | This STOMP implementation was forked from https://github.com/go-stomp/stomp and we customized it to accomplish with TR-369 protocol
2 |
3 | # stomp
4 |
5 | Go language implementation of a STOMP client library.
6 |
7 | 
8 | [](https://pkg.go.dev/github.com/go-stomp/stomp/v3)
9 |
10 | Features:
11 |
12 | * Supports STOMP Specifications Versions 1.0, 1.1, 1.2 (https://stomp.github.io/)
13 | * Protocol negotiation to select the latest mutually supported protocol
14 | * Heart beating for testing the underlying network connection
15 | * Tested against RabbitMQ v3.0.1
16 |
17 | ## Usage Instructions
18 |
19 | ```
20 | go get github.com/go-stomp/stomp/v3
21 | ```
22 |
23 | For API documentation, see https://pkg.go.dev/github.com/go-stomp/stomp/v3
24 |
25 |
26 | Breaking changes between this previous version and the current version are
27 | documented in [breaking_changes.md](breaking_changes.md).
28 |
29 |
30 | ## License
31 | Copyright 2012 - Present The go-stomp authors
32 |
33 | Licensed under the Apache License, Version 2.0 (the "License");
34 | you may not use this file except in compliance with the License.
35 | You may obtain a copy of the License at
36 |
37 | http://www.apache.org/licenses/LICENSE-2.0
38 |
39 | Unless required by applicable law or agreed to in writing, software
40 | distributed under the License is distributed on an "AS IS" BASIS,
41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42 | See the License for the specific language governing permissions and
43 | limitations under the License.
44 |
45 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/ack.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | )
6 |
7 | // The AckMode type is an enumeration of the acknowledgement modes for a
8 | // STOMP subscription.
9 | type AckMode int
10 |
11 | // String returns the string representation of the AckMode value.
12 | func (a AckMode) String() string {
13 | switch a {
14 | case AckAuto:
15 | return frame.AckAuto
16 | case AckClient:
17 | return frame.AckClient
18 | case AckClientIndividual:
19 | return frame.AckClientIndividual
20 | }
21 | panic("invalid AckMode value")
22 | }
23 |
24 | // ShouldAck returns true if this AckMode is an acknowledgement
25 | // mode which requires acknowledgement. Returns true for all values
26 | // except AckAuto, which returns false.
27 | func (a AckMode) ShouldAck() bool {
28 | switch a {
29 | case AckAuto:
30 | return false
31 | case AckClient, AckClientIndividual:
32 | return true
33 | }
34 | panic("invalid AckMode value")
35 | }
36 |
37 | const (
38 | // No acknowledgement is required, the server assumes that the client
39 | // received the message.
40 | AckAuto AckMode = iota
41 |
42 | // Client acknowledges messages. When a client acknowledges a message,
43 | // any previously received messages are also acknowledged.
44 | AckClient
45 |
46 | // Client acknowledges message. Each message is acknowledged individually.
47 | AckClientIndividual
48 | )
49 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22@sha256:82e07063a1ac3ee59e6f38b1222e32ce88469e4431ff6496cc40fb9a0fc18229 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o stomp cmd/stomp/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/stomp /
8 | ENTRYPOINT ["/stomp"]
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/cmd/stomp/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "os"
7 |
8 | "github.com/go-stomp/stomp/v3/server"
9 | "github.com/joho/godotenv"
10 | )
11 |
12 | type Credentials struct {
13 | Login string
14 | Passwd string
15 | }
16 |
17 | func (c *Credentials) Authenticate(login, passwd string) bool {
18 |
19 | if c.Login == "" && c.Passwd == "" {
20 | return true
21 | }
22 |
23 | if login != c.Login || passwd != c.Passwd {
24 | log.Println("CLIENT AUTH: Invalid Credentials")
25 | return false
26 | }
27 | return true
28 | }
29 |
30 | func main() {
31 |
32 | err := godotenv.Load()
33 | if err != nil {
34 | log.Println("Error loading godotenv file")
35 | }
36 | localEnv := ".env.local"
37 | if _, err = os.Stat(localEnv); err == nil {
38 | _ = godotenv.Overload(localEnv)
39 | log.Println("Loaded variables from '.env.local'")
40 | } else {
41 | log.Println("Loaded variables from '.env'")
42 | }
43 |
44 | log.SetFlags(log.LstdFlags | log.Lshortfile)
45 |
46 | creds := Credentials{
47 | Login: os.Getenv("STOMP_USERNAME"),
48 | Passwd: os.Getenv("STOMP_PASSWORD"),
49 | }
50 |
51 | l, err := net.Listen("tcp", server.DefaultAddr)
52 | if err != nil {
53 | log.Println("Error to open tcp port: ", err)
54 | }
55 |
56 | s := server.Server{
57 | Addr: server.DefaultAddr,
58 | HeartBeat: server.DefaultHeartBeat,
59 | Authenticator: &creds,
60 | }
61 |
62 | log.Println("Started STOMP server at port", s.Addr)
63 | err = s.Serve(l)
64 | if err != nil {
65 | log.Println("Error to start stomp server: ", err)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/ack.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | // Valid values for the "ack" header entry.
4 | const (
5 | AckAuto = "auto" // Client does not send ACK
6 | AckClient = "client" // Client sends ACK/NACK
7 | AckClientIndividual = "client-individual" // Client sends ACK/NACK for individual messages
8 | )
9 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/command.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | // STOMP frame commands. Used upper case naming
4 | // convention to avoid clashing with STOMP header names.
5 | const (
6 | // Connect commands.
7 | CONNECT = "CONNECT"
8 | STOMP = "STOMP"
9 | CONNECTED = "CONNECTED"
10 |
11 | // Client commands.
12 | SEND = "SEND"
13 | SUBSCRIBE = "SUBSCRIBE"
14 | UNSUBSCRIBE = "UNSUBSCRIBE"
15 | ACK = "ACK"
16 | NACK = "NACK"
17 | BEGIN = "BEGIN"
18 | COMMIT = "COMMIT"
19 | ABORT = "ABORT"
20 | DISCONNECT = "DISCONNECT"
21 |
22 | // Server commands.
23 | MESSAGE = "MESSAGE"
24 | RECEIPT = "RECEIPT"
25 | ERROR = "ERROR"
26 | )
27 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/encode.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "strings"
5 | "unsafe"
6 | )
7 |
8 | var (
9 | replacerForEncodeValue = strings.NewReplacer(
10 | "\\", "\\\\",
11 | "\r", "\\r",
12 | "\n", "\\n",
13 | ":", "\\c",
14 | )
15 | replacerForUnencodeValue = strings.NewReplacer(
16 | "\\r", "\r",
17 | "\\n", "\n",
18 | "\\c", ":",
19 | "\\\\", "\\",
20 | )
21 | )
22 |
23 | // Reduce one allocation on copying bytes to string
24 | func bytesToString(b []byte) string {
25 | /* #nosec G103 */
26 | return *(*string)(unsafe.Pointer(&b))
27 | }
28 |
29 | // Unencodes a header value using STOMP value encoding
30 | // TODO: return error if invalid sequences found (eg "\t")
31 | func unencodeValue(b []byte) (string, error) {
32 | s := replacerForUnencodeValue.Replace(bytesToString(b))
33 | return s, nil
34 | }
35 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/encode_test.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | . "gopkg.in/check.v1"
5 | )
6 |
7 | type EncodeSuite struct{}
8 |
9 | var _ = Suite(&EncodeSuite{})
10 |
11 | func (s *EncodeSuite) TestUnencodeValue(c *C) {
12 | val, err := unencodeValue([]byte(`Contains\r\nNewLine and \c colon and \\ backslash`))
13 | c.Check(err, IsNil)
14 | c.Check(val, Equals, "Contains\r\nNewLine and : colon and \\ backslash")
15 | }
16 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/errors.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | var (
8 | ErrInvalidHeartBeat = errors.New("invalid heart-beat")
9 | )
10 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/frame.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package frame provides functionality for manipulating STOMP frames.
3 | */
4 | package frame
5 |
6 | // A Frame represents a STOMP frame. A frame consists of a command
7 | // followed by a collection of header entries, and then an optional
8 | // body.
9 | type Frame struct {
10 | Command string
11 | Header *Header
12 | Body []byte
13 | }
14 |
15 | // New creates a new STOMP frame with the specified command and headers.
16 | // The headers should contain an even number of entries. Each even index is
17 | // the header name, and the odd indexes are the assocated header values.
18 | func New(command string, headers ...string) *Frame {
19 | f := &Frame{Command: command, Header: &Header{}}
20 | for index := 0; index < len(headers); index += 2 {
21 | f.Header.Add(headers[index], headers[index+1])
22 | }
23 | return f
24 | }
25 |
26 | // Clone creates a deep copy of the frame and its header. The cloned
27 | // frame shares the body with the original frame.
28 | func (f *Frame) Clone() *Frame {
29 | fc := &Frame{Command: f.Command}
30 | if f.Header != nil {
31 | fc.Header = f.Header.Clone()
32 | }
33 | if f.Body != nil {
34 | fc.Body = make([]byte, len(f.Body))
35 | copy(fc.Body, f.Body)
36 | }
37 | return fc
38 | }
39 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/frame_test.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "testing"
5 |
6 | . "gopkg.in/check.v1"
7 | )
8 |
9 | func TestFrame(t *testing.T) {
10 | TestingT(t)
11 | }
12 |
13 | type FrameSuite struct{}
14 |
15 | var _ = Suite(&FrameSuite{})
16 |
17 | func (s *FrameSuite) TestNew(c *C) {
18 | f := New("CCC")
19 | c.Check(f.Header.Len(), Equals, 0)
20 | c.Check(f.Command, Equals, "CCC")
21 |
22 | f = New("DDDD", "abc", "def")
23 | c.Check(f.Header.Len(), Equals, 1)
24 | k, v := f.Header.GetAt(0)
25 | c.Check(k, Equals, "abc")
26 | c.Check(v, Equals, "def")
27 | c.Check(f.Command, Equals, "DDDD")
28 |
29 | f = New("EEEEEEE", "abc", "def", "hij", "klm")
30 | c.Check(f.Command, Equals, "EEEEEEE")
31 | c.Check(f.Header.Len(), Equals, 2)
32 | k, v = f.Header.GetAt(0)
33 | c.Check(k, Equals, "abc")
34 | c.Check(v, Equals, "def")
35 | k, v = f.Header.GetAt(1)
36 | c.Check(k, Equals, "hij")
37 | c.Check(v, Equals, "klm")
38 | }
39 |
40 | func (s *FrameSuite) TestClone(c *C) {
41 | f1 := &Frame{
42 | Command: "AAAA",
43 | }
44 |
45 | f2 := f1.Clone()
46 | c.Check(f2.Command, Equals, f1.Command)
47 | c.Check(f2.Header, IsNil)
48 | c.Check(f2.Body, IsNil)
49 |
50 | f1.Header = NewHeader("aaa", "1", "bbb", "2", "ccc", "3")
51 |
52 | f2 = f1.Clone()
53 | c.Check(f2.Header.Len(), Equals, f1.Header.Len())
54 | for i := 0; i < f1.Header.Len(); i++ {
55 | k1, v1 := f1.Header.GetAt(i)
56 | k2, v2 := f2.Header.GetAt(i)
57 | c.Check(k1, Equals, k2)
58 | c.Check(v1, Equals, v2)
59 | }
60 |
61 | f1.Body = []byte{1, 2, 3, 4, 5, 6, 5, 4, 77, 88, 99, 0xaa, 0xbb, 0xcc, 0xff}
62 | f2 = f1.Clone()
63 | c.Check(len(f2.Body), Equals, len(f1.Body))
64 | for i := 0; i < len(f1.Body); i++ {
65 | c.Check(f1.Body[i], Equals, f2.Body[i])
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/header_test.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | . "gopkg.in/check.v1"
5 | )
6 |
7 | func (s *FrameSuite) TestHeaderGetSetAddDel(c *C) {
8 | h := &Header{}
9 | c.Assert(h.Get("xxx"), Equals, "")
10 | h.Add("xxx", "yyy")
11 | c.Assert(h.Get("xxx"), Equals, "yyy")
12 | h.Add("xxx", "zzz")
13 | c.Assert(h.GetAll("xxx"), DeepEquals, []string{"yyy", "zzz"})
14 | h.Set("xxx", "111")
15 | c.Assert(h.Get("xxx"), Equals, "111")
16 | h.Del("xxx")
17 | c.Assert(h.Get("xxx"), Equals, "")
18 | }
19 |
20 | func (s *FrameSuite) TestHeaderClone(c *C) {
21 | h := Header{}
22 | h.Set("xxx", "yyy")
23 | h.Set("yyy", "zzz")
24 |
25 | hc := h.Clone()
26 | h.Del("xxx")
27 | h.Del("yyy")
28 | c.Assert(hc.Get("xxx"), Equals, "yyy")
29 | c.Assert(hc.Get("yyy"), Equals, "zzz")
30 | }
31 |
32 | func (s *FrameSuite) TestHeaderContains(c *C) {
33 | h := NewHeader("xxx", "yyy", "zzz", "aaa", "xxx", "ccc")
34 | v, ok := h.Contains("xxx")
35 | c.Assert(v, Equals, "yyy")
36 | c.Assert(ok, Equals, true)
37 |
38 | v, ok = h.Contains("123")
39 | c.Assert(v, Equals, "")
40 | c.Assert(ok, Equals, false)
41 | }
42 |
43 | func (s *FrameSuite) TestContentLength(c *C) {
44 | h := NewHeader("xxx", "yy", "content-length", "202", "zz", "123")
45 | cl, ok, err := h.ContentLength()
46 | c.Assert(cl, Equals, 202)
47 | c.Assert(ok, Equals, true)
48 | c.Assert(err, Equals, nil)
49 |
50 | h.Set("content-length", "twenty")
51 | cl, ok, err = h.ContentLength()
52 | c.Assert(cl, Equals, 0)
53 | c.Assert(ok, Equals, true)
54 | c.Assert(err, NotNil)
55 |
56 | h.Del("content-length")
57 | cl, ok, err = h.ContentLength()
58 | c.Assert(cl, Equals, 0)
59 | c.Assert(ok, Equals, false)
60 | c.Assert(err, IsNil)
61 | }
62 |
63 | func (s *FrameSuite) TestLit(c *C) {
64 | _ = Frame{
65 | Command: "CONNECT",
66 | Header: NewHeader("login", "xxx", "passcode", "yyy"),
67 | Body: []byte{1, 2, 3, 4},
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/heartbeat.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "math"
5 | "regexp"
6 | "strconv"
7 | "strings"
8 | "time"
9 | )
10 |
11 | var (
12 | // Regexp for heart-beat header value
13 | heartBeatRegexp = regexp.MustCompile("^[0-9]+,[0-9]+$")
14 | )
15 |
16 | const (
17 | // Maximum number of milliseconds that can be represented
18 | // in a time.Duration.
19 | maxMilliseconds = math.MaxInt64 / int64(time.Millisecond)
20 | )
21 |
22 | // ParseHeartBeat parses the value of a STOMP heart-beat entry and
23 | // returns two time durations. Returns an error if the heart-beat
24 | // value is not in the correct format, or if the time durations are
25 | // too big to be represented by the time.Duration type.
26 | func ParseHeartBeat(heartBeat string) (time.Duration, time.Duration, error) {
27 | if !heartBeatRegexp.MatchString(heartBeat) {
28 | return 0, 0, ErrInvalidHeartBeat
29 | }
30 | slice := strings.Split(heartBeat, ",")
31 | value1, err := strconv.ParseInt(slice[0], 10, 64)
32 | if err != nil {
33 | return 0, 0, ErrInvalidHeartBeat
34 | }
35 | value2, err := strconv.ParseInt(slice[1], 10, 64)
36 | if err != nil {
37 | return 0, 0, ErrInvalidHeartBeat
38 | }
39 | if value1 > maxMilliseconds || value2 > maxMilliseconds {
40 | return 0, 0, ErrInvalidHeartBeat
41 | }
42 | return time.Duration(value1) * time.Millisecond,
43 | time.Duration(value2) * time.Millisecond, nil
44 | }
45 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/frame/writer_test.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 |
7 | . "gopkg.in/check.v1"
8 | )
9 |
10 | type WriterSuite struct{}
11 |
12 | var _ = Suite(&WriterSuite{})
13 |
14 | func (s *WriterSuite) TestWrites(c *C) {
15 | var frameTexts = []string{
16 | "CONNECT\nlogin:xxx\npasscode:yyy\n\n\x00",
17 |
18 | "SEND\n" +
19 | "destination:/queue/request\n" +
20 | "tx:1\n" +
21 | "content-length:5\n" +
22 | "\n\x00\x01\x02\x03\x04\x00",
23 |
24 | "SEND\ndestination:x\n\nABCD\x00",
25 |
26 | "SEND\ndestination:x\ndodgy\\nheader\\c:abc\\n\\c\n\n123456\x00",
27 | }
28 |
29 | for _, frameText := range frameTexts {
30 | writeToBufferAndCheck(c, frameText)
31 | }
32 | }
33 |
34 | func writeToBufferAndCheck(c *C, frameText string) {
35 | reader := NewReader(strings.NewReader(frameText))
36 |
37 | frame, err := reader.Read()
38 | c.Assert(err, IsNil)
39 | c.Assert(frame, NotNil)
40 |
41 | var b bytes.Buffer
42 | var writer = NewWriter(&b)
43 | err = writer.Write(frame)
44 | c.Assert(err, IsNil)
45 | newFrameText := b.String()
46 | c.Check(newFrameText, Equals, frameText)
47 | c.Check(b.String(), Equals, frameText)
48 | }
49 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-stomp/stomp/v3
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/golang/mock v1.6.0
7 | github.com/joho/godotenv v1.5.1
8 | github.com/kr/pretty v0.3.1 // indirect
9 | github.com/kr/text v0.2.0 // indirect
10 | github.com/rogpeppe/go-internal v1.12.0 // indirect
11 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
12 | )
13 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/id.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "strconv"
5 | "sync/atomic"
6 | )
7 |
8 | var _lastId uint64
9 |
10 | // allocateId returns a unique number for the current
11 | // process. Starts at one and increases. Used for
12 | // allocating subscription ids, receipt ids,
13 | // transaction ids, etc.
14 | func allocateId() string {
15 | id := atomic.AddUint64(&_lastId, 1)
16 | return strconv.FormatUint(id, 10)
17 | }
18 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/id_test.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | . "gopkg.in/check.v1"
5 | "runtime"
6 | )
7 |
8 | // only used during testing, does not need to be thread-safe
9 | func resetId() {
10 | _lastId = 0
11 | }
12 |
13 | func (s *StompSuite) SetUpSuite(c *C) {
14 | resetId()
15 | runtime.GOMAXPROCS(runtime.NumCPU())
16 | }
17 |
18 | func (s *StompSuite) TearDownSuite(c *C) {
19 | runtime.GOMAXPROCS(1)
20 | }
21 |
22 | func (s *StompSuite) TestAllocateId(c *C) {
23 | c.Assert(allocateId(), Equals, "1")
24 | c.Assert(allocateId(), Equals, "2")
25 |
26 | ch := make(chan bool, 50)
27 | for i := 0; i < 50; i++ {
28 | go doAllocate(100, ch)
29 | }
30 |
31 | for i := 0; i < 50; i++ {
32 | <-ch
33 | }
34 |
35 | c.Assert(allocateId(), Equals, "5003")
36 | }
37 |
38 | func doAllocate(count int, ch chan bool) {
39 | for i := 0; i < count; i++ {
40 | _ = allocateId()
41 | }
42 | ch <- true
43 | }
44 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/internal/log/stdlogger.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "fmt"
5 | stdlog "log"
6 | )
7 |
8 | var (
9 | debugPrefix = "DEBUG: "
10 | infoPrefix = "INFO: "
11 | warnPrefix = "WARN: "
12 | errorPrefix = "ERROR: "
13 | )
14 |
15 | func logf(prefix string, format string, value ...interface{}) {
16 | _ = stdlog.Output(3, fmt.Sprintf(prefix+format+"\n", value...))
17 | }
18 |
19 | type StdLogger struct{}
20 |
21 | func (s StdLogger) Debugf(format string, value ...interface{}) {
22 | logf(debugPrefix, format, value...)
23 | }
24 |
25 | func (s StdLogger) Debug(message string) {
26 | logf(debugPrefix, "%s", message)
27 | }
28 |
29 | func (s StdLogger) Infof(format string, value ...interface{}) {
30 | logf(infoPrefix, format, value...)
31 | }
32 |
33 | func (s StdLogger) Info(message string) {
34 | logf(infoPrefix, "%s", message)
35 | }
36 |
37 | func (s StdLogger) Warningf(format string, value ...interface{}) {
38 | logf(warnPrefix, format, value...)
39 | }
40 |
41 | func (s StdLogger) Warning(message string) {
42 | logf(warnPrefix, "%s", message)
43 | }
44 |
45 | func (s StdLogger) Errorf(format string, value ...interface{}) {
46 | logf(errorPrefix, format, value...)
47 | }
48 |
49 | func (s StdLogger) Error(message string) {
50 | logf(errorPrefix, "%s", message)
51 | }
52 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/logger.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | type Logger interface {
4 | Debugf(format string, value ...interface{})
5 | Infof(format string, value ...interface{})
6 | Warningf(format string, value ...interface{})
7 | Errorf(format string, value ...interface{})
8 |
9 | Debug(message string)
10 | Info(message string)
11 | Warning(message string)
12 | Error(message string)
13 | }
14 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/send_options.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | )
6 |
7 | // SendOpt contains options for for the Conn.Send and Transaction.Send functions.
8 | var SendOpt struct {
9 | // Receipt specifies that the client should request acknowledgement
10 | // from the server before the send operation successfully completes.
11 | Receipt func(*frame.Frame) error
12 |
13 | // NoContentLength specifies that the SEND frame should not include
14 | // a content-length header entry. By default the content-length header
15 | // entry is always included, but some message brokers assign special
16 | // meaning to STOMP frames that do not contain a content-length
17 | // header entry. (In particular ActiveMQ interprets STOMP frames
18 | // with no content-length as being a text message)
19 | NoContentLength func(*frame.Frame) error
20 |
21 | // Header provides the opportunity to include custom header entries
22 | // in the SEND frame that the client sends to the server. This option
23 | // can be specified multiple times if multiple custom header entries
24 | // are required.
25 | Header func(key, value string) func(*frame.Frame) error
26 | }
27 |
28 | func init() {
29 | SendOpt.Receipt = func(f *frame.Frame) error {
30 | if f.Command != frame.SEND {
31 | return ErrInvalidCommand
32 | }
33 | id := allocateId()
34 | f.Header.Set(frame.Receipt, id)
35 | return nil
36 | }
37 |
38 | SendOpt.NoContentLength = func(f *frame.Frame) error {
39 | if f.Command != frame.SEND {
40 | return ErrInvalidCommand
41 | }
42 | f.Header.Del(frame.ContentLength)
43 | return nil
44 | }
45 |
46 | SendOpt.Header = func(key, value string) func(*frame.Frame) error {
47 | return func(f *frame.Frame) error {
48 | if f.Command != frame.SEND {
49 | return ErrInvalidCommand
50 | }
51 | f.Header.Add(key, value)
52 | return nil
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/channel_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | . "gopkg.in/check.v1"
5 | )
6 |
7 | // Test suite for testing that channels work the way I expect.
8 | type ChannelSuite struct{}
9 |
10 | var _ = Suite(&ChannelSuite{})
11 |
12 | func (s *ChannelSuite) TestChannelWhenClosed(c *C) {
13 |
14 | ch := make(chan int, 10)
15 |
16 | ch <- 1
17 | ch <- 2
18 |
19 | select {
20 | case i, ok := <-ch:
21 | c.Assert(i, Equals, 1)
22 | c.Assert(ok, Equals, true)
23 | default:
24 | c.Error("expected value on channel")
25 | }
26 |
27 | select {
28 | case i := <-ch:
29 | c.Assert(i, Equals, 2)
30 | default:
31 | c.Error("expected value on channel")
32 | }
33 |
34 | select {
35 | case _ = <-ch:
36 | c.Error("not expecting anything on the channel")
37 | default:
38 | }
39 |
40 | ch <- 3
41 | close(ch)
42 |
43 | select {
44 | case i := <-ch:
45 | c.Assert(i, Equals, 3)
46 | default:
47 | c.Error("expected value on channel")
48 | }
49 |
50 | select {
51 | case _, ok := <-ch:
52 | c.Assert(ok, Equals, false)
53 | default:
54 | c.Error("expected value on channel")
55 | }
56 |
57 | select {
58 | case _, ok := <-ch:
59 | c.Assert(ok, Equals, false)
60 | default:
61 | c.Error("expected value on channel")
62 | }
63 | }
64 |
65 | func (s *ChannelSuite) TestMultipleChannels(c *C) {
66 |
67 | ch1 := make(chan int, 10)
68 | ch2 := make(chan string, 10)
69 |
70 | ch1 <- 1
71 |
72 | select {
73 | case i, ok := <-ch1:
74 | c.Assert(i, Equals, 1)
75 | c.Assert(ok, Equals, true)
76 | case _ = <-ch2:
77 | default:
78 | c.Error("expected value on channel")
79 | }
80 |
81 | select {
82 | case _ = <-ch1:
83 | c.Error("not expected")
84 | case _ = <-ch2:
85 | c.Error("not expected")
86 | default:
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/client.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package client implements client connectivity in the STOMP server.
3 |
4 | The key abstractions include a connection, a subscription and
5 | a client request.
6 | */
7 | package client
8 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/client_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "gopkg.in/check.v1"
5 | "testing"
6 | )
7 |
8 | // Runs all gocheck tests in this package.
9 | // See other *_test.go files for gocheck tests.
10 | func TestClient(t *testing.T) {
11 | check.TestingT(t)
12 | }
13 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/config.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/go-stomp/stomp/v3"
7 | )
8 |
9 | // Contains information the client package needs from the
10 | // rest of the STOMP server code.
11 | type Config interface {
12 | // Method to authenticate a login and associated passcode.
13 | // Returns true if login/passcode is valid, false otherwise.
14 | Authenticate(login, passcode string) bool
15 |
16 | // Default duration for read/write heart-beat values. If this
17 | // returns zero, no heart-beat will take place. If this value is
18 | // larger than the maximu permitted value (which is more than
19 | // 11 days, but less than 12 days), then it is truncated to the
20 | // maximum permitted values.
21 | HeartBeat() time.Duration
22 |
23 | // Logger provides the logger for a client
24 | Logger() stomp.Logger
25 | }
26 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/errors.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | const (
4 | notConnected = errorMessage("expected CONNECT or STOMP frame")
5 | unexpectedCommand = errorMessage("unexpected frame command")
6 | unknownCommand = errorMessage("unknown command")
7 | receiptInConnect = errorMessage("receipt header prohibited in CONNECT or STOMP frame")
8 | authenticationFailed = errorMessage("authentication failed")
9 | txAlreadyInProgress = errorMessage("transaction already in progress")
10 | txUnknown = errorMessage("unknown transaction")
11 | unsupportedVersion = errorMessage("unsupported version")
12 | subscriptionExists = errorMessage("subscription already exists")
13 | subscriptionNotFound = errorMessage("subscription not found")
14 | invalidFrameFormat = errorMessage("invalid frame format")
15 | invalidCommand = errorMessage("invalid command")
16 | unknownVersion = errorMessage("incompatible version")
17 | notConnectFrame = errorMessage("operation valid for STOMP and CONNECT frames only")
18 | invalidHeartBeat = errorMessage("invalid format for heart-beat")
19 | invalidOperationForFrame = errorMessage("invalid operation for frame")
20 | exceededMaxFrameSize = errorMessage("exceeded max frame size")
21 | invalidHeaderValue = errorMessage("invalid header value")
22 | )
23 |
24 | type errorMessage string
25 |
26 | func (e errorMessage) Error() string {
27 | return string(e)
28 | }
29 |
30 | func missingHeader(name string) errorMessage {
31 | return errorMessage("missing header: " + name)
32 | }
33 |
34 | func prohibitedHeader(name string) errorMessage {
35 | return errorMessage("prohibited header: " + name)
36 | }
37 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/request.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/go-stomp/stomp/v3/frame"
7 | )
8 |
9 | // Opcode used in client requests.
10 | type RequestOp int
11 |
12 | func (r RequestOp) String() string {
13 | return strconv.Itoa(int(r))
14 | }
15 |
16 | // Valid value for client request opcodes.
17 | const (
18 | SubscribeOp RequestOp = iota // subscription ready
19 | UnsubscribeOp // subscription not ready
20 | EnqueueOp // send a message to a queue
21 | RequeueOp // re-queue a message, not successfully sent
22 | ConnectedOp // connection established
23 | DisconnectedOp // connection disconnected
24 | )
25 |
26 | // Client requests received to be processed by main processing loop
27 | type Request struct {
28 | Op RequestOp // opcode for request
29 | Sub *Subscription // SubscribeOp, UnsubscribeOp
30 | Frame *frame.Frame // EnqueueOp, RequeueOp
31 | Conn *Conn // ConnectedOp, DisconnectedOp
32 | }
33 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/tx_store.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "container/list"
5 |
6 | "github.com/go-stomp/stomp/v3/frame"
7 | )
8 |
9 | type txStore struct {
10 | transactions map[string]*list.List
11 | }
12 |
13 | // Initializes a new store or clears out an existing store
14 | func (txs *txStore) Init() {
15 | txs.transactions = nil
16 | }
17 |
18 | func (txs *txStore) Begin(tx string) error {
19 | if txs.transactions == nil {
20 | txs.transactions = make(map[string]*list.List)
21 | }
22 |
23 | if _, ok := txs.transactions[tx]; ok {
24 | return txAlreadyInProgress
25 | }
26 |
27 | txs.transactions[tx] = list.New()
28 | return nil
29 | }
30 |
31 | func (txs *txStore) Abort(tx string) error {
32 | if list, ok := txs.transactions[tx]; ok {
33 | list.Init()
34 | delete(txs.transactions, tx)
35 | return nil
36 | }
37 | return txUnknown
38 | }
39 |
40 | // Commit causes all requests that have been queued for the transaction
41 | // to be sent to the request channel for processing. Calls the commit
42 | // function (commitFunc) in order for each request that is part of the
43 | // transaction.
44 | func (txs *txStore) Commit(tx string, commitFunc func(f *frame.Frame) error) error {
45 | if list, ok := txs.transactions[tx]; ok {
46 | for element := list.Front(); element != nil; element = list.Front() {
47 | err := commitFunc(list.Remove(element).(*frame.Frame))
48 | if err != nil {
49 | return err
50 | }
51 | }
52 | delete(txs.transactions, tx)
53 | return nil
54 | }
55 | return txUnknown
56 | }
57 |
58 | func (txs *txStore) Add(tx string, f *frame.Frame) error {
59 | if list, ok := txs.transactions[tx]; ok {
60 | f.Header.Del(frame.Transaction)
61 | list.PushBack(f)
62 | return nil
63 | }
64 | return txUnknown
65 | }
66 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/tx_store_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | . "gopkg.in/check.v1"
6 | )
7 |
8 | type TxStoreSuite struct{}
9 |
10 | var _ = Suite(&TxStoreSuite{})
11 |
12 | func (s *TxStoreSuite) TestDoubleBegin(c *C) {
13 | txs := txStore{}
14 |
15 | err := txs.Begin("tx1")
16 | c.Assert(err, IsNil)
17 |
18 | err = txs.Begin("tx1")
19 | c.Assert(err, Equals, txAlreadyInProgress)
20 | }
21 |
22 | func (s *TxStoreSuite) TestSuccessfulTx(c *C) {
23 | txs := txStore{}
24 |
25 | err := txs.Begin("tx1")
26 | c.Check(err, IsNil)
27 |
28 | err = txs.Begin("tx2")
29 | c.Assert(err, IsNil)
30 |
31 | f1 := frame.New(frame.MESSAGE,
32 | frame.Destination, "/queue/1")
33 |
34 | f2 := frame.New(frame.MESSAGE,
35 | frame.Destination, "/queue/2")
36 |
37 | f3 := frame.New(frame.MESSAGE,
38 | frame.Destination, "/queue/3")
39 |
40 | f4 := frame.New(frame.MESSAGE,
41 | frame.Destination, "/queue/4")
42 |
43 | err = txs.Add("tx1", f1)
44 | c.Assert(err, IsNil)
45 | err = txs.Add("tx1", f2)
46 | c.Assert(err, IsNil)
47 | err = txs.Add("tx1", f3)
48 | c.Assert(err, IsNil)
49 | err = txs.Add("tx2", f4)
50 |
51 | var tx1 []*frame.Frame
52 |
53 | txs.Commit("tx1", func(f *frame.Frame) error {
54 | tx1 = append(tx1, f)
55 | return nil
56 | })
57 | c.Check(err, IsNil)
58 |
59 | var tx2 []*frame.Frame
60 |
61 | err = txs.Commit("tx2", func(f *frame.Frame) error {
62 | tx2 = append(tx2, f)
63 | return nil
64 | })
65 | c.Check(err, IsNil)
66 |
67 | c.Check(len(tx1), Equals, 3)
68 | c.Check(tx1[0], Equals, f1)
69 | c.Check(tx1[1], Equals, f2)
70 | c.Check(tx1[2], Equals, f3)
71 |
72 | c.Check(len(tx2), Equals, 1)
73 | c.Check(tx2[0], Equals, f4)
74 |
75 | // already committed, so should cause an error
76 | err = txs.Commit("tx1", func(f *frame.Frame) error {
77 | c.Fatal("should not be called")
78 | return nil
79 | })
80 | c.Check(err, Equals, txUnknown)
81 | }
82 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/util.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // Convert a time.Duration to milliseconds in an integer.
8 | // Returns the duration in milliseconds, or max if the
9 | // duration is greater than max milliseconds.
10 | func asMilliseconds(d time.Duration, max int) int {
11 | if max < 0 {
12 | max = 0
13 | }
14 | max64 := int64(max)
15 | msec64 := int64(d / time.Millisecond)
16 | if msec64 > max64 {
17 | msec64 = max64
18 | }
19 | msec := int(msec64)
20 | return msec
21 | }
22 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/client/util_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | . "gopkg.in/check.v1"
5 | "math"
6 | "time"
7 | )
8 |
9 | type UtilSuite struct{}
10 |
11 | var _ = Suite(&UtilSuite{})
12 |
13 | func (s *UtilSuite) TestAsMilliseconds(c *C) {
14 | d := time.Duration(30) * time.Millisecond
15 | c.Check(asMilliseconds(d, math.MaxInt32), Equals, 30)
16 |
17 | // approximately one year
18 | d = time.Duration(365) * time.Duration(24) * time.Hour
19 | c.Check(asMilliseconds(d, math.MaxInt32), Equals, math.MaxInt32)
20 |
21 | d = time.Duration(365) * time.Duration(24) * time.Hour
22 | c.Check(asMilliseconds(d, maxHeartBeat), Equals, maxHeartBeat)
23 | }
24 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/queue/manager.go:
--------------------------------------------------------------------------------
1 | package queue
2 |
3 | // Queue manager.
4 | type Manager struct {
5 | qstore Storage // handles queue storage
6 | queues map[string]*Queue
7 | }
8 |
9 | // Create a queue manager with the specified queue storage mechanism
10 | func NewManager(qstore Storage) *Manager {
11 | qm := &Manager{qstore: qstore, queues: make(map[string]*Queue)}
12 | return qm
13 | }
14 |
15 | // Finds the queue for the given destination, and creates it if necessary.
16 | func (qm *Manager) Find(destination string) *Queue {
17 | q, ok := qm.queues[destination]
18 | if !ok {
19 | q = newQueue(destination, qm.qstore)
20 | qm.queues[destination] = q
21 | }
22 | return q
23 | }
24 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/queue/manager_test.go:
--------------------------------------------------------------------------------
1 | package queue
2 |
3 | import (
4 | . "gopkg.in/check.v1"
5 | )
6 |
7 | type ManagerSuite struct{}
8 |
9 | var _ = Suite(&ManagerSuite{})
10 |
11 | func (s *ManagerSuite) TestManager(c *C) {
12 | mgr := NewManager(NewMemoryQueueStorage())
13 |
14 | q1 := mgr.Find("/queue/1")
15 | c.Assert(q1, NotNil)
16 |
17 | q2 := mgr.Find("/queue/2")
18 | c.Assert(q2, NotNil)
19 |
20 | c.Assert(mgr.Find("/queue/1"), Equals, q1)
21 | }
22 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/queue/memory_queue.go:
--------------------------------------------------------------------------------
1 | package queue
2 |
3 | import (
4 | "container/list"
5 |
6 | "github.com/go-stomp/stomp/v3/frame"
7 | )
8 |
9 | // In-memory implementation of the QueueStorage interface.
10 | type MemoryQueueStorage struct {
11 | lists map[string]*list.List
12 | }
13 |
14 | func NewMemoryQueueStorage() Storage {
15 | m := &MemoryQueueStorage{lists: make(map[string]*list.List)}
16 | return m
17 | }
18 |
19 | func (m *MemoryQueueStorage) Enqueue(queue string, frame *frame.Frame) error {
20 | l, ok := m.lists[queue]
21 | if !ok {
22 | l = list.New()
23 | m.lists[queue] = l
24 | }
25 | l.PushBack(frame)
26 |
27 | return nil
28 | }
29 |
30 | // Pushes a frame to the head of the queue. Sets
31 | // the "message-id" header of the frame if it is not
32 | // already set.
33 | func (m *MemoryQueueStorage) Requeue(queue string, frame *frame.Frame) error {
34 | l, ok := m.lists[queue]
35 | if !ok {
36 | l = list.New()
37 | m.lists[queue] = l
38 | }
39 | l.PushFront(frame)
40 |
41 | return nil
42 | }
43 |
44 | // Removes a frame from the head of the queue.
45 | // Returns nil if no frame is available.
46 | func (m *MemoryQueueStorage) Dequeue(queue string) (*frame.Frame, error) {
47 | l, ok := m.lists[queue]
48 | if !ok {
49 | return nil, nil
50 | }
51 |
52 | element := l.Front()
53 | if element == nil {
54 | return nil, nil
55 | }
56 |
57 | return l.Remove(element).(*frame.Frame), nil
58 | }
59 |
60 | // Called at server startup. Allows the queue storage
61 | // to perform any initialization.
62 | func (m *MemoryQueueStorage) Start() {
63 | m.lists = make(map[string]*list.List)
64 | }
65 |
66 | // Called prior to server shutdown. Allows the queue storage
67 | // to perform any cleanup.
68 | func (m *MemoryQueueStorage) Stop() {
69 | m.lists = nil
70 | }
71 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/queue/memory_queue_test.go:
--------------------------------------------------------------------------------
1 | package queue
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | . "gopkg.in/check.v1"
6 | )
7 |
8 | type MemoryQueueSuite struct{}
9 |
10 | var _ = Suite(&MemoryQueueSuite{})
11 |
12 | func (s *MemoryQueueSuite) Test1(c *C) {
13 | mq := NewMemoryQueueStorage()
14 | mq.Start()
15 |
16 | f1 := frame.New(frame.MESSAGE,
17 | frame.Destination, "/queue/test",
18 | frame.MessageId, "msg-001",
19 | frame.Subscription, "1")
20 |
21 | err := mq.Enqueue("/queue/test", f1)
22 | c.Assert(err, IsNil)
23 |
24 | f2 := frame.New(frame.MESSAGE,
25 | frame.Destination, "/queue/test",
26 | frame.MessageId, "msg-002",
27 | frame.Subscription, "1")
28 |
29 | err = mq.Enqueue("/queue/test", f2)
30 | c.Assert(err, IsNil)
31 |
32 | f3 := frame.New(frame.MESSAGE,
33 | frame.Destination, "/queue/test2",
34 | frame.MessageId, "msg-003",
35 | frame.Subscription, "2")
36 |
37 | err = mq.Enqueue("/queue/test2", f3)
38 | c.Assert(err, IsNil)
39 |
40 | // attempt to dequeue from a different queue
41 | f, err := mq.Dequeue("/queue/other-queue")
42 | c.Check(err, IsNil)
43 | c.Assert(f, IsNil)
44 |
45 | f, err = mq.Dequeue("/queue/test2")
46 | c.Check(err, IsNil)
47 | c.Assert(f, Equals, f3)
48 |
49 | f, err = mq.Dequeue("/queue/test")
50 | c.Check(err, IsNil)
51 | c.Assert(f, Equals, f1)
52 |
53 | f, err = mq.Dequeue("/queue/test")
54 | c.Check(err, IsNil)
55 | c.Assert(f, Equals, f2)
56 |
57 | f, err = mq.Dequeue("/queue/test")
58 | c.Check(err, IsNil)
59 | c.Assert(f, IsNil)
60 |
61 | f, err = mq.Dequeue("/queue/test2")
62 | c.Check(err, IsNil)
63 | c.Assert(f, IsNil)
64 | }
65 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/queue/queue_test.go:
--------------------------------------------------------------------------------
1 | package queue
2 |
3 | import (
4 | "gopkg.in/check.v1"
5 | "testing"
6 | )
7 |
8 | // Runs all gocheck tests in this package.
9 | // See other *_test.go files for gocheck tests.
10 | func TestQueue(t *testing.T) {
11 | check.TestingT(t)
12 | }
13 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/queue/storage.go:
--------------------------------------------------------------------------------
1 | package queue
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | )
6 |
7 | // Interface for queue storage. The intent is that
8 | // different queue storage implementations can be
9 | // used, depending on preference. Queue storage
10 | // mechanisms could include in-memory, and various
11 | // persistent storage mechanisms (eg file system, DB, etc)
12 | type Storage interface {
13 | // Pushes a MESSAGE frame to the end of the queue. Sets
14 | // the "message-id" header of the frame before adding to
15 | // the queue.
16 | Enqueue(queue string, frame *frame.Frame) error
17 |
18 | // Pushes a MESSAGE frame to the head of the queue. Sets
19 | // the "message-id" header of the frame if it is not
20 | // already set.
21 | Requeue(queue string, frame *frame.Frame) error
22 |
23 | // Removes a frame from the head of the queue.
24 | // Returns nil if no frame is available.
25 | Dequeue(queue string) (*frame.Frame, error)
26 |
27 | // Called at server startup. Allows the queue storage
28 | // to perform any initialization.
29 | Start()
30 |
31 | // Called prior to server shutdown. Allows the queue storage
32 | // to perform any cleanup.
33 | Stop()
34 | }
35 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/queue_storage.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | )
6 |
7 | // QueueStorage is an interface that abstracts the queue storage mechanism.
8 | // The intent is that different queue storage implementations can be
9 | // used, depending on preference. Queue storage mechanisms could include
10 | // in-memory, and various persistent storage mechanisms (eg file system, DB, etc).
11 | type QueueStorage interface {
12 | // Enqueue adds a MESSAGE frame to the end of the queue.
13 | Enqueue(queue string, frame *frame.Frame) error
14 |
15 | // Requeue adds a MESSAGE frame to the head of the queue.
16 | // This will happen if a client fails to acknowledge receipt.
17 | Requeue(queue string, frame *frame.Frame) error
18 |
19 | // Dequeue removes a frame from the head of the queue.
20 | // Returns nil if no frame is available.
21 | Dequeue(queue string) (*frame.Frame, error)
22 |
23 | // Start is called at server startup. Allows the queue storage
24 | // to perform any initialization.
25 | Start()
26 |
27 | // Stop is called prior to server shutdown. Allows the queue storage
28 | // to perform any cleanup, such as flushing to disk.
29 | Stop()
30 | }
31 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/topic/manager.go:
--------------------------------------------------------------------------------
1 | package topic
2 |
3 | // Manager is a struct responsible for finding topics. Topics are
4 | // not created by the package user, rather they are created on demand
5 | // by the topic manager.
6 | type Manager struct {
7 | topics map[string]*Topic
8 | }
9 |
10 | // NewManager creates a new topic manager.
11 | func NewManager() *Manager {
12 | tm := &Manager{topics: make(map[string]*Topic)}
13 | return tm
14 | }
15 |
16 | // Finds the topic for the given destination, and creates it if necessary.
17 | func (tm *Manager) Find(destination string) *Topic {
18 | t, ok := tm.topics[destination]
19 | if !ok {
20 | t = newTopic(destination)
21 | tm.topics[destination] = t
22 | }
23 | return t
24 | }
25 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/topic/manager_test.go:
--------------------------------------------------------------------------------
1 | package topic
2 |
3 | import (
4 | . "gopkg.in/check.v1"
5 | )
6 |
7 | type ManagerSuite struct{}
8 |
9 | var _ = Suite(&ManagerSuite{})
10 |
11 | func (s *ManagerSuite) TestManager(c *C) {
12 | mgr := NewManager()
13 |
14 | t1 := mgr.Find("topic1")
15 | c.Assert(t1, NotNil)
16 |
17 | t2 := mgr.Find("topic2")
18 | c.Assert(t2, NotNil)
19 |
20 | c.Assert(mgr.Find("topic1"), Equals, t1)
21 | }
22 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/topic/subscription.go:
--------------------------------------------------------------------------------
1 | package topic
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | )
6 |
7 | // Subscription is the interface that wraps a subscriber to a topic.
8 | type Subscription interface {
9 | // Send a message frame to the topic subscriber.
10 | SendTopicFrame(f *frame.Frame)
11 | }
12 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/topic/testing_test.go:
--------------------------------------------------------------------------------
1 | package topic
2 |
3 | import (
4 | "gopkg.in/check.v1"
5 | "testing"
6 | )
7 |
8 | // Runs all gocheck tests in this package.
9 | // See other *_test.go files for gocheck tests.
10 | func Test(t *testing.T) {
11 | check.TestingT(t)
12 | }
13 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/server/topic/topic_test.go:
--------------------------------------------------------------------------------
1 | package topic
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | . "gopkg.in/check.v1"
6 | )
7 |
8 | type TopicSuite struct{}
9 |
10 | var _ = Suite(&TopicSuite{})
11 |
12 | func (s *TopicSuite) TestTopicWithoutSubscription(c *C) {
13 | topic := newTopic("destination")
14 |
15 | f := frame.New(frame.MESSAGE,
16 | frame.Destination, "destination")
17 |
18 | topic.Enqueue(f)
19 | }
20 |
21 | func (s *TopicSuite) TestTopicWithOneSubscription(c *C) {
22 | sub := &fakeSubscription{}
23 |
24 | topic := newTopic("destination")
25 | topic.Subscribe(sub)
26 |
27 | f := frame.New(frame.MESSAGE,
28 | frame.Destination, "destination")
29 |
30 | topic.Enqueue(f)
31 |
32 | c.Assert(len(sub.Frames), Equals, 1)
33 | c.Assert(sub.Frames[0], Equals, f)
34 | }
35 |
36 | func (s *TopicSuite) TestTopicWithTwoSubscriptions(c *C) {
37 | sub1 := &fakeSubscription{}
38 | sub2 := &fakeSubscription{}
39 |
40 | topic := newTopic("destination")
41 | topic.Subscribe(sub1)
42 | topic.Subscribe(sub2)
43 |
44 | f := frame.New(frame.MESSAGE,
45 | frame.Destination, "destination",
46 | "xxx", "yyy")
47 |
48 | topic.Enqueue(f)
49 |
50 | c.Assert(len(sub1.Frames), Equals, 1)
51 | c.Assert(len(sub2.Frames), Equals, 1)
52 | c.Assert(sub1.Frames[0], Not(Equals), f)
53 | c.Assert(sub2.Frames[0], Equals, f)
54 | }
55 |
56 | type fakeSubscription struct {
57 | // frames received by the subscription
58 | Frames []*frame.Frame
59 | }
60 |
61 | func (s *fakeSubscription) SendTopicFrame(f *frame.Frame) {
62 | s.Frames = append(s.Frames, f)
63 | }
64 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/stomp.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package stomp provides operations that allow communication with a message broker that supports the STOMP protocol.
3 | STOMP is the Streaming Text-Oriented Messaging Protocol. See http://stomp.github.com/ for more details.
4 |
5 | This package provides support for all STOMP protocol features in the STOMP protocol specifications,
6 | versions 1.0, 1.1 and 1.2. These features including protocol negotiation, heart-beating, value encoding,
7 | and graceful shutdown.
8 |
9 | Connecting to a STOMP server is achieved using the stomp.Dial function, or the stomp.Connect function. See
10 | the examples section for a summary of how to use these functions. Both functions return a stomp.Conn object
11 | for subsequent interaction with the STOMP server.
12 |
13 | Once a connection (stomp.Conn) is created, it can be used to send messages to the STOMP server, or create
14 | subscriptions for receiving messages from the STOMP server. Transactions can be created to send multiple
15 | messages and/ or acknowledge multiple received messages from the server in one, atomic transaction. The
16 | examples section has examples of using subscriptions and transactions.
17 |
18 | The client program can instruct the stomp.Conn to gracefully disconnect from the STOMP server using the
19 | Disconnect method. This will perform a graceful shutdown sequence as specified in the STOMP specification.
20 |
21 | Source code and other details for the project are available at GitHub:
22 |
23 | https://github.com/go-stomp/stomp
24 |
25 | */
26 | package stomp
27 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/stomp_test.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "testing"
5 |
6 | "gopkg.in/check.v1"
7 | )
8 |
9 | // Runs all gocheck tests in this package.
10 | // See other *_test.go files for gocheck tests.
11 | func TestStomp(t *testing.T) {
12 | check.Suite(&StompSuite{t})
13 | check.TestingT(t)
14 | }
15 |
16 | type StompSuite struct {
17 | t *testing.T
18 | }
19 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/stompd/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | A simple, stand-alone STOMP server.
3 |
4 | TODO: graceful shutdown
5 |
6 | TODO: UNIX daemon functionality
7 |
8 | TODO: Windows service functionality (if possible?)
9 |
10 | TODO: Logging options (syslog, windows event log)
11 | */
12 | package main
13 |
14 | import (
15 | "flag"
16 | "fmt"
17 | "log"
18 | "net"
19 | "os"
20 |
21 | "github.com/go-stomp/stomp/v3/server"
22 | )
23 |
24 | // TODO: experimenting with ways to gracefully shutdown the server,
25 | // at the moment it just dies ungracefully on SIGINT.
26 |
27 | /*
28 |
29 | func main() {
30 | // create a channel for listening for termination signals
31 | stopChannel := newStopChannel()
32 |
33 | for {
34 | select {
35 | case sig := <-stopChannel:
36 | log.Println("received signal:", sig)
37 | break
38 | }
39 | }
40 |
41 | }
42 | */
43 |
44 | var listenAddr = flag.String("addr", ":61613", "Listen address")
45 | var helpFlag = flag.Bool("help", false, "Show this help text")
46 |
47 | func main() {
48 | flag.Parse()
49 | if *helpFlag {
50 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
51 | flag.PrintDefaults()
52 | os.Exit(1)
53 | }
54 |
55 | l, err := net.Listen("tcp", *listenAddr)
56 | if err != nil {
57 | log.Fatalf("failed to listen: %s", err.Error())
58 | }
59 | defer func() { l.Close() }()
60 |
61 | log.Println("listening on", l.Addr().Network(), l.Addr().String())
62 | server.Serve(l)
63 | }
64 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/stompd/signals.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "os/signal"
6 | )
7 |
8 | // newStopChannel creates a channel for receiving signals
9 | // for stopping the program. Calls an os-dependent setupStopSignals
10 | // function.
11 | func newStopChannel() chan os.Signal {
12 | c := make(chan os.Signal, 2)
13 | signal.Notify(c, os.Interrupt)
14 |
15 | // os dependent between windows and unix
16 | setupStopSignals(c)
17 |
18 | return c
19 | }
20 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/stompd/signals_unix.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "os/signal"
6 | "syscall"
7 | )
8 |
9 | // setupStopSignals sets up UNIX-specific signals for terminating
10 | // the program
11 | func setupStopSignals(signalChannel chan os.Signal) {
12 | // TODO: not sure whether SIGHUP should be used here, only if not in
13 | // daemon mode
14 | signal.Notify(signalChannel, syscall.SIGHUP)
15 |
16 | signal.Notify(signalChannel, syscall.SIGTERM)
17 | }
18 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/stompd/signals_windows.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | func signals(signalChannel chan os.Signal) {
8 | // Windows has no other signals other than os.Interrupt
9 |
10 | // TODO: What might be good here is to simulate a signal
11 | // if running as a Windows service and the stop request is
12 | // received. Not sure how to do this though.
13 | }
14 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/stompd/stompd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/backend/services/mtp/stomp/stompd/stompd
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/subscribe_options.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | )
6 |
7 | // SubscribeOpt contains options for for the Conn.Subscribe function.
8 | var SubscribeOpt struct {
9 | // Id provides the opportunity to specify the value of the "id" header
10 | // entry in the STOMP SUBSCRIBE frame.
11 | //
12 | // If the client program does specify the value for "id",
13 | // it is responsible for choosing a unique value.
14 | Id func(id string) func(*frame.Frame) error
15 |
16 | // Header provides the opportunity to include custom header entries
17 | // in the SUBSCRIBE frame that the client sends to the server.
18 | Header func(key, value string) func(*frame.Frame) error
19 | }
20 |
21 | func init() {
22 | SubscribeOpt.Id = func(id string) func(*frame.Frame) error {
23 | return func(f *frame.Frame) error {
24 | if f.Command != frame.SUBSCRIBE {
25 | return ErrInvalidCommand
26 | }
27 | f.Header.Set(frame.Id, id)
28 | return nil
29 | }
30 | }
31 |
32 | SubscribeOpt.Header = func(key, value string) func(*frame.Frame) error {
33 | return func(f *frame.Frame) error {
34 | if f.Command != frame.SUBSCRIBE &&
35 | f.Command != frame.UNSUBSCRIBE {
36 | return ErrInvalidCommand
37 | }
38 | f.Header.Add(key, value)
39 | return nil
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/testutil/fake_conn_test.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | . "gopkg.in/check.v1"
5 | "testing"
6 | )
7 |
8 | func TestTestUtil(t *testing.T) {
9 | TestingT(t)
10 | }
11 |
12 | type FakeConnSuite struct{}
13 |
14 | var _ = Suite(&FakeConnSuite{})
15 |
16 | func (s *FakeConnSuite) TestFakeConn(c *C) {
17 | //c.Skip("temporary")
18 | fc1, fc2 := NewFakeConn(c)
19 |
20 | one := []byte{1, 2, 3, 4}
21 | two := []byte{5, 6, 7, 8, 9, 10, 11, 12, 13}
22 | stop := make(chan struct{})
23 |
24 | go func() {
25 | defer func() {
26 | fc2.Close()
27 | close(stop)
28 | }()
29 |
30 | rx1 := make([]byte, 6)
31 | n, err := fc2.Read(rx1)
32 | c.Assert(n, Equals, 4)
33 | c.Assert(err, IsNil)
34 | c.Assert(rx1[0:n], DeepEquals, one)
35 |
36 | rx2 := make([]byte, 5)
37 | n, err = fc2.Read(rx2)
38 | c.Assert(n, Equals, 5)
39 | c.Assert(err, IsNil)
40 | c.Assert(rx2, DeepEquals, []byte{5, 6, 7, 8, 9})
41 |
42 | rx3 := make([]byte, 10)
43 | n, err = fc2.Read(rx3)
44 | c.Assert(n, Equals, 4)
45 | c.Assert(err, IsNil)
46 | c.Assert(rx3[0:n], DeepEquals, []byte{10, 11, 12, 13})
47 | }()
48 |
49 | c.Assert(fc1.C, Equals, c)
50 | c.Assert(fc2.C, Equals, c)
51 |
52 | n, err := fc1.Write(one)
53 | c.Assert(n, Equals, 4)
54 | c.Assert(err, IsNil)
55 |
56 | n, err = fc1.Write(two)
57 | c.Assert(n, Equals, len(two))
58 | c.Assert(err, IsNil)
59 |
60 | <-stop
61 | }
62 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/testutil/testutil.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package testutil contains operations useful for testing. In particular,
3 | it provides fake connections useful for testing client/server interactions.
4 | */
5 | package testutil
6 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/validator.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | import (
4 | "github.com/go-stomp/stomp/v3/frame"
5 | )
6 |
7 | // Validator is an interface for validating STOMP frames.
8 | type Validator interface {
9 | // Validate returns nil if the frame is valid, or an error if not valid.
10 | Validate(f *frame.Frame) error
11 | }
12 |
13 | func NewValidator(version Version) Validator {
14 | return validatorNull{}
15 | }
16 |
17 | type validatorNull struct{}
18 |
19 | func (v validatorNull) Validate(f *frame.Frame) error {
20 | return nil
21 | }
22 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/version.go:
--------------------------------------------------------------------------------
1 | package stomp
2 |
3 | // Version is the STOMP protocol version.
4 | type Version string
5 |
6 | const (
7 | V10 Version = "1.0"
8 | V11 Version = "1.1"
9 | V12 Version = "1.2"
10 | )
11 |
12 | // String returns a string representation of the STOMP version.
13 | func (v Version) String() string {
14 | return string(v)
15 | }
16 |
17 | // CheckSupported is used to determine whether a particular STOMP
18 | // version is supported by this library. Returns nil if the version is
19 | // supported, or ErrUnsupportedVersion if not supported.
20 | func (v Version) CheckSupported() error {
21 | switch v {
22 | case V10, V11, V12:
23 | return nil
24 | }
25 | return ErrUnsupportedVersion
26 | }
27 |
28 | // SupportsNack indicates whether this version of the STOMP protocol
29 | // supports use of the NACK command.
30 | func (v Version) SupportsNack() bool {
31 | switch v {
32 | case V10:
33 | return false
34 | case V11, V12:
35 | return true
36 | }
37 |
38 | // unknown version
39 | return false
40 | }
41 |
--------------------------------------------------------------------------------
/backend/services/mtp/stomp/version_test.go:
--------------------------------------------------------------------------------
1 | package stomp_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/go-stomp/stomp/v3"
7 | )
8 |
9 | func TestSupportsNack(t *testing.T) {
10 | testCases := []struct {
11 | Version stomp.Version
12 | SupportsNack bool
13 | }{
14 | {
15 | Version: stomp.Version("1.0"),
16 | SupportsNack: false,
17 | },
18 | {
19 | Version: stomp.Version("1.1"),
20 | SupportsNack: true,
21 | },
22 | {
23 | Version: stomp.Version("1.2"),
24 | SupportsNack: true,
25 | },
26 | {
27 | Version: stomp.Version("xxx"),
28 | SupportsNack: false,
29 | },
30 | }
31 |
32 | for _, testCase := range testCases {
33 | version := testCase.Version
34 | expected := testCase.SupportsNack
35 | actual := version.SupportsNack()
36 | if expected != actual {
37 | t.Errorf("Version %v: SupportsNack: expected %v, actual %v",
38 | version, expected, actual)
39 | }
40 |
41 | }
42 |
43 | }
44 |
45 | func TestCheckSupported(t *testing.T) {
46 | testCases := []struct {
47 | Version stomp.Version
48 | Err error
49 | }{
50 | {
51 | Version: stomp.Version("1.0"),
52 | Err: nil,
53 | },
54 | {
55 | Version: stomp.Version("1.1"),
56 | Err: nil,
57 | },
58 | {
59 | Version: stomp.Version("1.2"),
60 | Err: nil,
61 | },
62 | {
63 | Version: stomp.Version("2.2"),
64 | Err: stomp.ErrUnsupportedVersion,
65 | },
66 | }
67 |
68 | for _, testCase := range testCases {
69 | version := testCase.Version
70 | expected := testCase.Err
71 | actual := version.CheckSupported()
72 | if expected != actual {
73 | t.Errorf("Version %v: CheckSupported: expected %v, actual %v",
74 | version, expected, actual)
75 | }
76 |
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/backend/services/mtp/ws-adapter/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/backend/services/mtp/ws-adapter/.env
--------------------------------------------------------------------------------
/backend/services/mtp/ws-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | .env.local
--------------------------------------------------------------------------------
/backend/services/mtp/ws-adapter/README.md:
--------------------------------------------------------------------------------
1 | - acts as a bridge between websockets server and controller
--------------------------------------------------------------------------------
/backend/services/mtp/ws-adapter/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o ws-adapter cmd/ws-adapter/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/ws-adapter /
8 | ENTRYPOINT ["/ws-adapter"]
--------------------------------------------------------------------------------
/backend/services/mtp/ws-adapter/cmd/ws-adapter/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/OktopUSP/oktopus/backend/services/mtp/ws-adapter/internal/bridge"
10 | "github.com/OktopUSP/oktopus/backend/services/mtp/ws-adapter/internal/config"
11 | "github.com/OktopUSP/oktopus/backend/services/mtp/ws-adapter/internal/nats"
12 | )
13 |
14 | func main() {
15 |
16 | done := make(chan os.Signal, 1)
17 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
18 |
19 | c := config.NewConfig()
20 |
21 | kv, publisher, subscriber := nats.StartNatsClient(c.Nats)
22 |
23 | bridge := bridge.NewBridge(publisher, subscriber, c.Ws.Ctx, c.Ws, kv)
24 |
25 | if !c.Ws.NoTls {
26 | bridge.StartBridge(c.Ws.Port, false)
27 | }
28 |
29 | if c.Ws.TlsEnable {
30 | bridge.StartBridge(c.Ws.TlsPort, true)
31 | }
32 |
33 | <-done
34 |
35 | log.Println("websockets adapter is shutting down...")
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/backend/services/mtp/ws-adapter/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/OktopUSP/oktopus/backend/services/mtp/ws-adapter
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/google/uuid v1.6.0
9 | github.com/gorilla/websocket v1.5.1
10 | github.com/joho/godotenv v1.5.1
11 | github.com/nats-io/nats.go v1.35.0
12 | golang.org/x/sys v0.33.0
13 | google.golang.org/protobuf v1.34.1
14 | )
15 |
16 | require (
17 | github.com/klauspost/compress v1.17.8 // indirect
18 | github.com/nats-io/nkeys v0.4.7 // indirect
19 | github.com/nats-io/nuid v1.0.1 // indirect
20 | golang.org/x/crypto v0.38.0 // indirect
21 | golang.org/x/net v0.40.0 // indirect
22 | golang.org/x/text v0.25.0 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/backend/services/mtp/ws/.env:
--------------------------------------------------------------------------------
1 | SERVER_PORT=""
2 | SERVER_AUTH_TOKEN=""
3 | CONTROLLER_EID=""
4 | SERVER_AUTH_ENABLE=""
5 | SERVER_TLS_ENABLE=""
--------------------------------------------------------------------------------
/backend/services/mtp/ws/.gitignore:
--------------------------------------------------------------------------------
1 | .env.local
2 | *.pem
--------------------------------------------------------------------------------
/backend/services/mtp/ws/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o ws cmd/ws/main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/ws /
8 | ENTRYPOINT ["/ws"]
--------------------------------------------------------------------------------
/backend/services/mtp/ws/cmd/ws/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/OktopUSP/oktopus/ws/internal/config"
10 | "github.com/OktopUSP/oktopus/ws/internal/nats"
11 | "github.com/OktopUSP/oktopus/ws/internal/ws"
12 | )
13 |
14 | func main() {
15 |
16 | done := make(chan os.Signal, 1)
17 |
18 | conf := config.NewConfig()
19 |
20 | // Locks app running until it receives a stop command as Ctrl+C.
21 | signal.Notify(done, syscall.SIGINT)
22 |
23 | _, kv := nats.StartNatsClient(conf.Nats)
24 |
25 | ws.StartNewServer(conf, kv)
26 |
27 | <-done
28 |
29 | log.Println("(⌐■_■) Websockets server is out!")
30 | }
31 |
--------------------------------------------------------------------------------
/backend/services/mtp/ws/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/OktopUSP/oktopus/ws
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/gorilla/mux v1.8.1
9 | github.com/gorilla/websocket v1.5.1
10 | github.com/joho/godotenv v1.5.1
11 | github.com/nats-io/nats.go v1.35.0
12 | google.golang.org/protobuf v1.34.1
13 | )
14 |
15 | require (
16 | github.com/klauspost/compress v1.17.8 // indirect
17 | github.com/nats-io/nkeys v0.4.7 // indirect
18 | github.com/nats-io/nuid v1.0.1 // indirect
19 | golang.org/x/crypto v0.38.0 // indirect
20 | golang.org/x/net v0.40.0 // indirect
21 | golang.org/x/sys v0.33.0 // indirect
22 | golang.org/x/text v0.25.0 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/backend/services/mtp/ws/internal/ws/ws.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | // Websockets server implementation inspired by https://github.com/gorilla/websocket/tree/main/examples/chat
4 |
5 | import (
6 | "log"
7 | "net/http"
8 |
9 | "github.com/OktopUSP/oktopus/ws/internal/config"
10 | "github.com/OktopUSP/oktopus/ws/internal/ws/handler"
11 | "github.com/gorilla/mux"
12 | "github.com/nats-io/nats.go/jetstream"
13 | )
14 |
15 | // Starts New Websockets Server
16 | func StartNewServer(c config.Config, kv jetstream.KeyValue) {
17 | // Initialize handlers of websockets events
18 | go handler.InitHandlers(c.ControllerEID)
19 |
20 | r := mux.NewRouter()
21 | r.HandleFunc("/ws/agent/{passwd}", func(w http.ResponseWriter, r *http.Request) {
22 | handler.ServeAgent(w, r, c.ControllerEID, kv, c.Auth)
23 | })
24 | r.HandleFunc("/ws/agent", func(w http.ResponseWriter, r *http.Request) {
25 | handler.ServeAgent(w, r, c.ControllerEID, kv, c.Auth)
26 | })
27 | r.HandleFunc("/ws/controller", func(w http.ResponseWriter, r *http.Request) {
28 | handler.ServeController(w, r, c.ControllerEID, c.Auth, kv)
29 | })
30 |
31 | go func() {
32 | if c.Tls {
33 | log.Println("Websockets server running with TLS at port", c.TlsPort)
34 | err := http.ListenAndServeTLS(c.TlsPort, c.FullChain, c.PrivateKey, r)
35 | if err != nil {
36 | log.Fatal("ListenAndServeTLS: ", err)
37 | }
38 | }
39 | }()
40 |
41 | go func() {
42 | if !c.NoTls {
43 | log.Println("Websockets server running at port", c.Port)
44 | err := http.ListenAndServe(c.Port, r)
45 | if err != nil {
46 | log.Fatal("ListenAndServe: ", err)
47 | }
48 | }
49 | }()
50 | }
51 |
--------------------------------------------------------------------------------
/backend/services/utils/file-server/.env:
--------------------------------------------------------------------------------
1 | DIRECTORY_PATH="."
2 | SERVER_PORT=":8004"
--------------------------------------------------------------------------------
/backend/services/utils/file-server/.gitignore:
--------------------------------------------------------------------------------
1 | firmwares/
2 | .env.local
3 | images/
--------------------------------------------------------------------------------
/backend/services/utils/file-server/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22.2@sha256:450e3822c7a135e1463cd83e51c8e2eb03b86a02113c89424e6f0f8344bb4168 as builder
2 | WORKDIR /app
3 | COPY ../ .
4 | RUN CGO_ENABLED=0 GOOS=linux go build -o file-server main.go
5 |
6 | FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed
7 | COPY --from=builder /app/file-server /
8 | ENTRYPOINT ["/file-server"]
--------------------------------------------------------------------------------
/backend/services/utils/file-server/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/leandrofars/oktopus/http_file_server
2 |
3 | go 1.21.3
4 |
5 | require github.com/joho/godotenv v1.5.1 // indirect
6 |
--------------------------------------------------------------------------------
/backend/services/utils/file-server/go.sum:
--------------------------------------------------------------------------------
1 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
2 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
3 |
--------------------------------------------------------------------------------
/backend/services/utils/file-server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "os"
8 |
9 | "github.com/joho/godotenv"
10 | )
11 |
12 | func main() {
13 |
14 | err := godotenv.Load()
15 |
16 | localEnv := ".env.local"
17 |
18 | if _, err := os.Stat(localEnv); err == nil {
19 | _ = godotenv.Overload(localEnv)
20 | log.Println("Loaded variables from '.env.local'")
21 | } else {
22 | log.Println("Loaded variables from '.env'")
23 | }
24 |
25 | if err != nil {
26 | log.Println("Error to load environment variables:", err)
27 | }
28 |
29 | directoryPath := os.Getenv("DIRECTORY_PATH")
30 |
31 | // Check if the directory exists
32 | _, err = os.Stat(directoryPath)
33 | if os.IsNotExist(err) {
34 | fmt.Printf("Directory '%s' not found.\n", directoryPath)
35 | return
36 | }
37 |
38 | // Create a file server handler to serve the directory's contents
39 | fileServer := http.FileServer(http.Dir(directoryPath))
40 |
41 | // Create a new HTTP server and handle requests
42 | http.Handle("/", fileServer)
43 |
44 | port := os.Getenv("SERVER_PORT")
45 | fmt.Printf("Server started at http://localhost%s\n", port)
46 | err = http.ListenAndServe(port, nil)
47 | if err != nil {
48 | fmt.Printf("Error starting server: %s\n", err)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/backend/services/utils/socketio/.env:
--------------------------------------------------------------------------------
1 | CORS_ALLOWED_ORIGINS=""
2 | # addresses must be separated by commas example: "http://localhost:3000,http://myapp.com"
--------------------------------------------------------------------------------
/backend/services/utils/socketio/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .env.local
--------------------------------------------------------------------------------
/backend/services/utils/socketio/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16.20.2-alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY ../package*.json ./
6 |
7 | RUN npm install
8 |
9 | COPY ../ .
10 |
11 | CMD ["node", "server.js"]
--------------------------------------------------------------------------------
/backend/services/utils/socketio/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "socketio",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "@koa/cors": "^5.0.0",
15 | "cors": "^2.8.5",
16 | "dotenv": "^16.3.1",
17 | "express": "^4.18.2",
18 | "koa": "^2.14.2",
19 | "socket.io": "^4.6.2"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/deploy/compose/.env.acs:
--------------------------------------------------------------------------------
1 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
2 | NATS_ENABLE_TLS="true"
3 | CLIENT_CRT=/tmp/nats/config/cert.pem
4 | CLIENT_KEY=/tmp/nats/config/key.pem
5 | SERVER_CA=/tmp/nats/config/rootCA.pem
6 |
--------------------------------------------------------------------------------
/deploy/compose/.env.adapter:
--------------------------------------------------------------------------------
1 | MONGO_URI=mongodb://mongo_usp:27017
2 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
3 | NATS_ENABLE_TLS="true"
4 | CLIENT_CRT=/tmp/nats/config/cert.pem
5 | CLIENT_KEY=/tmp/nats/config/key.pem
6 | SERVER_CA=/tmp/nats/config/rootCA.pem
7 |
--------------------------------------------------------------------------------
/deploy/compose/.env.controller:
--------------------------------------------------------------------------------
1 | MONGO_URI=mongodb://mongo_usp:27017
2 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
3 | NATS_ENABLE_TLS="true"
4 | CLIENT_CRT=/tmp/nats/config/cert.pem
5 | CLIENT_KEY=/tmp/nats/config/key.pem
6 | SERVER_CA=/tmp/nats/config/rootCA.pem
7 |
--------------------------------------------------------------------------------
/deploy/compose/.env.file-server:
--------------------------------------------------------------------------------
1 | DIRECTORY_PATH="/app"
2 | SERVER_PORT=":8004"
--------------------------------------------------------------------------------
/deploy/compose/.env.mqtt:
--------------------------------------------------------------------------------
1 | REDIS_ENABLE=false
2 | REDIS_ADDR=redis_usp:6379
3 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
4 | NATS_ENABLE_TLS="true"
5 | CLIENT_CRT=/tmp/nats/config/cert.pem
6 | CLIENT_KEY=/tmp/nats/config/key.pem
7 | SERVER_CA=/tmp/nats/config/rootCA.pem
8 |
--------------------------------------------------------------------------------
/deploy/compose/.env.mqtt-adapter:
--------------------------------------------------------------------------------
1 | MQTT_URL=tcp://mqtt:1883
2 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
3 | NATS_ENABLE_TLS="true"
4 | CLIENT_CRT=/tmp/nats/config/cert.pem
5 | CLIENT_KEY=/tmp/nats/config/key.pem
6 | SERVER_CA=/tmp/nats/config/rootCA.pem
7 |
--------------------------------------------------------------------------------
/deploy/compose/.env.nats:
--------------------------------------------------------------------------------
1 | NATS_NAME=oktopus
2 | NATS_USER=oktopususer
3 | NATS_PW=oktopuspw
4 |
--------------------------------------------------------------------------------
/deploy/compose/.env.socketio:
--------------------------------------------------------------------------------
1 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
2 | NATS_ENABLE_TLS="true"
3 | CLIENT_CRT=/tmp/nats/config/cert.pem
4 | CLIENT_KEY=/tmp/nats/config/key.pem
5 | SERVER_CA=/tmp/nats/config/rootCA.pem
6 |
--------------------------------------------------------------------------------
/deploy/compose/.env.stomp-adapter:
--------------------------------------------------------------------------------
1 | STOMP_SERVER=stomp:61613
2 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
3 | NATS_ENABLE_TLS="true"
4 | CLIENT_CRT=/tmp/nats/config/cert.pem
5 | CLIENT_KEY=/tmp/nats/config/key.pem
6 | SERVER_CA=/tmp/nats/config/rootCA.pem
7 |
--------------------------------------------------------------------------------
/deploy/compose/.env.ws:
--------------------------------------------------------------------------------
1 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
2 | NATS_ENABLE_TLS="true"
3 | CLIENT_CRT=/tmp/nats/config/cert.pem
4 | CLIENT_KEY=/tmp/nats/config/key.pem
5 | SERVER_CA=/tmp/nats/config/rootCA.pem
6 |
--------------------------------------------------------------------------------
/deploy/compose/.env.ws-adapter:
--------------------------------------------------------------------------------
1 | WS_ADDR=ws
2 | NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
3 | NATS_ENABLE_TLS="true"
4 | CLIENT_CRT=/tmp/nats/config/cert.pem
5 | CLIENT_KEY=/tmp/nats/config/key.pem
6 | SERVER_CA=/tmp/nats/config/rootCA.pem
7 |
--------------------------------------------------------------------------------
/deploy/compose/.gitignore:
--------------------------------------------------------------------------------
1 | portainer_data/*
2 | !portainer_data/.gitkeep
3 | mongo_data/*
4 | !mongo_data/.gitkeep
5 | nats_data/*
6 | !nats_data/.gitkeep
7 | firmwares/*
8 | !firmwares/.gitkeep
9 | images/*
10 | !images/logo.png
--------------------------------------------------------------------------------
/deploy/compose/firmwares/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/deploy/compose/firmwares/.gitkeep
--------------------------------------------------------------------------------
/deploy/compose/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/deploy/compose/images/logo.png
--------------------------------------------------------------------------------
/deploy/compose/mongo_data/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/deploy/compose/mongo_data/.gitkeep
--------------------------------------------------------------------------------
/deploy/compose/nats_config/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEeTCCAuGgAwIBAgIRAJenDys60PD3Cjh9VXeAzsowDQYJKoZIhvcNAQELBQAw
3 | gY0xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTExMC8GA1UECwwoY2hp
4 | ZXNhQEFkcmlhbm9NYWMubG9jYWwgKEFkcmlhbm8gQ2hpZXNhKTE4MDYGA1UEAwwv
5 | bWtjZXJ0IGNoaWVzYUBBZHJpYW5vTWFjLmxvY2FsIChBZHJpYW5vIENoaWVzYSkw
6 | HhcNMjQwNzA2MTQzODQ2WhcNMjYxMDA2MTQzODQ2WjBcMScwJQYDVQQKEx5ta2Nl
7 | cnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxMTAvBgNVBAsMKGNoaWVzYUBBZHJp
8 | YW5vTWFjLmxvY2FsIChBZHJpYW5vIENoaWVzYSkwggEiMA0GCSqGSIb3DQEBAQUA
9 | A4IBDwAwggEKAoIBAQCdQCgwn7M5jtkNzYv/yUelUV84ehsYjPTQspCXCjDdUltO
10 | E6jafRM/2SopUWwzPTorIZkerIpqq/avi77e6wSdCKJZBA5DsrMSOmFjjMqG4H2b
11 | euzr5b+ZKzKnw4nF/CuB2cmWkz/qAiluqVdRl+Gef/Ouyuer/NuhJhK6Mgy1VuAd
12 | ynwizGsAgQ/oAGDuHWmcMhGw/2NVTnXqHei5ntr+F4r08YOgXnRsGId+16nJU5xd
13 | Xs80sw2dFl+1zQiFJxWjVQk65CyZfLDcwC7e3PAMsT+8obzuBKCn6fW3JiIZ68tc
14 | o8GZClHSkeS7IMp+/wtjMXf1iipE0bRWzzdl1xQnAgMBAAGjgYMwgYAwDgYDVR0P
15 | AQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFH2KEsMw
16 | S7/Qu1eE1DxRhZTI0Z7RMDgGA1UdEQQxMC+CCm1zZ19icm9rZXKCCWxvY2FsaG9z
17 | dIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAYEAVr06
18 | Kg/FaWxUbD6Q+e5OLzckeeFZOKcb/2mmEH+e99HebnCuydiiGebZVWcLsoOw+gmb
19 | WG/4rFyp9D/ZYFiQxWNRm/nCcxiHIV+JHDxhBn3vKENxYNNbr+WTMBTqmaYqPARK
20 | SelnX+XDRTZUnoRBo03wZFYwOYwHUFWrlMFz+o6jOo2KZa4J+pcEDbttkJEoVhiV
21 | C8EKIxSsG9TuW4lh5UySfB62ZfQBNd8SyhD4LF5JHFbBV9ysnylg/8gVvavjHWtM
22 | FleOk/lNi9oj2n8uQEGbBT6FpTaKKJDiF85E3J4fuv8iNiFy2nWhCpJFF9K5/hu6
23 | s41Y2ZRb7luBoUj+CQWlTpGjCJPzzWmSkZUjf5kRP6b01bAXoLBzBBn50KWf8DWw
24 | Rx7VaHtzbBRxfzESQxslGRgZvz2KxjY/x2jH6xdW+bzt93aHkgeGkDrjpY9q/uQF
25 | SyEJYHIDt+FZupcc3KjvTY2p+icbckOrwQAtVWXzEjCsnDjf33+O7VGdACDm
26 | -----END CERTIFICATE-----
27 |
--------------------------------------------------------------------------------
/deploy/compose/nats_config/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCdQCgwn7M5jtkN
3 | zYv/yUelUV84ehsYjPTQspCXCjDdUltOE6jafRM/2SopUWwzPTorIZkerIpqq/av
4 | i77e6wSdCKJZBA5DsrMSOmFjjMqG4H2beuzr5b+ZKzKnw4nF/CuB2cmWkz/qAilu
5 | qVdRl+Gef/Ouyuer/NuhJhK6Mgy1VuAdynwizGsAgQ/oAGDuHWmcMhGw/2NVTnXq
6 | Hei5ntr+F4r08YOgXnRsGId+16nJU5xdXs80sw2dFl+1zQiFJxWjVQk65CyZfLDc
7 | wC7e3PAMsT+8obzuBKCn6fW3JiIZ68tco8GZClHSkeS7IMp+/wtjMXf1iipE0bRW
8 | zzdl1xQnAgMBAAECggEAMRDQuYNLJ/2DioQFV/WVDmdaf8PR6pIo3WmqJga/An/t
9 | D2qg+DOoqvZ26leGnGJRYR3lqiWKNwibO2EuWF4anWkRRxc14DfFGj3vH2HR2832
10 | Q2pSvLR+WSuabbBcr9MkPCsZdItTmQ+9n9Lk9QegFZW1Emgra4XFff3kQAbX4kjR
11 | ATyEsvZsgnkK5jHi5bzVHEKY730MKyhYzmHmUgxH1ibD5x+tnFtRiBGK+qY24erW
12 | rw+wu0YKu1ESp62orYDnaA8MH7aWlFZLniPB4liA0h0bElrgHA8Zndx+cEtXqF4m
13 | 5+igR3MjXbg2at80ah++z6EGUTtEPccRSpcA7qFqMQKBgQDFVp1msdn45Xwquw+8
14 | CSZ45KAurvQEPNxtK/GMHtR5LjRZlKQEpNy3AVjz56azv72kqUfhNsP+ZNokMQTH
15 | NTMvdkLzcwKe3qCnsfBRlSkRBVCwan266oCJW8SSX8pdaoME9lzc2BfMVAxJ1n+Y
16 | 289ZHXyyPYdNd+L4FxjreJDkKQKBgQDL/uD+kCSKZ/dYVZqwF06DIzYiBv1Xjne3
17 | 3H90J0h/2iU9/yX9UsCS7D5RWbVROUyMh64fpH0f2lVFT8Xo3SjBWISrMovOxEzl
18 | C2wZwM2VQmrfhiUg9h46drJSl/JL9/V/QOnpuQe35bVWjROtFStaZagUK/3vx1hh
19 | xCV4iVS/zwKBgQC2Ea3zvA/x9jlTa3ee84pNbBLmP4DgEA8Hos2fjCpZC+o85ElY
20 | B4ukRVf+4TILEdM1AwJQpii6o+4oChnwegMZvTEUUH6QebMcRa4Gd2qGS7MgsYAD
21 | XqztDoAU1NBu1ADCKVOQZse+O6WC0qazL8rk27Ha+a3GKeB9KUJSrtBv0QKBgCi5
22 | IftPlSvYI2WL+Uxr6q19KwJR+OMwuq+Goh7y9KMpTkP5GoFesrjh1nLxAKRNVv26
23 | 3ETO1ne0Y09p5G1fMRKf9CQk/Anz4BHdXOArQB8q2iDzK5hP6arsJR8d3C3UOzsD
24 | H28cE/FfNvsnQKVN05DBOHOGcLQcTIV/3acZa0S7AoGBAJqSujzhVkewH7wJWOOh
25 | 92F66vMnCsE4Qn10h84GQQWORgjr8z8d8UbW7VrobVfZP74IBqo3ZUBYm85+lUFu
26 | FWHjTkXoZzwP8IvMW0gE2iC3W4QSr2Z96a6RlRJdxC70LB+Pm6FFwLTloKBGK8Bf
27 | Kp53gUDjsvJcQGv6nOty0uHQ
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/deploy/compose/nats_config/nats.cfg:
--------------------------------------------------------------------------------
1 | server_name: $NATS_NAME
2 | port: 4222
3 | http_port: 8222
4 | authorization: {
5 | users: [
6 | {user: $NATS_USER, password: $NATS_PW}
7 | ]
8 | }
9 | tls: {
10 | cert_file: "/tmp/nats/config/cert.pem"
11 | key_file: "/tmp/nats/config/key.pem"
12 | ca_file: "/tmp/nats/config/rootCA.pem"
13 | }
14 | // enables jetstream, an empty block will enable and use defaults
15 | jetstream {
16 | // jetstream data will be in /data/nats-server/jetstream
17 | store_dir: "/tmp/nats/jetstream"
18 | }
19 |
--------------------------------------------------------------------------------
/deploy/compose/nats_config/rootCA.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIE6zCCA1OgAwIBAgIQcw3hWvi1nfQFSkUzrcdrhTANBgkqhkiG9w0BAQsFADCB
3 | jTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTEwLwYDVQQLDChjaGll
4 | c2FAQWRyaWFub01hYy5sb2NhbCAoQWRyaWFubyBDaGllc2EpMTgwNgYDVQQDDC9t
5 | a2NlcnQgY2hpZXNhQEFkcmlhbm9NYWMubG9jYWwgKEFkcmlhbm8gQ2hpZXNhKTAe
6 | Fw0yMjA4MTIxNTM1NDdaFw0zMjA4MTIxNTM1NDdaMIGNMR4wHAYDVQQKExVta2Nl
7 | cnQgZGV2ZWxvcG1lbnQgQ0ExMTAvBgNVBAsMKGNoaWVzYUBBZHJpYW5vTWFjLmxv
8 | Y2FsIChBZHJpYW5vIENoaWVzYSkxODA2BgNVBAMML21rY2VydCBjaGllc2FAQWRy
9 | aWFub01hYy5sb2NhbCAoQWRyaWFubyBDaGllc2EpMIIBojANBgkqhkiG9w0BAQEF
10 | AAOCAY8AMIIBigKCAYEA4HBEZqDbKPB0StZX/ILPU1q2Tm/U4j32/HvaWOGt528q
11 | 15y5eqa4OqpYQ7waVhkSR+M6258yAk9BCCOGrI+oXcllO7LKv4bg/y+HVhfEg4E0
12 | fnZS583+VWrIcIKOENQQX1cbFnIqLldBL/ph6YNMDT8wOoLqtQ2vp1EiXFA3ghQs
13 | fK3iqDL31SQ7cZhdw7hnNACgvIM6MipwFLYbMsm27eMa9fCL4ZB3xokhbYVBmEcT
14 | RTdhGvcRBs+QF8fcP/Y6uC9y7q2ddeIYEAf5xCVc15QepPqVskuAM3rktzVpBKEl
15 | lzJGtW3EWu3kojrrW8dYZkZFJkHy9ymZVNUPjpAGiIIylp1Rik/o9VttXi+BQmrV
16 | l1tpkrU1JfhLcktjBtRxtvRJTARpSkMM8gVc1rA2KgNsULzF4Zaas12wUh7r/VzW
17 | 4lwKiV3vgAM0wE5Fo4NLmv53/j3w7yX9GtSq0Ck/cKu2k0cOe+PTlG9aQ5MdhLCx
18 | +BwMij/ohBfh0qtgPVPdAgMBAAGjRTBDMA4GA1UdDwEB/wQEAwICBDASBgNVHRMB
19 | Af8ECDAGAQH/AgEAMB0GA1UdDgQWBBR9ihLDMEu/0LtXhNQ8UYWUyNGe0TANBgkq
20 | hkiG9w0BAQsFAAOCAYEAyGNw197jniIGfCpcArC2lHbzNIQCq97BXsOj4h+CaUB+
21 | ZfTpTOdtS5DK2fSQ4jwH6nzm7VQvrnHcbP2pCgVDWW9da+Acnh1U6dYXI1VPDDVA
22 | KZGDiK63M6PneX3IDl2N2fubRDP1ALkZQ6zwjtJLC2nT6IVjAJdRNt8egbCUAyMb
23 | nOb4F+Zrr0L2SILM0jHq9diJVpX4Lxqggldmd+5HdVGHoR8BGzw4MReuoRWOZtf/
24 | H+opFtJ2f8GNTWsWtmN7FsjrGOOBgREB/Tz7WCWgGEeXQveJEfkOIddVVgb/n9Ly
25 | mEOTbtJj36o4JJzv0v42SdT1WUkdvc6AmLVmYFn0uaLohNLVelJmU+aGavuVFpFb
26 | 08uVsSl8JMBWwVO23CpuvZFbI/1BFVix85UZgDj8BnpAWlsJxPYV1HqxS2oJJJbn
27 | zRf8/0xzFPrLirXzebIr4jiCRVBRamacQTxlvPUz6c6FLTQLaqQdUV/IZaXsXX38
28 | byH1ABmJPGjXLm1r2RWD
29 | -----END CERTIFICATE-----
30 |
--------------------------------------------------------------------------------
/deploy/compose/nats_data/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/deploy/compose/nats_data/.gitkeep
--------------------------------------------------------------------------------
/deploy/compose/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | listen [::]:80;
4 | server_name _;
5 | root /usr/share/nginx/html;
6 |
7 | include /etc/nginx/default.d/*.conf;
8 |
9 | error_page 404 /404.html;
10 | location = /404.html {
11 | }
12 |
13 | location / {
14 | proxy_pass http://host.docker.internal:3000;
15 | proxy_read_timeout 60;
16 | proxy_connect_timeout 60;
17 | proxy_redirect off;
18 |
19 | proxy_http_version 1.1;
20 | proxy_set_header Upgrade $http_upgrade;
21 | proxy_set_header Connection 'upgrade';
22 | proxy_set_header Host $host;
23 | proxy_cache_bypass $http_upgrade;
24 | }
25 | location /api {
26 | proxy_pass http://host.docker.internal:8000;
27 | proxy_read_timeout 60;
28 | proxy_connect_timeout 60;
29 | proxy_redirect off;
30 | }
31 | location /images {
32 | proxy_pass http://host.docker.internal:8004;
33 | proxy_read_timeout 60;
34 | proxy_connect_timeout 60;
35 | proxy_redirect off;
36 | }
37 | location /companylink {
38 | return 301 https://oktopus.app.br/controller;
39 | }
40 |
41 | error_page 500 502 503 504 /50x.html;
42 | location = /50x.html {
43 | }
44 | }
--------------------------------------------------------------------------------
/deploy/compose/portainer_data/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/deploy/compose/portainer_data/.gitkeep
--------------------------------------------------------------------------------
/deploy/compose/run.sh:
--------------------------------------------------------------------------------
1 | COMPOSE_PROFILES=nats,controller,cwmp,mqtt,stomp,ws,adapter,frontend,portainer docker compose up -d
2 |
--------------------------------------------------------------------------------
/deploy/compose/stop.sh:
--------------------------------------------------------------------------------
1 | COMPOSE_PROFILES=nats,controller,cwmp,mqtt,stomp,ws,adapter,frontend,portainer docker compose down
2 |
--------------------------------------------------------------------------------
/deploy/kubernetes/adapter.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: adapter
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: adapter
10 | strategy:
11 | type: Recreate # Specify the Recreate strategy
12 | template:
13 | metadata:
14 | labels:
15 | app: adapter
16 | spec:
17 | containers:
18 | - name: adapter
19 | image: oktopusp/adapter:latest
20 | resources:
21 | requests:
22 | memory: 64Mi
23 | cpu: 0.1
24 | limits:
25 | memory: 128Mi
26 | cpu: 0.2
27 | imagePullPolicy: IfNotPresent
28 | env:
29 | - name: NATS_URL
30 | value: "nats://nats:4222"
31 | - name: NATS_NAME
32 | value: "adapter"
33 | - name: NATS_VERIFY_CERTIFICATES
34 | value: "false"
35 | - name: MONGO_URI
36 | value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/?replicaSet=mongodb&ssl=false"
37 | - name: CONTROLLER_ID
38 | value: "oktopusController"
39 |
40 |
--------------------------------------------------------------------------------
/deploy/kubernetes/controller.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: controller
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: controller
10 | strategy:
11 | type: Recreate
12 | template:
13 | metadata:
14 | labels:
15 | app: controller
16 | spec:
17 | containers:
18 | - name: controller
19 | image: oktopusp/controller:latest
20 | resources:
21 | # requests:
22 | # memory: 64Mi
23 | # cpu: 0.5
24 | # limits:
25 | # memory: 256Mi
26 | # cpu: 1
27 | imagePullPolicy: IfNotPresent
28 | env:
29 | - name: NATS_URL
30 | value: "nats://nats:4222"
31 | - name: NATS_NAME
32 | value: "adapter"
33 | - name: NATS_VERIFY_CERTIFICATES
34 | value: "false"
35 | - name: MONGO_URI
36 | value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/?replicaSet=mongodb&ssl=false"
37 | - name: REST_API_PORT
38 | value: "8000"
39 | ---
40 | apiVersion: v1
41 | kind: Service
42 | metadata:
43 | name: controller-svc
44 | spec:
45 | selector:
46 | app: controller
47 | ports:
48 | - protocol: TCP
49 | port: 8000
50 | targetPort: 8000
51 | type: ClusterIP
52 |
53 |
--------------------------------------------------------------------------------
/deploy/kubernetes/frontend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: frontend
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: frontend
10 | strategy:
11 | type: Recreate # Specify the Recreate strategy
12 | template:
13 | metadata:
14 | labels:
15 | app: frontend
16 | spec:
17 | containers:
18 | - name: frontend
19 | image: oktopusp/frontend:latest
20 | resources:
21 | requests:
22 | memory: 64Mi
23 | cpu: 100m
24 | limits:
25 | memory: 256Mi
26 | cpu: 200m
27 | ports:
28 | - containerPort: 3000
29 | imagePullPolicy: IfNotPresent
30 | env:
31 | - name: NEXT_PUBLIC_REST_ENDPOINT
32 | value: "/api"
33 | ---
34 | apiVersion: v1
35 | kind: Service
36 | metadata:
37 | name: frontend-svc
38 | spec:
39 | selector:
40 | app: frontend
41 | ports:
42 | - protocol: TCP
43 | port: 3000
44 | targetPort: 3000
45 | #externalTrafficPolicy: Local
46 | type: ClusterIP
47 |
--------------------------------------------------------------------------------
/deploy/kubernetes/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: web-ingress
5 | namespace: default
6 | annotations:
7 | spec:
8 | ingressClassName: "haproxy"
9 | rules:
10 | - host: oktopus.rdss.cloud
11 | http:
12 | paths:
13 | - path: /
14 | pathType: Prefix
15 | backend:
16 | service:
17 | name: frontend-svc
18 | port:
19 | number: 3000
20 | - path: /api
21 | pathType: Prefix
22 | backend:
23 | service:
24 | name: controller-svc
25 | port:
26 | number: 8000
27 | - path: /socket.io
28 | pathType: Prefix
29 | backend:
30 | service:
31 | name: socketio-svc
32 | port:
33 | number: 5000
34 |
35 |
--------------------------------------------------------------------------------
/deploy/kubernetes/mongodb.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: mongodbcommunity.mongodb.com/v1
3 | kind: MongoDBCommunity
4 | metadata:
5 | name: mongodb
6 | spec:
7 | members: 3
8 | type: ReplicaSet
9 | version: "6.0.5"
10 | security:
11 | authentication:
12 | modes: ["SCRAM"]
13 | users:
14 | - name: oktopusp
15 | db: admin
16 | passwordSecretRef: # a reference to the secret that will be used to generate the user's password
17 | name: mongo-secret
18 | roles:
19 | - name: clusterAdmin
20 | db: admin
21 | - name: userAdminAnyDatabase
22 | db: admin
23 | - name: readWriteAnyDatabase
24 | db: admin
25 | scramCredentialsSecretName: my-scram
26 | additionalMongodConfig:
27 | storage.wiredTiger.engineConfig.journalCompressor: zlib
28 |
29 | # the user credentials will be generated from this secret
30 | # once the credentials are generated, this secret is no longer required
31 | ---
32 | apiVersion: v1
33 | kind: Secret
34 | metadata:
35 | name: mongo-secret
36 | type: Opaque
37 | stringData:
38 | password: oktopusp
39 |
--------------------------------------------------------------------------------
/deploy/kubernetes/mqtt-adapter.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: mqtt-adapter
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: mqtt-adapter
10 | template:
11 | metadata:
12 | labels:
13 | app: mqtt-adapter
14 | spec:
15 | containers:
16 | - name: mqtt-adapter
17 | image: oktopusp/mqtt-adapter:latest
18 | resources:
19 | requests:
20 | memory: 64Mi
21 | cpu: 0.1
22 | limits:
23 | memory: 128Mi
24 | cpu: 0.2
25 | imagePullPolicy: IfNotPresent
26 | env:
27 | - name: NATS_URL
28 | value: "nats:4222"
29 | - name: NATS_NAME
30 | value: "mqtt-adapter"
31 | - name: NATS_VERIFY_CERTIFICATES
32 | value: "false"
33 | - name: MQTT_URL
34 | value: "tcp://mqtt-svc:1883"
35 | - name: MQTT_CLIENT_ID
36 | value: "mqtt-adapter"
37 | - name: MQTT_USERNAME
38 | value: ""
39 | - name: MQTT_PASSWORD
40 | value: ""
41 | - name: MQTT_QOS
42 | value: "1"
43 | - name: MQTT_SERVICE_HOST
44 | value: "mqtt"
45 |
--------------------------------------------------------------------------------
/deploy/kubernetes/mqtt.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: mqtt-svc
5 | spec:
6 | selector:
7 | app: mqtt
8 | ports:
9 | - protocol: TCP
10 | port: 1883
11 | targetPort: 1883
12 | externalTrafficPolicy: Local
13 | type: LoadBalancer
14 | ---
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name: mqtt
19 | spec:
20 | replicas: 1
21 | selector:
22 | matchLabels:
23 | app: mqtt
24 | template:
25 | metadata:
26 | labels:
27 | app: mqtt
28 | spec:
29 | containers:
30 | - name: mqtt
31 | image: oktopusp/mqtt:latest
32 | ports:
33 | - containerPort: 1883
34 | resources:
35 | requests:
36 | memory: 64Mi
37 | cpu: 100m
38 | limits:
39 | memory: 256Mi
40 | cpu: 200m
41 | imagePullPolicy: IfNotPresent
42 | env:
43 | - name: MQTT_PORT
44 | value: ":1883"
45 | - name: MQTT_TLS
46 | value: "false"
47 | - name: LOG_LEVEL
48 | value: "0" # 0 - DEBUG
49 | - name: REDIS_ENABLE
50 | value: "false"
51 |
52 |
--------------------------------------------------------------------------------
/deploy/kubernetes/socketio.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: socketio
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: socketio
10 | template:
11 | metadata:
12 | labels:
13 | app: socketio
14 | spec:
15 | containers:
16 | - name: socketio
17 | image: oktopusp/socketio:latest
18 | imagePullPolicy: IfNotPresent
19 | env:
20 | - name: NATS_URL
21 | value: "nats:4222"
22 | - name: CORS_ALLOWED_ORIGINS
23 | value: ""
24 | ---
25 | apiVersion: v1
26 | kind: Service
27 | metadata:
28 | name: socketio-svc
29 | spec:
30 | selector:
31 | app: socketio
32 | ports:
33 | - protocol: TCP
34 | port: 5000
35 | targetPort: 5000
36 |
--------------------------------------------------------------------------------
/deploy/kubernetes/ws-adapter.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: ws-adapter
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: ws-adapter
10 | template:
11 | metadata:
12 | labels:
13 | app: ws-adapter
14 | spec:
15 | containers:
16 | - name: ws-adapter
17 | image: oktopusp/ws-adapter:latest
18 | resources:
19 | #requests:
20 | # memory: 64Mi
21 | # cpu: 0.1
22 | #limits:
23 | # memory: 256Mi
24 | # cpu: 0.2
25 | imagePullPolicy: IfNotPresent
26 | env:
27 | - name: NATS_URL
28 | value: "nats://nats:4222"
29 | - name: NATS_NAME
30 | value: "ws-adapter"
31 | - name: NATS_VERIFY_CERTIFICATES
32 | value: "0" # 0 - DEBUG
33 | - name: WS_TOKEN
34 | value: ""
35 | - name: WS_AUTH_ENABLE
36 | value: "false"
37 | - name: WS_ADDR
38 | value: "ws-svc"
39 | - name: WS_PORT
40 | value: ":8080"
41 | - name: WS_ROUTE
42 | value: "/ws/controller"
43 | - name: WS_TLS_ENABLE
44 | value: "false"
45 | - name: WS_SKIP_TLS_VERIFY
46 | value: "false"
47 |
--------------------------------------------------------------------------------
/deploy/kubernetes/ws.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: ws-svc
5 | spec:
6 | selector:
7 | app: ws
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | nodePort: 30005
13 | type: NodePort
14 | ---
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name: ws
19 | spec:
20 | replicas: 1
21 | selector:
22 | matchLabels:
23 | app: ws
24 | template:
25 | metadata:
26 | labels:
27 | app: ws
28 | spec:
29 | containers:
30 | - name: ws
31 | image: oktopusp/ws:latest
32 | ports:
33 | - containerPort: 8080
34 | resources:
35 | requests:
36 | memory: 64Mi
37 | cpu: 100m
38 | limits:
39 | memory: 256Mi
40 | cpu: 200m
41 | imagePullPolicy: IfNotPresent
42 | env:
43 | - name: SERVER_PORT
44 | value: ":8080"
45 | - name: SERVER_AUTH_TOKEN
46 | value: ""
47 | - name: SERVER_AUTH_ENABLE
48 | value: "false"
49 | - name: CONTROLLER_EID
50 | value: "oktopusController"
51 | - name: SERVER_TLS_ENABLE
52 | value: "false"
53 |
--------------------------------------------------------------------------------
/frontend/.env:
--------------------------------------------------------------------------------
1 | # ----------------------------- Local Environment ---------------------------- #
2 |
3 | NEXT_PUBLIC_WS_ENDPOINT="http://localhost:5000/"
4 | NEXT_PUBLIC_REST_ENDPOINT="http://localhost:8000"
5 | # ---------------------------------------------------------------------------- #
6 |
7 | # -------------------------- Production Environment -------------------------- #
8 |
9 | #NEXT_PUBLIC_REST_ENDPOINT="https://demo.oktopus.app.br/"
10 | #NEXT_PUBLIC_WS_ENDPOINT="https://demo.oktopus.app.br/"
11 | # ---------------------------------------------------------------------------- #
12 |
13 | # ---------------------------- Mocked Environment ---------------------------- #
14 |
15 | #NEXT_PUBLIC_REST_ENDPOINT="https://c970e88c-2d7b-4363-a58c-ff59d66438a8.mock.pstmn.io"
16 |
17 | # ---------------------------------------------------------------------------- #
18 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "@next/next/no-img-element": "off",
5 | "jsx-a11y/alt-text": "off",
6 | "react/display-name": "off",
7 | "react/no-unescaped-entities": 0,
8 | "react/jsx-max-props-per-line": [
9 | 1,
10 | {
11 | "maximum": 1
12 | }
13 | ]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=lf
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | out
13 | .next
14 |
15 | # misc
16 | .DS_Store
17 | .eslintcache
18 | .idea
19 | /.env.local
20 | /.env.development.local
21 | /.env.test.local
22 | /.env.production.local
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | build/image.sh
29 |
--------------------------------------------------------------------------------
/frontend/build/.dockerignore:
--------------------------------------------------------------------------------
1 | ../.next/
2 | ../node_modules/
3 | ../.env
4 | ../.env.local
--------------------------------------------------------------------------------
/frontend/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18.18.0-alpine as builder
2 |
3 | WORKDIR /app
4 |
5 | COPY ../ ./
6 | RUN rm .env && rm .env.local || true
7 |
8 | RUN npm install
9 |
10 | RUN npm run build
11 |
12 | FROM node:18.18.0-alpine as runner
13 |
14 | WORKDIR /app
15 |
16 | COPY --from=builder /app/.next ./.next
17 | COPY --from=builder /app/package.json ./package.json
18 | COPY --from=builder /app/package-lock.json ./package-lock.json
19 | COPY --from=builder /app/public ./public
20 |
21 | RUN npm install
22 |
23 | CMD [ "npm", "run", "start" ]
24 |
--------------------------------------------------------------------------------
/frontend/build/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: help build push start stop release remove delete run logs bash
2 |
3 | DOCKER_USER ?= oktopusp
4 | DOCKER_APP ?= frontend
5 | DOCKER_TAG ?= $(shell git log --format="%h" -n 1)
6 | CONTAINER_SHELL ?= /bin/sh
7 |
8 | .DEFAULT_GOAL := help
9 |
10 | help:
11 | @echo "Makefile arguments:"
12 | @echo ""
13 | @echo "DOCKER_USER - docker user to build image"
14 | @echo "DOCKER_APP - docker image name"
15 | @echo "DOCKER_TAG - docker image tag"
16 | @echo "CONTAINER_SHELL - container shell e.g:'/bin/bash'"
17 | @echo ""
18 | @echo "Makefile commands:"
19 | @echo ""
20 | @echo "build - docker image build"
21 | @echo "release - tag image as latest and push to registry"
22 |
23 | build:
24 | @docker build -t ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} -f Dockerfile ../
25 |
26 | release: build
27 | @docker push ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG}
28 | @docker tag ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} ${DOCKER_USER}/${DOCKER_APP}:latest
29 | @docker push ${DOCKER_USER}/${DOCKER_APP}:latest
--------------------------------------------------------------------------------
/frontend/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "."
4 | }
5 | }
--------------------------------------------------------------------------------
/frontend/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: false,
3 | eslint: {
4 | // Warning: This allows production builds to successfully complete even if
5 | // your project has ESLint errors.
6 | ignoreDuringBuilds: true,
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "material-kit-react",
3 | "version": "3.0.0",
4 | "author": "Devias",
5 | "licence": "MIT",
6 | "homepage": "https://devias.io",
7 | "private": false,
8 | "scripts": {
9 | "dev": "next",
10 | "build": "next build",
11 | "start": "next start",
12 | "export": "next export",
13 | "lint": "next lint",
14 | "lint-fix": "next lint --fix"
15 | },
16 | "dependencies": {
17 | "@emotion/cache": "11.10.5",
18 | "@emotion/react": "11.10.6",
19 | "@emotion/server": "11.10.0",
20 | "@emotion/styled": "11.10.6",
21 | "@heroicons/react": "2.0.16",
22 | "@mui/lab": "5.0.0-alpha.120",
23 | "@mui/material": "5.11.10",
24 | "@mui/system": "5.11.9",
25 | "@mui/x-date-pickers": "5.0.19",
26 | "@react-google-maps/api": "^2.19.3",
27 | "apexcharts": "^3.37.0",
28 | "date-fns": "2.29.3",
29 | "formik": "2.2.9",
30 | "next": "^14.2.26",
31 | "nprogress": "0.2.0",
32 | "prop-types": "15.8.1",
33 | "react": "^18.3.1",
34 | "react-apexcharts": "^1.4.0",
35 | "react-dom": "^18.3.1",
36 | "simple-peer": "^9.11.1",
37 | "simplebar-react": "^3.2.1",
38 | "socket.io-client": "^4.6.2",
39 | "styled-components": "^6.0.0-rc.3",
40 | "yup": "1.0.0"
41 | },
42 | "devDependencies": {
43 | "@types/node": "18.13.0",
44 | "@types/nprogress": "0.2.0",
45 | "@types/numeral": "2.0.2",
46 | "@types/react": "18.0.28",
47 | "@types/react-dom": "18.0.11",
48 | "eslint": "8.34.0",
49 | "eslint-config-next": "^14.2.4"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/frontend/public/assets/avatars/default-avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/assets/avatars/default-avatar.png
--------------------------------------------------------------------------------
/frontend/public/assets/errors/error-401.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/assets/errors/error-401.png
--------------------------------------------------------------------------------
/frontend/public/assets/errors/error-404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/assets/errors/error-404.png
--------------------------------------------------------------------------------
/frontend/public/assets/errors/error-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/assets/errors/error-500.png
--------------------------------------------------------------------------------
/frontend/public/assets/general/github-mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/assets/general/github-mark.png
--------------------------------------------------------------------------------
/frontend/public/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/assets/logo.png
--------------------------------------------------------------------------------
/frontend/public/assets/mtp/boot-stomp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/public/assets/mtp/websocket.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/favicon-16x16.png
--------------------------------------------------------------------------------
/frontend/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/favicon-32x32.png
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OktopUSP/oktopus/616df2c2858d33d46e973853871d649f87bfd66b/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Oktopus TR-369",
3 | "name": "Oktopus",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/components/chart.js:
--------------------------------------------------------------------------------
1 | import dynamic from 'next/dynamic';
2 | import { styled } from '@mui/material/styles';
3 |
4 | const ApexChart = dynamic(() => import('react-apexcharts'), {
5 | ssr: false,
6 | loading: () => null
7 | });
8 |
9 | export const Chart = styled(ApexChart)``;
10 |
--------------------------------------------------------------------------------
/frontend/src/components/logo.js:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@mui/material/styles';
2 |
3 | export const Logo = () => {
4 | const theme = useTheme();
5 | const fillColor = '#FFFFFF';
6 |
7 | return (
8 |
15 |
20 |
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/frontend/src/components/scrollbar.js:
--------------------------------------------------------------------------------
1 | import SimpleBar from 'simplebar-react';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const Scrollbar = styled(SimpleBar)``;
5 |
--------------------------------------------------------------------------------
/frontend/src/components/severity-pill.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { styled } from '@mui/material/styles';
3 |
4 | const SeverityPillRoot = styled('span')(({ theme, ownerState }) => {
5 | const backgroundColor = theme.palette[ownerState.color].alpha12;
6 | const color = theme.palette.mode === 'dark'
7 | ? theme.palette[ownerState.color].main
8 | : theme.palette[ownerState.color].dark;
9 |
10 | return {
11 | alignItems: 'center',
12 | backgroundColor,
13 | borderRadius: 12,
14 | color,
15 | cursor: 'default',
16 | display: 'inline-flex',
17 | flexGrow: 0,
18 | flexShrink: 0,
19 | fontFamily: theme.typography.fontFamily,
20 | fontSize: theme.typography.pxToRem(12),
21 | lineHeight: 2,
22 | fontWeight: 600,
23 | justifyContent: 'center',
24 | letterSpacing: 0.5,
25 | minWidth: 20,
26 | paddingLeft: theme.spacing(1),
27 | paddingRight: theme.spacing(1),
28 | textTransform: 'uppercase',
29 | whiteSpace: 'nowrap'
30 | };
31 | });
32 |
33 | export const SeverityPill = (props) => {
34 | const { color = 'primary', children, ...other } = props;
35 |
36 | const ownerState = { color };
37 |
38 | return (
39 |
43 | {children}
44 |
45 | );
46 | };
47 |
48 | SeverityPill.propTypes = {
49 | children: PropTypes.node,
50 | color: PropTypes.oneOf([
51 | 'primary',
52 | 'secondary',
53 | 'error',
54 | 'info',
55 | 'warning',
56 | 'success'
57 | ])
58 | };
59 |
--------------------------------------------------------------------------------
/frontend/src/components/visually-hidden-input.js:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 |
3 | export const VisuallyHiddenInput = styled('input')({
4 | clip: 'rect(0 0 0 0)',
5 | clipPath: 'inset(50%)',
6 | height: 1,
7 | overflow: 'hidden',
8 | position: 'absolute',
9 | bottom: 0,
10 | left: 0,
11 | whiteSpace: 'nowrap',
12 | width: 1,
13 | });
--------------------------------------------------------------------------------
/frontend/src/contexts/error-context.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useState } from 'react';
2 |
3 |
4 | export const AlertContext = createContext({ undefined });
5 |
6 | export const AlertProvider = (props) => {
7 | const { children } = props;
8 | /*
9 | {
10 | severity: '', // options => error, warning, info, success
11 | message: '',
12 | title: '',
13 | }
14 | */
15 | // const [alert, setAlert] = useState(null);
16 | const [alert, setAlert] = useState();
17 |
18 | return (
19 |
25 | {children}
26 |
27 | );
28 | };
29 |
30 | export const AlertConsumer = AlertContext.Consumer;
31 |
32 | export const useAlertContext = () => useContext(AlertContext);
--------------------------------------------------------------------------------
/frontend/src/guards/auth-guard.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 | import { useRouter } from 'next/router';
3 | import PropTypes from 'prop-types';
4 | import { useAuthContext } from 'src/contexts/auth-context';
5 |
6 | export const AuthGuard = (props) => {
7 | const { children } = props;
8 | const router = useRouter();
9 | const { isAuthenticated } = useAuthContext();
10 | const ignore = useRef(false);
11 | const [checked, setChecked] = useState(false);
12 |
13 | // Only do authentication check on component mount.
14 | // This flow allows you to manually redirect the user after sign-out, otherwise this will be
15 | // triggered and will automatically redirect to sign-in page.
16 |
17 | useEffect(
18 | () => {
19 | if (!router.isReady) {
20 | return;
21 | }
22 |
23 | // Prevent from calling twice in development mode with React.StrictMode enabled
24 | if (ignore.current) {
25 | return;
26 | }
27 |
28 | ignore.current = true;
29 |
30 | if (!isAuthenticated) {
31 | console.log('Not authenticated, redirecting');
32 | router
33 | .replace({
34 | pathname: '/auth/login',
35 | query: router.asPath !== '/' ? { continueUrl: router.asPath } : undefined
36 | })
37 | .catch(console.error);
38 | } else {
39 | setChecked(true);
40 | }
41 | },
42 | [router.isReady, isAuthenticated, router]
43 | );
44 |
45 | if (!checked) {
46 | return null;
47 | }
48 |
49 | // If got here, it means that the redirect did not occur, and that tells us that the user is
50 | // authenticated / authorized.
51 |
52 | return children;
53 | };
54 |
55 | AuthGuard.propTypes = {
56 | children: PropTypes.node
57 | };
58 |
--------------------------------------------------------------------------------
/frontend/src/hocs/with-auth-guard.js:
--------------------------------------------------------------------------------
1 | import { AuthGuard } from 'src/guards/auth-guard';
2 |
3 | export const withAuthGuard = (Component) => (props) => (
4 |
5 |
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/frontend/src/hooks/use-auth.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { AuthContext } from 'src/contexts/auth-context';
3 |
4 | export const useAuth = () => useContext(AuthContext);
5 |
--------------------------------------------------------------------------------
/frontend/src/hooks/use-mocked-user.js:
--------------------------------------------------------------------------------
1 | export const useMockedUser = () => {
2 | // To get the user from the authContext, you can use
3 | // `const { user } = useAuth();`
4 | return {
5 | id: '5e86809283e28b96d2d38537',
6 | avatar: '/assets/avatars/avatar-anika-visser.png',
7 | name: 'Anika Visser',
8 | email: 'anika.visser@devias.io'
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/frontend/src/hooks/use-nprogress.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import Router from 'next/router';
3 | import nProgress from 'nprogress';
4 |
5 | export function useNProgress() {
6 | useEffect(() => {
7 | Router.events.on('routeChangeStart', nProgress.start);
8 | Router.events.on('routeChangeError', nProgress.done);
9 | Router.events.on('routeChangeComplete', nProgress.done);
10 |
11 | return () => {
12 | Router.events.off('routeChangeStart', nProgress.start);
13 | Router.events.off('routeChangeError', nProgress.done);
14 | Router.events.off('routeChangeComplete', nProgress.done);
15 | };
16 | }, []);
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/hooks/use-popover.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef, useState } from 'react';
2 |
3 | export function usePopover() {
4 | const anchorRef = useRef(null);
5 | const [open, setOpen] = useState(false);
6 |
7 | const handleOpen = useCallback(() => {
8 | setOpen(true);
9 | }, []);
10 |
11 | const handleClose = useCallback(() => {
12 | setOpen(false);
13 | }, []);
14 |
15 | const handleToggle = useCallback(() => {
16 | setOpen((prevState) => !prevState);
17 | }, []);
18 |
19 | return {
20 | anchorRef,
21 | handleClose,
22 | handleOpen,
23 | handleToggle,
24 | open
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/hooks/use-selection.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from 'react';
2 |
3 | export const useSelection = (items = []) => {
4 | const [selected, setSelected] = useState([]);
5 |
6 | useEffect(() => {
7 | setSelected([]);
8 | }, [items]);
9 |
10 | const handleSelectAll = useCallback(() => {
11 | setSelected([...items]);
12 | }, [items]);
13 |
14 | const handleSelectOne = useCallback((item) => {
15 | setSelected((prevState) => [...prevState, item]);
16 | }, []);
17 |
18 | const handleDeselectAll = useCallback(() => {
19 | setSelected([]);
20 | }, []);
21 |
22 | const handleDeselectOne = useCallback((item) => {
23 | setSelected((prevState) => {
24 | return prevState.filter((_item) => _item !== item);
25 | });
26 | }, []);
27 |
28 | return {
29 | handleDeselectAll,
30 | handleDeselectOne,
31 | handleSelectAll,
32 | handleSelectOne,
33 | selected
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/frontend/src/layouts/dashboard/account-popover.js:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { useRouter } from 'next/navigation';
3 | import PropTypes from 'prop-types';
4 | import { Box, Divider, MenuItem, MenuList, Popover, Typography } from '@mui/material';
5 | import { useAuth } from 'src/hooks/use-auth';
6 |
7 | export const AccountPopover = (props) => {
8 | const { anchorEl, onClose, open } = props;
9 | const router = useRouter();
10 | const auth = useAuth();
11 |
12 |
13 | const handleSignOut = useCallback(
14 | () => {
15 | onClose?.();
16 | auth.signOut();
17 | router.push('/auth/login');
18 | },
19 | [onClose, auth, router]
20 | );
21 |
22 | return (
23 |
33 |
39 |
40 | Account
41 |
42 |
46 | {auth.user.name}
47 |
48 |
49 |
50 | *': {
56 | borderRadius: 1
57 | }
58 | }}
59 | >
60 |
61 | Sign out
62 |
63 |
64 |
65 | );
66 | };
67 |
68 | AccountPopover.propTypes = {
69 | anchorEl: PropTypes.any,
70 | onClose: PropTypes.func,
71 | open: PropTypes.bool.isRequired
72 | };
73 |
--------------------------------------------------------------------------------
/frontend/src/pages/account.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { Box, Container, Stack, Typography, Unstable_Grid2 as Grid } from '@mui/material';
3 | import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
4 | import { AccountProfile } from 'src/sections/account/account-profile';
5 | import { AccountProfileDetails } from 'src/sections/account/account-profile-details';
6 |
7 | const Page = () => (
8 | <>
9 |
10 |
11 | Account | Devias Kit
12 |
13 |
14 |
21 |
22 |
23 |
24 |
25 | Account
26 |
27 |
28 |
29 |
33 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | >
53 | );
54 |
55 | Page.getLayout = (page) => (
56 |
57 | {page}
58 |
59 | );
60 |
61 | export default Page;
62 |
--------------------------------------------------------------------------------
/frontend/src/pages/settings.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { Box, Container, Stack, Typography } from '@mui/material';
3 | import { SettingsNotifications } from 'src/sections/settings/settings-notifications';
4 | import { SettingsPassword } from 'src/sections/settings/settings-password';
5 | import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
6 |
7 | const Page = () => (
8 | <>
9 |
10 |
11 | Settings | Oktopus
12 |
13 |
14 |
21 |
22 |
23 |
24 | Settings
25 |
26 | {/* */}
27 |
28 |
29 |
30 |
31 | >
32 | );
33 |
34 | Page.getLayout = (page) => (
35 |
36 | {page}
37 |
38 | );
39 |
40 | export default Page;
41 |
--------------------------------------------------------------------------------
/frontend/src/sections/account/account-profile.js:
--------------------------------------------------------------------------------
1 | import {
2 | Avatar,
3 | Box,
4 | Button,
5 | Card,
6 | CardActions,
7 | CardContent,
8 | Divider,
9 | Typography
10 | } from '@mui/material';
11 |
12 | const user = {
13 | avatar: '/assets/avatars/avatar-anika-visser.png',
14 | city: 'Los Angeles',
15 | country: 'USA',
16 | jobTitle: 'Senior Developer',
17 | name: 'Anika Visser',
18 | timezone: 'GTM-7'
19 | };
20 |
21 | export const AccountProfile = () => (
22 |
23 |
24 |
31 |
39 |
43 | {user.name}
44 |
45 |
49 | {user.city} {user.country}
50 |
51 |
55 | {user.timezone}
56 |
57 |
58 |
59 |
60 |
61 |
65 | Upload picture
66 |
67 |
68 |
69 | );
70 |
--------------------------------------------------------------------------------
/frontend/src/sections/companies/companies-search.js:
--------------------------------------------------------------------------------
1 | import MagnifyingGlassIcon from '@heroicons/react/24/solid/MagnifyingGlassIcon';
2 | import { Card, InputAdornment, OutlinedInput, SvgIcon } from '@mui/material';
3 |
4 | export const CompaniesSearch = () => (
5 |
6 |
12 |
16 |
17 |
18 |
19 | )}
20 | sx={{ maxWidth: 500 }}
21 | />
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/frontend/src/sections/customer/customers-search.js:
--------------------------------------------------------------------------------
1 | import MagnifyingGlassIcon from '@heroicons/react/24/solid/MagnifyingGlassIcon';
2 | import { Card, InputAdornment, OutlinedInput, SvgIcon } from '@mui/material';
3 |
4 | export const CustomersSearch = () => (
5 |
6 |
12 |
16 |
17 |
18 |
19 | )}
20 | sx={{ maxWidth: 500 }}
21 | />
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/frontend/src/sections/overview/overview-total-profit.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import CurrencyDollarIcon from '@heroicons/react/24/solid/CurrencyDollarIcon';
3 | import { Avatar, Card, CardContent, Stack, SvgIcon, Typography } from '@mui/material';
4 |
5 | export const OverviewTotalProfit = (props) => {
6 | const { value, sx } = props;
7 |
8 | return (
9 |
10 |
11 |
17 |
18 |
22 | Total Profit
23 |
24 |
25 | {value}
26 |
27 |
28 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | OverviewTotalProfit.propTypes = {
46 | value: PropTypes.string,
47 | sx: PropTypes.object
48 | };
49 |
--------------------------------------------------------------------------------
/frontend/src/sections/settings/color-theme.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import IconButton from '@mui/material/IconButton';
3 | import Box from '@mui/material/Box';
4 | import { useTheme, ThemeProvider, createTheme } from '@mui/material/styles';
5 | import Brightness4Icon from '@mui/icons-material/Brightness4';
6 | import Brightness7Icon from '@mui/icons-material/Brightness7';
7 |
8 | const ColorModeContext = React.createContext({ toggleColorMode: () => {} });
9 |
10 | function MyApp() {
11 | const theme = useTheme();
12 | const colorMode = React.useContext(ColorModeContext);
13 | return (
14 |
26 | {theme.palette.mode} mode
27 |
32 | {theme.palette.mode === 'dark' ? : }
33 |
34 |
35 | );
36 | }
37 |
38 | export default function ToggleColorMode() {
39 | const [mode, setMode] = React.useState<'light' | 'dark'>('light');
40 | const colorMode = React.useMemo(
41 | () => ({
42 | toggleColorMode: () => {
43 | setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
44 | },
45 | }),
46 | [setMode],
47 | );
48 |
49 | const theme = React.useMemo(
50 | () =>
51 | createTheme({
52 | palette: {
53 | mode,
54 | },
55 | }),
56 | [mode],
57 | );
58 |
59 | return (
60 |
61 |
62 |
63 |
64 |
65 | );
66 | }
--------------------------------------------------------------------------------
/frontend/src/theme/create-palette.js:
--------------------------------------------------------------------------------
1 | import { common } from '@mui/material/colors';
2 | import { alpha } from '@mui/material/styles';
3 | import { error, indigo, info, neutral, success, warning, graphics } from './colors';
4 |
5 | const getColorScheme = () => {
6 | return JSON.stringify({
7 | "buttons": "#c05521",
8 | "sidebar_end": "#305a85",
9 | "sidebar_initial": "#173033",
10 | "tables": "#214256",
11 | "words_outside_sidebar": "#173033",
12 | "connected_mtps_color": "#c05521"
13 | });
14 | }
15 |
16 | export function createPalette() {
17 |
18 | let colors = getColorScheme();
19 | console.log("colors scheme:", colors);
20 |
21 | let neutralColors = neutral(colors);
22 |
23 | return {
24 | action: {
25 | active: neutralColors[500],
26 | disabled: alpha(neutralColors[900], 0.38),
27 | disabledBackground: alpha(neutralColors[900], 0.12),
28 | focus: alpha(neutralColors[900], 0.16),
29 | hover: alpha(neutralColors[900], 0.04),
30 | selected: alpha(neutralColors[900], 0.12)
31 | },
32 | background: {
33 | default: common.white,
34 | paper: common.white
35 | },
36 | divider: '#F2F4F7',
37 | error,
38 | graphics,
39 | info,
40 | mode: 'light',
41 | neutral: neutralColors,
42 | primary: indigo(colors),
43 | success,
44 | text: {
45 | primary: neutralColors[900],
46 | secondary: neutralColors[500],
47 | disabled: alpha(neutralColors[900], 0.38)
48 | },
49 | warning
50 | };
51 | }
52 |
--------------------------------------------------------------------------------
/frontend/src/theme/create-shadows.js:
--------------------------------------------------------------------------------
1 | export const createShadows = () => {
2 | return [
3 | 'none',
4 | '0px 1px 2px rgba(0, 0, 0, 0.08)',
5 | '0px 1px 5px rgba(0, 0, 0, 0.08)',
6 | '0px 1px 8px rgba(0, 0, 0, 0.08)',
7 | '0px 1px 10px rgba(0, 0, 0, 0.08)',
8 | '0px 1px 14px rgba(0, 0, 0, 0.08)',
9 | '0px 1px 18px rgba(0, 0, 0, 0.08)',
10 | '0px 2px 16px rgba(0, 0, 0, 0.08)',
11 | '0px 3px 14px rgba(0, 0, 0, 0.08)',
12 | '0px 3px 16px rgba(0, 0, 0, 0.08)',
13 | '0px 4px 18px rgba(0, 0, 0, 0.08)',
14 | '0px 4px 20px rgba(0, 0, 0, 0.08)',
15 | '0px 5px 22px rgba(0, 0, 0, 0.08)',
16 | '0px 5px 24px rgba(0, 0, 0, 0.08)',
17 | '0px 5px 26px rgba(0, 0, 0, 0.08)',
18 | '0px 6px 28px rgba(0, 0, 0, 0.08)',
19 | '0px 6px 30px rgba(0, 0, 0, 0.08)',
20 | '0px 6px 32px rgba(0, 0, 0, 0.08)',
21 | '0px 7px 34px rgba(0, 0, 0, 0.08)',
22 | '0px 7px 36px rgba(0, 0, 0, 0.08)',
23 | '0px 8px 38px rgba(0, 0, 0, 0.08)',
24 | '0px 8px 40px rgba(0, 0, 0, 0.08)',
25 | '0px 8px 42px rgba(0, 0, 0, 0.08)',
26 | '0px 9px 44px rgba(0, 0, 0, 0.08)',
27 | '0px 9px 46px rgba(0, 0, 0, 0.08)'
28 | ];
29 | };
30 |
--------------------------------------------------------------------------------
/frontend/src/theme/index.js:
--------------------------------------------------------------------------------
1 | import { createTheme as createMuiTheme } from '@mui/material';
2 | import { createPalette } from './create-palette';
3 | import { createComponents } from './create-components';
4 | import { createShadows } from './create-shadows';
5 | import { createTypography } from './create-typography';
6 |
7 | export function createTheme() {
8 | const palette = createPalette();
9 | const components = createComponents({ palette });
10 | const shadows = createShadows();
11 | const typography = createTypography();
12 |
13 | return createMuiTheme({
14 | breakpoints: {
15 | values: {
16 | xs: 0,
17 | sm: 600,
18 | md: 900,
19 | lg: 1200,
20 | xl: 1440
21 | }
22 | },
23 | components,
24 | palette,
25 | shadows,
26 | shape: {
27 | borderRadius: 8
28 | },
29 | typography
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/src/utils/apply-pagination.js:
--------------------------------------------------------------------------------
1 | export function applyPagination(documents, page, rowsPerPage) {
2 | return documents.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
3 | }
--------------------------------------------------------------------------------
/frontend/src/utils/create-emotion-cache.js:
--------------------------------------------------------------------------------
1 | import createCache from '@emotion/cache';
2 |
3 | export const createEmotionCache = () => {
4 | return createCache({ key: 'css' });
5 | };
6 |
--------------------------------------------------------------------------------
/frontend/src/utils/create-resource-id.js:
--------------------------------------------------------------------------------
1 | export const createResourceId = () => {
2 | const arr = new Uint8Array(12);
3 | window.crypto.getRandomValues(arr);
4 | return Array.from(arr, (v) => v.toString(16).padStart(2, '0')).join('');
5 | };
6 |
--------------------------------------------------------------------------------
/frontend/src/utils/get-initials.js:
--------------------------------------------------------------------------------
1 | export const getInitials = (name = '') => name
2 | .replace(/\s+/, ' ')
3 | .split(' ')
4 | .slice(0, 2)
5 | .map((v) => v && v[0].toUpperCase())
6 | .join('');
7 |
--------------------------------------------------------------------------------
/frontend/src/utils/map.css:
--------------------------------------------------------------------------------
1 | /* The location pointed to by the popup tip. */
2 | .popup-tip-anchor {
3 | height: 0;
4 | position: absolute;
5 | /* The max width of the info window. */
6 | width: 200px;
7 | }
8 | /* The bubble is anchored above the tip. */
9 | .popup-bubble-anchor {
10 | position: absolute;
11 | width: 100%;
12 | bottom: /* TIP_HEIGHT= */ 8px;
13 | left: 0;
14 | }
15 | /* Draw the tip. */
16 | .popup-bubble-anchor::after {
17 | content: "";
18 | position: absolute;
19 | top: 0;
20 | left: 0;
21 | /* Center the tip horizontally. */
22 | transform: translate(-50%, 0);
23 | /* The tip is a https://css-tricks.com/snippets/css/css-triangle/ */
24 | width: 0;
25 | height: 0;
26 | /* The tip is 8px high, and 12px wide. */
27 | border-left: 6px solid transparent;
28 | border-right: 6px solid transparent;
29 | border-top: /* TIP_HEIGHT= */ 8px solid white;
30 | }
31 | /* The popup bubble itself. */
32 | .popup-bubble-content {
33 | position: absolute;
34 | top: 0;
35 | left: 0;
36 | transform: translate(-50%, -100%);
37 | /* Style the info window. */
38 | background-color: white;
39 | padding: 5px;
40 | border-radius: 5px;
41 | font-family: sans-serif;
42 | font-size: 14px ;
43 | overflow-y: auto;
44 | max-height: 80px;
45 | box-shadow: 0px 2px 10px 1px rgba(0, 0, 0, 0.5);
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/src/utils/mapStyles.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "featureType": "all",
4 | "elementType": "labels.text",
5 | "stylers": [
6 | {
7 | "visibility": "on"
8 | }
9 | ]
10 | },
11 | {
12 | "featureType": "poi",
13 | "elementType": "labels.icon",
14 | "stylers": [
15 | {
16 | "visibility": "off"
17 | }
18 | ]
19 | }
20 | ]
--------------------------------------------------------------------------------