From a986835e24e67589e1321691c8dd54b267f0adb9 Mon Sep 17 00:00:00 2001 From: Roger Date: Sat, 20 Apr 2024 18:56:55 +0000 Subject: [PATCH 01/18] dev-k8s --- deploy/kubernetes/frontend.yaml | 18 +++++++++--------- deploy/kubernetes/mqtt.yaml | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index 6731899..0f8e9e5 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -25,13 +25,13 @@ spec: memory: 256Mi cpu: 200m ports: - - containerPort: 3000 + - containerPort: 8080 imagePullPolicy: IfNotPresent env: - - name: NEXT_PUBLIC_REST_ENDPOINT - value: "192.168.1.130:30003" - - name: NEXT_PUBLIC_WS_ENDPOINT - value: "192.168.1.130:30005" + # - name: NEXT_PUBLIC_REST_ENDPOINT + # value: "192.168.1.130:30003" + #- name: NEXT_PUBLIC_WS_ENDPOINT + # value: "192.168.1.130:30005" --- apiVersion: v1 kind: Service @@ -42,7 +42,7 @@ spec: app: frontend ports: - protocol: TCP - port: 3000 - targetPort: 3000 - nodePort: 30001 - type: NodePort + port: 8080 + targetPort: 8080 + externalTrafficPolicy: Local + type: LoadBalancer diff --git a/deploy/kubernetes/mqtt.yaml b/deploy/kubernetes/mqtt.yaml index 0d705ee..d421f58 100644 --- a/deploy/kubernetes/mqtt.yaml +++ b/deploy/kubernetes/mqtt.yaml @@ -9,8 +9,8 @@ spec: - protocol: TCP port: 1883 targetPort: 1883 - nodePort: 30000 - type: NodePort + externalTrafficPolicy: Local + type: LoadBalancer --- apiVersion: apps/v1 kind: Deployment From 5e7340c1fa797fb217c0d824dd8c7dc08e27c8fb Mon Sep 17 00:00:00 2001 From: Roger Date: Sun, 21 Apr 2024 01:24:28 +0000 Subject: [PATCH 02/18] ingress controller for frontend and rest api --- deploy/kubernetes/controller.yaml | 3 +-- deploy/kubernetes/frontend.yaml | 16 +++++------ .../haproxy-kubernetes-ingress-cm.yaml | 7 ----- .../kubernetes/haproxy-tcp-services-cm.yaml | 8 ------ deploy/kubernetes/haproxy-tcp-services.yaml | 8 ------ deploy/kubernetes/ingress.yaml | 27 +++++++++++++++++++ 6 files changed, 36 insertions(+), 33 deletions(-) delete mode 100644 deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml delete mode 100644 deploy/kubernetes/haproxy-tcp-services-cm.yaml delete mode 100644 deploy/kubernetes/haproxy-tcp-services.yaml create mode 100644 deploy/kubernetes/ingress.yaml diff --git a/deploy/kubernetes/controller.yaml b/deploy/kubernetes/controller.yaml index 450317d..17b13cf 100644 --- a/deploy/kubernetes/controller.yaml +++ b/deploy/kubernetes/controller.yaml @@ -48,6 +48,5 @@ spec: - protocol: TCP port: 8000 targetPort: 8000 - nodePort: 30003 - type: NodePort + type: ClusterIP diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index 0f8e9e5..6c6b9e7 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -16,7 +16,7 @@ spec: spec: containers: - name: frontend - image: oktopusp/frontend:latest + image: rogersacchelli/frontend:1.0.3 resources: requests: memory: 64Mi @@ -25,11 +25,11 @@ spec: memory: 256Mi cpu: 200m ports: - - containerPort: 8080 + - containerPort: 3000 imagePullPolicy: IfNotPresent env: - # - name: NEXT_PUBLIC_REST_ENDPOINT - # value: "192.168.1.130:30003" + - name: NEXT_PUBLIC_REST_ENDPOINT + value: "/api" #- name: NEXT_PUBLIC_WS_ENDPOINT # value: "192.168.1.130:30005" --- @@ -42,7 +42,7 @@ spec: app: frontend ports: - protocol: TCP - port: 8080 - targetPort: 8080 - externalTrafficPolicy: Local - type: LoadBalancer + port: 3000 + targetPort: 3000 + #externalTrafficPolicy: Local + type: ClusterIP diff --git a/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml b/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml deleted file mode 100644 index c4aabd9..0000000 --- a/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: haproxy-kubernetes-ingress - namespace: haproxy-controller -data: - syslog-server: "address:stdout, format: raw, facility:daemon" diff --git a/deploy/kubernetes/haproxy-tcp-services-cm.yaml b/deploy/kubernetes/haproxy-tcp-services-cm.yaml deleted file mode 100644 index a4b8f7f..0000000 --- a/deploy/kubernetes/haproxy-tcp-services-cm.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: haproxy-tcp - namespace: default -data: - 1883: # Port where the frontend is going to listen to. - default/mqtt-svc:1883 # Kubernetes service in the format NS/ServiceName:ServicePort diff --git a/deploy/kubernetes/haproxy-tcp-services.yaml b/deploy/kubernetes/haproxy-tcp-services.yaml deleted file mode 100644 index 77db1d6..0000000 --- a/deploy/kubernetes/haproxy-tcp-services.yaml +++ /dev/null @@ -1,8 +0,0 @@ -controller: - service: - tcpPorts: - - name: mqtt - port: 1883 - targetPort: 1883 - extraArgs: - - --configmap-tcp-services=default/haproxy-tcp diff --git a/deploy/kubernetes/ingress.yaml b/deploy/kubernetes/ingress.yaml new file mode 100644 index 0000000..5072c8b --- /dev/null +++ b/deploy/kubernetes/ingress.yaml @@ -0,0 +1,27 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: web-ingress + namespace: default + annotations: +spec: + ingressClassName: "haproxy" + rules: + - host: oktopus.rdss.cloud + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: frontend-svc + port: + number: 3000 + - path: /api + pathType: Prefix + backend: + service: + name: controller-svc + port: + number: 8000 + From 01dbfd01632825d710688f30575d8ca15ba753a2 Mon Sep 17 00:00:00 2001 From: Roger Date: Sun, 21 Apr 2024 02:10:37 +0000 Subject: [PATCH 03/18] socketio ingress configuration --- deploy/kubernetes/adapter.yaml | 2 +- deploy/kubernetes/ingress.yaml | 7 +++++++ deploy/kubernetes/socketio.yaml | 2 -- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/deploy/kubernetes/adapter.yaml b/deploy/kubernetes/adapter.yaml index d2d2d23..3160766 100644 --- a/deploy/kubernetes/adapter.yaml +++ b/deploy/kubernetes/adapter.yaml @@ -33,7 +33,7 @@ spec: - name: NATS_VERIFY_CERTIFICATES value: "false" - name: MONGO_URI - 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/adapter?replicaSet=mongodb&ssl=false" + 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" - name: CONTROLLER_ID value: "oktopusController" diff --git a/deploy/kubernetes/ingress.yaml b/deploy/kubernetes/ingress.yaml index 5072c8b..b429543 100644 --- a/deploy/kubernetes/ingress.yaml +++ b/deploy/kubernetes/ingress.yaml @@ -24,4 +24,11 @@ spec: name: controller-svc port: number: 8000 + - path: /socket.io + pathType: Prefix + backend: + service: + name: socketio-svc + port: + number: 5000 diff --git a/deploy/kubernetes/socketio.yaml b/deploy/kubernetes/socketio.yaml index a51c5fd..7a71bdc 100644 --- a/deploy/kubernetes/socketio.yaml +++ b/deploy/kubernetes/socketio.yaml @@ -31,5 +31,3 @@ spec: - protocol: TCP port: 5000 targetPort: 5000 - nodePort: 30002 - type: NodePort From 36285e314f89e0db5a0e18c5e47cb59ecb36398e Mon Sep 17 00:00:00 2001 From: Roger Date: Tue, 23 Apr 2024 02:09:19 +0000 Subject: [PATCH 04/18] final adjustments --- deploy/kubernetes/frontend.yaml | 2 -- deploy/kubernetes/mqtt-adapter.yaml | 2 +- deploy/kubernetes/mqtt.yaml | 3 +++ deploy/kubernetes/socketio.yaml | 2 ++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index 6c6b9e7..92d9391 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -30,8 +30,6 @@ spec: env: - name: NEXT_PUBLIC_REST_ENDPOINT value: "/api" - #- name: NEXT_PUBLIC_WS_ENDPOINT - # value: "192.168.1.130:30005" --- apiVersion: v1 kind: Service diff --git a/deploy/kubernetes/mqtt-adapter.yaml b/deploy/kubernetes/mqtt-adapter.yaml index 3d7b7b5..8bd522d 100644 --- a/deploy/kubernetes/mqtt-adapter.yaml +++ b/deploy/kubernetes/mqtt-adapter.yaml @@ -31,7 +31,7 @@ spec: - name: NATS_VERIFY_CERTIFICATES value: "false" - name: MQTT_URL - value: "tcp://mqtt:1883" + value: "tcp://mqtt-svc:1883" - name: MQTT_CLIENT_ID value: "mqtt-adapter" - name: MQTT_USERNAME diff --git a/deploy/kubernetes/mqtt.yaml b/deploy/kubernetes/mqtt.yaml index d421f58..55fca1b 100644 --- a/deploy/kubernetes/mqtt.yaml +++ b/deploy/kubernetes/mqtt.yaml @@ -46,3 +46,6 @@ spec: value: "false" - name: LOG_LEVEL value: "0" # 0 - DEBUG + - name: REDIS_ENABLE + value: "false" + diff --git a/deploy/kubernetes/socketio.yaml b/deploy/kubernetes/socketio.yaml index 7a71bdc..e8695a1 100644 --- a/deploy/kubernetes/socketio.yaml +++ b/deploy/kubernetes/socketio.yaml @@ -19,6 +19,8 @@ spec: env: - name: NATS_URL value: "nats:4222" + - name: CORS_ALLOWED_ORIGINS + value: "" --- apiVersion: v1 kind: Service From ec9161b037cb35a09fc521918736cddc16bf0b5a Mon Sep 17 00:00:00 2001 From: Roger Date: Tue, 23 Apr 2024 02:14:00 +0000 Subject: [PATCH 05/18] Ingress Controller on Readme --- deploy/kubernetes/README.md | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md index 9ef30ea..8975b3e 100644 --- a/deploy/kubernetes/README.md +++ b/deploy/kubernetes/README.md @@ -19,6 +19,15 @@ git clone https://github.com/OktopUSP/oktopus export DEPLOYMENT_PATH=oktopus/deploy/kubernetes ``` +## HAProxy Ingress Controller + +```shell +helm install haproxy-kubernetes-ingress haproxytech/kubernetes-ingress \ + --create-namespace \ + --namespace haproxy-controller \ + --set controller.kind=DaemonSet \ + --set controller.daemonset.useHostPort=true +``` ## MongoBD @@ -49,27 +58,6 @@ helm install nats nats/nats --set config.jetstream.enabled=true ## Oktopus - -Node Ports - -For this deployment, we are not using a load balancer and kubernetes is deployed on-premises so we are using Nodeports to insource the client traffic into cluster. below the ports set on deployment files: - -1. MQTT broker service (mqtt-svc): 30000 -2. Frontend (frontend-svc): 30001 -3. SocketIO: (socketio-svc): 30002 -4. Controller (controller-svc): 30003 -5. WebSocket (ws-svc): 30005 - -Before deploying the files, edit the frontend.yaml file to set the correct enviroment variables: - -```yaml -env: - - name: NEXT_PUBLIC_REST_ENDPOINT - value: ":30003" - - name: NEXT_PUBLIC_WS_ENDPOINT - value: ":30005" -``` - ```shell kubectl apply -f $DEPLOYMENT_PATH/mqtt.yaml kubectl apply -f $DEPLOYMENT_PATH/mqtt-adapter.yaml @@ -88,4 +76,4 @@ kubectl apply -f $DEPLOYMENT_PATH/ws-adapter.yaml kubectl get pods kubectl get svc -``` \ No newline at end of file +``` From 74ffae7feb3aa7d58f37811e6e0e8cfb33f37508 Mon Sep 17 00:00:00 2001 From: Roger Date: Tue, 23 Apr 2024 02:18:05 +0000 Subject: [PATCH 06/18] Frontend image oktopusp --- deploy/kubernetes/frontend.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index 92d9391..86945db 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -16,7 +16,7 @@ spec: spec: containers: - name: frontend - image: rogersacchelli/frontend:1.0.3 + image: oktopusp/frontend:latest resources: requests: memory: 64Mi From b258c1ada0c4825b6905c88617c6ae980bbc87c6 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 24 Apr 2024 15:10:03 -0300 Subject: [PATCH 07/18] fix(frontend): block access to cwmp or offline device --- frontend/src/sections/overview/overview-latest-orders.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/sections/overview/overview-latest-orders.js b/frontend/src/sections/overview/overview-latest-orders.js index 6f85c03..8b36dca 100644 --- a/frontend/src/sections/overview/overview-latest-orders.js +++ b/frontend/src/sections/overview/overview-latest-orders.js @@ -97,13 +97,11 @@ export const OverviewLatestOrders = (props) => { - { order.Status == 2 && (order.Mqtt == 0 && order.Websockets == 0 && order.Stomp == 0) ? : : { - if (order.Status == 2){ router.push("devices/"+order.SN+"/discovery") - } } } > From 2ead5e0be3ef73cb29ff4b206d163e113fb9ed6f Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 25 Apr 2024 18:07:01 -0300 Subject: [PATCH 08/18] feat(frontend): 403 status error forbidden page --- frontend/src/pages/403.js | 79 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 frontend/src/pages/403.js diff --git a/frontend/src/pages/403.js b/frontend/src/pages/403.js new file mode 100644 index 0000000..a65ce5e --- /dev/null +++ b/frontend/src/pages/403.js @@ -0,0 +1,79 @@ +import Head from 'next/head'; +import NextLink from 'next/link'; +import ArrowLeftIcon from '@heroicons/react/24/solid/ArrowLeftIcon'; +import { Box, Button, Container, SvgIcon, Typography } from '@mui/material'; + +const Page = () => ( + <> + + + 403 | Oktopus TR-369 + + + + + + + Under development + + + 403: You're not allowed to perform this action + + + You either tried to perform an action you're not authorized to do or you came here by mistake. + + + + + + +); + +export default Page; From 90042da9327c1a7e371655eaff404ca65e5ac196 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 25 Apr 2024 18:07:31 -0300 Subject: [PATCH 09/18] feat(api): return user creation date --- backend/services/controller/internal/api/user.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go index 6800d40..542264a 100644 --- a/backend/services/controller/internal/api/user.go +++ b/backend/services/controller/internal/api/user.go @@ -9,6 +9,7 @@ import ( "github.com/leandrofars/oktopus/internal/api/auth" "github.com/leandrofars/oktopus/internal/db" "github.com/leandrofars/oktopus/internal/utils" + "go.mongodb.org/mongo-driver/bson/primitive" ) func (a *Api) retrieveUsers(w http.ResponseWriter, r *http.Request) { @@ -20,6 +21,11 @@ func (a *Api) retrieveUsers(w http.ResponseWriter, r *http.Request) { } for _, x := range users { + objectID, ok := x["_id"].(primitive.ObjectID) + if ok { + creationTime := objectID.Timestamp() + x["createdAt"] = creationTime.Format("02/01/2006") + } delete(x, "password") } From cccf28bc039db98d72aa1b4a654cf67e0be7b174 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 10:04:49 -0300 Subject: [PATCH 10/18] fix(frontend): device loading + device found message --- frontend/src/pages/devices.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/devices.js b/frontend/src/pages/devices.js index 158bf21..a55f33e 100644 --- a/frontend/src/pages/devices.js +++ b/frontend/src/pages/devices.js @@ -22,7 +22,7 @@ const Page = () => { const router = useRouter() const auth = useAuth(); const [devices, setDevices] = useState([]); - const [deviceFound, setDeviceFound] = useState(false) + const [deviceFound, setDeviceFound] = useState(true) const [pages, setPages] = useState(0); const [page, setPage] = useState(null); const [Loading, setLoading] = useState(true); @@ -104,6 +104,7 @@ const Page = () => { const fetchDevicePerId = async (id) => { setLoading(true) + setDeviceFound(true) var myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("Authorization", auth.user.token); From 57aec575c82b91de32a6375a5b65662c67139892 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:09:48 -0300 Subject: [PATCH 11/18] feat(api): validate emails correctly --- .../services/controller/internal/api/user.go | 17 ++++++++++++++++- backend/services/controller/internal/db/db.go | 10 ++++++++++ backend/services/controller/internal/db/user.go | 8 +++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go index 542264a..bb32b37 100644 --- a/backend/services/controller/internal/api/user.go +++ b/backend/services/controller/internal/api/user.go @@ -4,6 +4,7 @@ import ( "encoding/json" "log" "net/http" + "net/mail" "github.com/gorilla/mux" "github.com/leandrofars/oktopus/internal/api/auth" @@ -33,7 +34,6 @@ func (a *Api) retrieveUsers(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println(err) } - return } func (a *Api) registerUser(w http.ResponseWriter, r *http.Request) { @@ -70,12 +70,27 @@ func (a *Api) registerUser(w http.ResponseWriter, r *http.Request) { return } + if user.Email == "" || user.Password == "" || !valid(user.Email) { + w.WriteHeader(http.StatusBadRequest) + return + } + if err := a.db.RegisterUser(user); err != nil { + if err == db.ErrorUserExists { + w.WriteHeader(http.StatusConflict) + w.Write([]byte("User with this email already exists")) + return + } w.WriteHeader(http.StatusInternalServerError) return } } +func valid(email string) bool { + _, err := mail.ParseAddress(email) + return err == nil +} + func (a *Api) deleteUser(w http.ResponseWriter, r *http.Request) { tokenString := r.Header.Get("Authorization") if tokenString == "" { diff --git a/backend/services/controller/internal/db/db.go b/backend/services/controller/internal/db/db.go index 820a925..c3365b2 100644 --- a/backend/services/controller/internal/db/db.go +++ b/backend/services/controller/internal/db/db.go @@ -4,6 +4,7 @@ import ( "context" "log" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -33,6 +34,15 @@ func NewDatabase(ctx context.Context, mongoUri string) Database { log.Println("Connected to MongoDB-->", mongoUri) db.users = client.Database("account-mngr").Collection("users") + indexField := bson.M{"email": 1} + _, err = db.users.Indexes().CreateOne(ctx, mongo.IndexModel{ + Keys: indexField, + Options: options.Index().SetUnique(true), + }) + if err != nil { + log.Fatalln(err) + } + db.ctx = ctx return db diff --git a/backend/services/controller/internal/db/user.go b/backend/services/controller/internal/db/user.go index af0950f..e7e2cfc 100644 --- a/backend/services/controller/internal/db/user.go +++ b/backend/services/controller/internal/db/user.go @@ -1,6 +1,7 @@ package db import ( + "errors" "log" "go.mongodb.org/mongo-driver/bson" @@ -13,8 +14,11 @@ type User struct { Name string `json:"name"` Password string `json:"password,omitempty"` Level int `json:"level"` + Phone string `json:"phone"` } +var ErrorUserExists = errors.New("User already exists") + func (d *Database) RegisterUser(user User) error { err := d.users.FindOne(d.ctx, bson.D{{"email", user.Email}}).Err() if err != nil { @@ -23,8 +27,10 @@ func (d *Database) RegisterUser(user User) error { return err } log.Println(err) + return err + } else { + return ErrorUserExists } - return err } func (d *Database) UpdatePassword(user User) error { From 6dd9614eb518350de540ff0c709d70efb2c8d355 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:10:29 -0300 Subject: [PATCH 12/18] fix(frontend): authcontext get token data from localStorage when page is reloaded --- frontend/src/contexts/auth-context.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/contexts/auth-context.js b/frontend/src/contexts/auth-context.js index 44d790a..0b72543 100644 --- a/frontend/src/contexts/auth-context.js +++ b/frontend/src/contexts/auth-context.js @@ -82,18 +82,22 @@ export const AuthProvider = (props) => { console.error(err); } + console.log("isAuthenticated: ", isAuthenticated) if (isAuthenticated) { const user = { id: '5e86809283e28b96d2d38537', avatar: '/assets/avatars/default-avatar.png', name: 'Oktopus', email: 'anika.visser@devias.io', + token: localStorage.getItem("token") }; dispatch({ type: HANDLERS.INITIALIZE, payload: user }); + + console.log("AUTH CONTEXT --> auth.user.token:", user.token) } else { dispatch({ type: HANDLERS.INITIALIZE From 0327fa9969db1b3736b2a8d5c0dd4c3c770a58f1 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:26:16 -0300 Subject: [PATCH 13/18] feat(frontend): users crud | close #252 --- frontend/src/layouts/dashboard/config.js | 20 + frontend/src/pages/customers.js | 290 ----------- frontend/src/pages/users.js | 449 ++++++++++++++++++ .../src/sections/customer/customers-table.js | 145 ++++-- 4 files changed, 575 insertions(+), 329 deletions(-) delete mode 100644 frontend/src/pages/customers.js create mode 100644 frontend/src/pages/users.js diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js index 612a933..ea00338 100644 --- a/frontend/src/layouts/dashboard/config.js +++ b/frontend/src/layouts/dashboard/config.js @@ -2,6 +2,8 @@ import ChartBarIcon from '@heroicons/react/24/solid/ChartBarIcon'; import CogIcon from '@heroicons/react/24/solid/CogIcon'; import ChatBubbleLeftRightIcon from '@heroicons/react/24/solid/ChatBubbleLeftRightIcon' import MapIcon from '@heroicons/react/24/solid/MapIcon' +import UserGroupIcon from '@heroicons/react/24/solid/UserGroupIcon' +import KeyIcon from '@heroicons/react/24/solid/KeyIcon' import CpuChip from '@heroicons/react/24/solid/CpuChipIcon'; import { SvgIcon } from '@mui/material'; @@ -42,6 +44,24 @@ export const items = [ // // ) // }, + // { + // title: 'Credentials', + // path: '/credentials', + // icon: ( + // + // + // + // ) + // }, + { + title: 'Users', + path: '/users', + icon: ( + + + + ) + }, { title: 'Settings', path: '/settings', diff --git a/frontend/src/pages/customers.js b/frontend/src/pages/customers.js deleted file mode 100644 index d31a34a..0000000 --- a/frontend/src/pages/customers.js +++ /dev/null @@ -1,290 +0,0 @@ -import { useCallback, useMemo, useState } from 'react'; -import Head from 'next/head'; -import { subDays, subHours } from 'date-fns'; -import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon'; -import ArrowUpOnSquareIcon from '@heroicons/react/24/solid/ArrowUpOnSquareIcon'; -import PlusIcon from '@heroicons/react/24/solid/PlusIcon'; -import { Box, Button, Container, Stack, SvgIcon, Typography } from '@mui/material'; -import { useSelection } from 'src/hooks/use-selection'; -import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; -import { CustomersTable } from 'src/sections/customer/customers-table'; -import { CustomersSearch } from 'src/sections/customer/customers-search'; -import { applyPagination } from 'src/utils/apply-pagination'; - -const now = new Date(); - -const data = [ - { - id: '5e887ac47eed253091be10cb', - address: { - city: 'Cleveland', - country: 'USA', - state: 'Ohio', - street: '2849 Fulton Street' - }, - avatar: '/assets/avatars/avatar-carson-darrin.png', - createdAt: subDays(subHours(now, 7), 1).getTime(), - email: 'carson.darrin@devias.io', - name: 'Carson Darrin', - phone: '304-428-3097' - }, - { - id: '5e887b209c28ac3dd97f6db5', - address: { - city: 'Atlanta', - country: 'USA', - state: 'Georgia', - street: '1865 Pleasant Hill Road' - }, - avatar: '/assets/avatars/avatar-fran-perez.png', - createdAt: subDays(subHours(now, 1), 2).getTime(), - email: 'fran.perez@devias.io', - name: 'Fran Perez', - phone: '712-351-5711' - }, - { - id: '5e887b7602bdbc4dbb234b27', - address: { - city: 'North Canton', - country: 'USA', - state: 'Ohio', - street: '4894 Lakeland Park Drive' - }, - avatar: '/assets/avatars/avatar-jie-yan-song.png', - createdAt: subDays(subHours(now, 4), 2).getTime(), - email: 'jie.yan.song@devias.io', - name: 'Jie Yan Song', - phone: '770-635-2682' - }, - { - id: '5e86809283e28b96d2d38537', - address: { - city: 'Madrid', - country: 'Spain', - name: 'Anika Visser', - street: '4158 Hedge Street' - }, - avatar: '/assets/avatars/avatar-anika-visser.png', - createdAt: subDays(subHours(now, 11), 2).getTime(), - email: 'anika.visser@devias.io', - name: 'Anika Visser', - phone: '908-691-3242' - }, - { - id: '5e86805e2bafd54f66cc95c3', - address: { - city: 'San Diego', - country: 'USA', - state: 'California', - street: '75247' - }, - avatar: '/assets/avatars/avatar-miron-vitold.png', - createdAt: subDays(subHours(now, 7), 3).getTime(), - email: 'miron.vitold@devias.io', - name: 'Miron Vitold', - phone: '972-333-4106' - }, - { - id: '5e887a1fbefd7938eea9c981', - address: { - city: 'Berkeley', - country: 'USA', - state: 'California', - street: '317 Angus Road' - }, - avatar: '/assets/avatars/avatar-penjani-inyene.png', - createdAt: subDays(subHours(now, 5), 4).getTime(), - email: 'penjani.inyene@devias.io', - name: 'Penjani Inyene', - phone: '858-602-3409' - }, - { - id: '5e887d0b3d090c1b8f162003', - address: { - city: 'Carson City', - country: 'USA', - state: 'Nevada', - street: '2188 Armbrester Drive' - }, - avatar: '/assets/avatars/avatar-omar-darboe.png', - createdAt: subDays(subHours(now, 15), 4).getTime(), - email: 'omar.darobe@devias.io', - name: 'Omar Darobe', - phone: '415-907-2647' - }, - { - id: '5e88792be2d4cfb4bf0971d9', - address: { - city: 'Los Angeles', - country: 'USA', - state: 'California', - street: '1798 Hickory Ridge Drive' - }, - avatar: '/assets/avatars/avatar-siegbert-gottfried.png', - createdAt: subDays(subHours(now, 2), 5).getTime(), - email: 'siegbert.gottfried@devias.io', - name: 'Siegbert Gottfried', - phone: '702-661-1654' - }, - { - id: '5e8877da9a65442b11551975', - address: { - city: 'Murray', - country: 'USA', - state: 'Utah', - street: '3934 Wildrose Lane' - }, - avatar: '/assets/avatars/avatar-iulia-albu.png', - createdAt: subDays(subHours(now, 8), 6).getTime(), - email: 'iulia.albu@devias.io', - name: 'Iulia Albu', - phone: '313-812-8947' - }, - { - id: '5e8680e60cba5019c5ca6fda', - address: { - city: 'Salt Lake City', - country: 'USA', - state: 'Utah', - street: '368 Lamberts Branch Road' - }, - avatar: '/assets/avatars/avatar-nasimiyu-danai.png', - createdAt: subDays(subHours(now, 1), 9).getTime(), - email: 'nasimiyu.danai@devias.io', - name: 'Nasimiyu Danai', - phone: '801-301-7894' - } -]; - -const useCustomers = (page, rowsPerPage) => { - return useMemo( - () => { - return applyPagination(data, page, rowsPerPage); - }, - [page, rowsPerPage] - ); -}; - -const useCustomerIds = (customers) => { - return useMemo( - () => { - return customers.map((customer) => customer.id); - }, - [customers] - ); -}; - -const Page = () => { - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(5); - const customers = useCustomers(page, rowsPerPage); - const customersIds = useCustomerIds(customers); - const customersSelection = useSelection(customersIds); - - const handlePageChange = useCallback( - (event, value) => { - setPage(value); - }, - [] - ); - - const handleRowsPerPageChange = useCallback( - (event) => { - setRowsPerPage(event.target.value); - }, - [] - ); - - return ( - <> - - - Customers | Devias Kit - - - - - - - - - Customers - - - - - - -
- -
-
- - -
-
-
- - ); -}; - -Page.getLayout = (page) => ( - - {page} - -); - -export default Page; diff --git a/frontend/src/pages/users.js b/frontend/src/pages/users.js new file mode 100644 index 0000000..3549a49 --- /dev/null +++ b/frontend/src/pages/users.js @@ -0,0 +1,449 @@ +import { useCallback, useMemo, useState, useEffect } from 'react'; +import Head from 'next/head'; +import { subDays, subHours } from 'date-fns'; +import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon'; +import ArrowUpOnSquareIcon from '@heroicons/react/24/solid/ArrowUpOnSquareIcon'; +import PlusIcon from '@heroicons/react/24/solid/PlusIcon'; +import { Box, Button, CircularProgress, Container, Dialog, DialogContent, DialogTitle, Stack, SvgIcon, Typography, + DialogActions, + TextField, + Backdrop, +} from '@mui/material'; +import { useSelection } from 'src/hooks/use-selection'; +import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; +import { CustomersTable } from 'src/sections/customer/customers-table'; +import { CustomersSearch } from 'src/sections/customer/customers-search'; +import { applyPagination } from 'src/utils/apply-pagination'; +import { useAuth } from 'src/hooks/use-auth'; +import { useRouter } from 'next/router'; +import { is } from 'date-fns/locale'; +import { set } from 'nprogress'; + +const Page = () => { + + const auth = useAuth(); + const router = useRouter(); + + const validateEmail = (email) => { + return email.match( + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ); + }; + + //const [page, setPage] = useState(0); + //const [rowsPerPage, setRowsPerPage] = useState(5); + const [loading, setLoading] = useState(true); + const [creatingNewUser, setCreatingNewUser] = useState(false); + const [users, setUsers] = useState([]); + const [selected, setSelected] = useState([]); + const [addDeviceDialogOpen, setAddDeviceDialogOpen] = useState(false); + const [newUserData, setNewUserData] = useState({}); + const [isPasswordEmpty, setIsPasswordEmpty] = useState(false); + const [isEmailEmpty, setIsEmailEmpty] = useState(false); + const [isEmailExistent, setIsEmailExistent] = useState(false); + + const deleteUser = (id) => { + console.log("request to delete user: ", id) + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var requestOptions = { + method: 'DELETE', + headers: myHeaders, + redirect: 'follow' + } + + return fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT + '/auth/delete/' + id, requestOptions) + .then(response => { + if (response.status === 401) { + router.push("/auth/login") + } else if (response.status === 403) { + return router.push("/403") + } + setUsers(users.filter(user => user.email !== id)) + }) + .catch(error => { + return console.error('Error:', error) + }); + } + + + const fetchUsers = async () => { + console.log("fetching users data...") + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + } + + return fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT + '/users', requestOptions) + .then(response => { + if (response.status === 401) { + router.push("/auth/login") + } else if (response.status === 403) { + return router.push("/403") + } + return response.json() + }) + .then(json => { + console.log("users: ", json) + setUsers(json) + // setPages(json.pages + 1) + // setPage(json.page +1) + // setDevices(json.devices) + setLoading(false) + }) + .catch(error => { + return console.error('Error:', error) + }); + } + + useEffect(() => { + // if (auth.user.token) { + // console.log("auth.user.token =", auth.user.token) + // }else{ + // auth.user.token = localStorage.getItem("token") + // } + //console.log("auth.user.token =", auth.user.token) + fetchUsers() + }, []); + + // const handlePageChange = useCallback( + // (event, value) => { + // setPage(value); + // }, + // [] + // ); + + // const handleRowsPerPageChange = useCallback( + // (event) => { + // setRowsPerPage(event.target.value); + // }, + // [] + // ); + + const createUser = async (data) => { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var raw = JSON.stringify(data); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + + let result = await fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT+"/auth/register", requestOptions) + + if (result.status == 200) { + console.log("user created: deu boa raça !!") + }else if (result.status == 403) { + console.log("num tenx permissão, seu boca de sandália") + setCreatingNewUser(false) + return router.push("/403") + }else if (result.status == 401){ + console.log("taix nem autenticado, sai fora oh") + setCreatingNewUser(false) + return router.push("/auth/login") + }else if (result.status == 409){ + console.log("usuário já existe, seu boca de bagre") + setIsEmailExistent(true) + setCreatingNewUser(false) + return + }else if (result.status == 400){ + console.log("faltou mandar dados jow") + setAddDeviceDialogOpen(false) + setNewUserData({}) + setIsPasswordEmpty(false) + setIsEmailEmpty(false) + setIsEmailExistent(false) + setCreatingNewUser(false) + return + }else { + console.log("agora quebrasse ux córno mô quiridu") + const content = await result.json() + setCreatingNewUser(false) + throw new Error(content); + } + setAddDeviceDialogOpen(false) + data["_id"] = data.email + data["createdAt"] = new Date().toLocaleDateString('es-pa') + data["level"] = 0 + + setUsers([...users, data]) + setNewUserData({}) + setIsPasswordEmpty(false) + setIsEmailEmpty(false) + setIsEmailExistent(false) + setCreatingNewUser(false) + } + + + + return ( + <> + + + Oktopus | Users + + + + + + + + + Users + + + {/* */} + {/* */} + + +
+ +
+
+ {/* */} + {users && !loading ? + { + setSelected(selected.filter((item) => item !== id)) + }} + //onPageChange={handlePageChange} + //onRowsPerPageChange={handleRowsPerPageChange} + //onSelectAll={customersSelection.handleSelectAll} + onSelectOne={(id) => { + setSelected([...selected, id]) + console.log("added user " + id + " to selected array") + }} + //page={page} + //rowsPerPage={rowsPerPage} + deleteUser={deleteUser} + selected={selected} + /> : + + } +
+
+
+ { + setAddDeviceDialogOpen(false) + setIsEmailEmpty(false) + setIsEmailExistent(false) + setIsPasswordEmpty(false) + setNewUserData({}) + }} + > + Create User + + + { + setNewUserData({...newUserData, email: event.target.value}) + } + } + variant="standard"> + + { + setNewUserData({...newUserData, password: event.target.value}) + } + } + variant="standard"> + + + + { + setNewUserData({...newUserData, name: event.target.value}) + } + } + > + + { + setNewUserData({...newUserData, phone: event.target.value}) + } + } + > + + + + + + + + { + theme.zIndex.drawer + 1 }} + open={creatingNewUser} + > + + + } + + + ); +}; + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; diff --git a/frontend/src/sections/customer/customers-table.js b/frontend/src/sections/customer/customers-table.js index 2d1fe88..f790415 100644 --- a/frontend/src/sections/customer/customers-table.js +++ b/frontend/src/sections/customer/customers-table.js @@ -1,21 +1,31 @@ import PropTypes from 'prop-types'; -import { format } from 'date-fns'; import { Avatar, Box, Card, Checkbox, + Icon, Stack, + Tab, Table, TableBody, TableCell, TableHead, - TablePagination, + //TablePagination, TableRow, - Typography + Typography, + SvgIcon, + Dialog, + DialogActions, + DialogTitle, + DialogContent, + DialogContentText, + Button } from '@mui/material'; import { Scrollbar } from 'src/components/scrollbar'; import { getInitials } from 'src/utils/get-initials'; +import TrashIcon from '@heroicons/react/24/outline/TrashIcon'; +import { useState } from 'react'; export const CustomersTable = (props) => { const { @@ -27,14 +37,18 @@ export const CustomersTable = (props) => { onRowsPerPageChange, onSelectAll, onSelectOne, + deleteUser, page = 0, rowsPerPage = 0, selected = [] } = props; - const selectedSome = (selected.length > 0) && (selected.length < items.length); - const selectedAll = (items.length > 0) && (selected.length === items.length); + // const selectedSome = (selected.length > 0) && (selected.length < items.length); + // const selectedAll = (items.length > 0) && (selected.length === items.length); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [userToDelete, setUserToDelete] = useState("") + return ( @@ -42,8 +56,8 @@ export const CustomersTable = (props) => { - - */} + {/* { @@ -53,55 +67,60 @@ export const CustomersTable = (props) => { onDeselectAll?.(); } }} - /> - - + /> */} + {/* */} + Name Email - + {/* Location - + */} Phone - Signed Up + Created At + + + Level + + + Actions {items.map((customer) => { - const isSelected = selected.includes(customer.id); - const createdAt = format(customer.createdAt, 'dd/MM/yyyy'); - + const isSelected = selected.includes(customer._id); return ( - - */} + {/* { if (event.target.checked) { - onSelectOne?.(customer.id); + console.log(customer._id+" is selected"); + onSelectOne(customer._id); } else { - onDeselectOne?.(customer.id); + onDeselectOne(customer._id); } }} - /> - - + /> */} + {/* */} + - + {getInitials(customer.name)} @@ -112,14 +131,33 @@ export const CustomersTable = (props) => { {customer.email} - - {customer.address.city}, {customer.address.state}, {customer.address.country} - + {/* + {customer.address} + */} {customer.phone} - {createdAt} + {customer.createdAt} + + + {customer.level == 1 ? "Admin" : "User"} + + + { customer.level == 0 ? : } ); @@ -128,15 +166,43 @@ export const CustomersTable = (props) => {
- + //onPageChange={onPageChange} + //onRowsPerPageChange={onRowsPerPageChange} + //page={page} + //rowsPerPage={rowsPerPage} + //rowsPerPageOptions={[5, 10, 25]} + /> */} + setShowDeleteDialog(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + {"Delete User"} + + + Are you sure you want to delete this user? + + + + + + +
); }; @@ -147,10 +213,11 @@ CustomersTable.propTypes = { onDeselectAll: PropTypes.func, onDeselectOne: PropTypes.func, onPageChange: PropTypes.func, - onRowsPerPageChange: PropTypes.func, + //onRowsPerPageChange: PropTypes.func, onSelectAll: PropTypes.func, onSelectOne: PropTypes.func, - page: PropTypes.number, - rowsPerPage: PropTypes.number, + deleteUser: PropTypes.func, + //page: PropTypes.number, + //rowsPerPage: PropTypes.number, selected: PropTypes.array }; From 5dd34fb07362caf6c90c4379d967c44d343146f6 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:45:05 -0300 Subject: [PATCH 14/18] feat(api): allow user to delete his own account --- .../services/controller/internal/api/user.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go index bb32b37..a3707a6 100644 --- a/backend/services/controller/internal/api/user.go +++ b/backend/services/controller/internal/api/user.go @@ -105,21 +105,21 @@ func (a *Api) deleteUser(w http.ResponseWriter, r *http.Request) { //Check if user which is requesting deletion has the necessary privileges rUser, err := a.db.FindUser(email) - if rUser.Level != AdminUser { - w.WriteHeader(http.StatusForbidden) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) return } userEmail := mux.Vars(r)["user"] - if userEmail == email { - w.WriteHeader(http.StatusBadRequest) - return - } - if err := a.db.DeleteUser(userEmail); err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(err) - return + if rUser.Email == userEmail || (rUser.Level == AdminUser && rUser.Email != userEmail) { //Admin can delete any user, but can't delete himself + if err := a.db.DeleteUser(userEmail); err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(err) + return + } + } else { + w.WriteHeader(http.StatusForbidden) } } From 390fda46fb8292789e248f96ee000a5dbd15e43e Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:45:25 -0300 Subject: [PATCH 15/18] fix(api): delete own account --- backend/services/controller/internal/api/user.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go index a3707a6..ec775b6 100644 --- a/backend/services/controller/internal/api/user.go +++ b/backend/services/controller/internal/api/user.go @@ -112,8 +112,7 @@ func (a *Api) deleteUser(w http.ResponseWriter, r *http.Request) { userEmail := mux.Vars(r)["user"] - if rUser.Email == userEmail || (rUser.Level == AdminUser && rUser.Email != userEmail) { //Admin can delete any user, but can't delete himself - if err := a.db.DeleteUser(userEmail); err != nil { + if rUser.Email == userEmail || (rUser.Level == AdminUser && rUser.Email != userEmail) { //Admin can delete any account, but admin account can never be deleted w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(err) return From 0a0af85c65765eac72cc4881915748f230f0a496 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:54:49 -0300 Subject: [PATCH 16/18] fix: conflict --- backend/services/controller/internal/api/user.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go index ec775b6..ba92ad1 100644 --- a/backend/services/controller/internal/api/user.go +++ b/backend/services/controller/internal/api/user.go @@ -113,6 +113,8 @@ func (a *Api) deleteUser(w http.ResponseWriter, r *http.Request) { userEmail := mux.Vars(r)["user"] if rUser.Email == userEmail || (rUser.Level == AdminUser && rUser.Email != userEmail) { //Admin can delete any account, but admin account can never be deleted + if rUser.Email == userEmail || (rUser.Level == AdminUser && rUser.Email != userEmail) { //Admin can delete any account, but can't delete his own account + if err := a.db.DeleteUser(userEmail); err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(err) return From 3fb5f72788b2efee916a57f17a1d979ad5b7f43b Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:53:14 -0300 Subject: [PATCH 17/18] fix(frontend): centralize access device link | close #199 --- frontend/src/sections/overview/overview-latest-orders.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/sections/overview/overview-latest-orders.js b/frontend/src/sections/overview/overview-latest-orders.js index 8b36dca..b84a1f2 100644 --- a/frontend/src/sections/overview/overview-latest-orders.js +++ b/frontend/src/sections/overview/overview-latest-orders.js @@ -79,7 +79,7 @@ export const OverviewLatestOrders = (props) => { hover key={order.SN} > - + {order.SN} @@ -97,7 +97,8 @@ export const OverviewLatestOrders = (props) => { - { order.Mqtt == 0 && order.Websockets == 0 && order.Stomp == 0 ? : : } ); From 56c93d08061f34d01e3fc32271028c64d50e244d Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:56:46 -0300 Subject: [PATCH 18/18] fix(api): conflict in commits --- backend/services/controller/internal/api/user.go | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go index ba92ad1..b3893ad 100644 --- a/backend/services/controller/internal/api/user.go +++ b/backend/services/controller/internal/api/user.go @@ -113,7 +113,6 @@ func (a *Api) deleteUser(w http.ResponseWriter, r *http.Request) { userEmail := mux.Vars(r)["user"] if rUser.Email == userEmail || (rUser.Level == AdminUser && rUser.Email != userEmail) { //Admin can delete any account, but admin account can never be deleted - if rUser.Email == userEmail || (rUser.Level == AdminUser && rUser.Email != userEmail) { //Admin can delete any account, but can't delete his own account if err := a.db.DeleteUser(userEmail); err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(err)