├── .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 | [![CircleCI](https://dl.circleci.com/status-badge/img/gh/OktopUSP/oktopus/tree/main.svg?style=svg)](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 | ![Build Status](https://github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/go-stomp/stomp/actions/workflows/test.yml/badge.svg?branch=master) 6 | [![Go Reference](https://pkg.go.dev/badge/github.com/oktopUSP/oktopus/backend/services/mtp/stomp-adapter/internal/go-stomp/stomp/v3.svg)](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 | ![Build Status](https://github.com/go-stomp/stomp/actions/workflows/test.yml/badge.svg?branch=master) 8 | [![Go Reference](https://pkg.go.dev/badge/github.com/go-stomp/stomp/v3.svg)](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 | 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 | ] --------------------------------------------------------------------------------