From c79919c6aeeb6cc16d777a64a569d88ee67ec904 Mon Sep 17 00:00:00 2001 From: Heitor Danilo Date: Mon, 12 May 2025 00:07:14 -0300 Subject: [PATCH 1/3] feat(api): introduce postgres --- .env | 11 + api/Dockerfile | 2 + api/go.mod | 13 +- api/go.sum | 29 +- api/server.go | 20 +- api/store/pg/api-key.go | 38 +++ api/store/pg/device.go | 118 +++++++ api/store/pg/metrics.go | 11 + api/store/pg/namespace.go | 68 ++++ api/store/pg/options/options.go | 9 + api/store/pg/pg.go | 53 +++ api/store/pg/private-key.go | 17 + api/store/pg/public-key.go | 58 ++++ api/store/pg/query-options.go | 17 + api/store/pg/session.go | 56 ++++ api/store/pg/system.go | 17 + api/store/pg/tags.go | 17 + api/store/pg/transaction.go | 13 + api/store/pg/user.go | 50 +++ api/store/pg/utils.go | 19 ++ cli/go.mod | 62 +--- cli/go.sum | 559 ++------------------------------ cli/main.go | 29 +- docker-compose.yml | 29 ++ 24 files changed, 700 insertions(+), 615 deletions(-) create mode 100644 api/store/pg/api-key.go create mode 100644 api/store/pg/device.go create mode 100644 api/store/pg/metrics.go create mode 100644 api/store/pg/namespace.go create mode 100644 api/store/pg/options/options.go create mode 100644 api/store/pg/pg.go create mode 100644 api/store/pg/private-key.go create mode 100644 api/store/pg/public-key.go create mode 100644 api/store/pg/query-options.go create mode 100644 api/store/pg/session.go create mode 100644 api/store/pg/system.go create mode 100644 api/store/pg/tags.go create mode 100644 api/store/pg/transaction.go create mode 100644 api/store/pg/user.go create mode 100644 api/store/pg/utils.go diff --git a/.env b/.env index 64974c23250..a2fde616c8f 100644 --- a/.env +++ b/.env @@ -43,6 +43,17 @@ SHELLHUB_DOMAIN=localhost # VALUES: A valid network name SHELLHUB_NETWORK=shellhub_network +# The host for PostgreSQL connection. +SHELLHUB_POSTGRES_HOST=postgres +# The port for PostgreSQL connection. +SHELLHUB_POSTGRES_PORT=5432 +# The username for PostgreSQL authentication. +SHELLHUB_POSTGRES_USER=admin +# The password for PostgreSQL authentication. +SHELLHUB_POSTGRES_PASSWORD=admin +# The name of the default PostgreSQL. +SHELLHUB_POSTGRES_DB=main + # Enable tunnels feature. SHELLHUB_TUNNELS=false diff --git a/api/Dockerfile b/api/Dockerfile index 48bb1fa1b3e..78842ea36ab 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -54,6 +54,7 @@ COPY ./api/entrypoint-dev.sh /entrypoint.sh WORKDIR $GOPATH/src/github.com/shellhub-io/shellhub/api RUN mkdir -p /templates +RUN mkdir -p /migrations COPY ./install.sh /templates/install.sh @@ -67,6 +68,7 @@ RUN apk add curl COPY --from=builder /go/src/github.com/shellhub-io/shellhub/api/api /api RUN mkdir -p /templates +RUN mkdir -p /migrations COPY ./install.sh /templates/install.sh diff --git a/api/go.mod b/api/go.mod index f811d5b02a8..fdf25ab493d 100644 --- a/api/go.mod +++ b/api/go.mod @@ -9,6 +9,7 @@ require ( github.com/getsentry/sentry-go v0.31.1 github.com/golang-jwt/jwt/v4 v4.5.2 github.com/gorilla/websocket v1.5.3 + github.com/jackc/pgx/v5 v5.7.4 github.com/labstack/echo/v4 v4.13.3 github.com/labstack/gommon v0.4.2 github.com/pkg/errors v0.9.1 @@ -19,6 +20,8 @@ require ( github.com/square/mongo-lock v0.0.0-20230808145049-cfcf499f6bf0 github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0 + github.com/uptrace/bun v1.2.11 + github.com/uptrace/bun/dialect/pgdialect v1.2.11 github.com/xakep666/mongo-migrate v0.3.2 go.mongodb.org/mongo-driver v1.17.3 golang.org/x/crypto v0.33.0 @@ -67,12 +70,16 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hibiken/asynq v0.24.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/leodido/go-urn v1.2.2 // indirect github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mholt/archiver/v4 v4.0.0-alpha.8 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -91,6 +98,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/redis/go-redis/v9 v9.0.3 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sethvargo/go-envconfig v0.9.0 // indirect @@ -105,11 +113,12 @@ require ( github.com/tklauser/numcpus v0.7.0 // indirect github.com/tkuchiki/go-timezone v0.2.2 // indirect github.com/tkuchiki/parsetime v0.3.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/vmihailenco/go-tinylfu v0.2.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect diff --git a/api/go.sum b/api/go.sum index 55d67352109..0c5936cfb19 100644 --- a/api/go.sum +++ b/api/go.sum @@ -196,6 +196,16 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -225,9 +235,8 @@ github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 h1:1KuuSOy4ZNgW0K github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -286,6 +295,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k= github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -324,6 +335,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -354,9 +366,15 @@ github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGt github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY= github.com/tkuchiki/parsetime v0.3.0 h1:cvblFQlPeAPJL8g6MgIGCHnnmHSZvluuY+hexoZCNqc= github.com/tkuchiki/parsetime v0.3.0/go.mod h1:OJkQmIrf5Ao7R+WYIdITPOfDVj8LmnHGCfQ8DTs3LCA= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/uptrace/bun v1.2.11 h1:l9dTymsdZZAoSZ1+Qo3utms0RffgkDbIv+1UGk8N1wQ= +github.com/uptrace/bun v1.2.11/go.mod h1:ww5G8h59UrOnCHmZ8O1I/4Djc7M/Z3E+EWFS2KLB6dQ= +github.com/uptrace/bun/dialect/pgdialect v1.2.11 h1:n0VKWm1fL1dwJK5TRxYYLaRKRe14BOg2+AQgpvqzG/M= +github.com/uptrace/bun/dialect/pgdialect v1.2.11/go.mod h1:NvV1S/zwtwBnW8yhJ3XEKAQEw76SkeH7yUhfrx3W1Eo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -364,8 +382,8 @@ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+ github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI= github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q= github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= -github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xakep666/mongo-migrate v0.3.2 h1:qmDtIGiMRIwMvc84fOlsDoP+08S6NWLJDPqa4wPfQ1U= @@ -528,7 +546,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/api/server.go b/api/server.go index ea83eec8e14..d96fbfbeb33 100644 --- a/api/server.go +++ b/api/server.go @@ -8,8 +8,7 @@ import ( "github.com/labstack/echo/v4" "github.com/shellhub-io/shellhub/api/routes" "github.com/shellhub-io/shellhub/api/services" - "github.com/shellhub-io/shellhub/api/store/mongo" - "github.com/shellhub-io/shellhub/api/store/mongo/options" + "github.com/shellhub-io/shellhub/api/store/pg" "github.com/shellhub-io/shellhub/pkg/api/internalclient" "github.com/shellhub-io/shellhub/pkg/cache" "github.com/shellhub-io/shellhub/pkg/geoip/geolite2" @@ -22,6 +21,17 @@ type env struct { // MongoURI specifies the connection string for MongoDB. MongoURI string `env:"MONGO_URI,default=mongodb://mongo:27017/main"` + // PostgresHost specifies the host for PostgreSQL. + PostgresHost string `env:"POSTGRES_HOST,default=postgres"` + // PostgresPort specifies the port for PostgreSQL. + PostgresPort string `env:"POSTGRES_PORT,default=5432"` + // PostgresUser specifies the username for authenticate PostgreSQL. + PostgresUser string `env:"POSTGRES_USER,default=admin"` + // PostgresUser specifies the password for authenticate PostgreSQL. + PostgresPassword string `env:"POSTGRES_PASSWORD,default=admin"` + // PostgresDB especifica o nome do banco de dados PostgreSQL a ser utilizado. + PostgresDB string `env:"POSTGRES_DB,default=main"` + // RedisURI specifies the connection string for Redis. RedisURI string `env:"REDIS_URI,default=redis://redis:6379"` // RedisCachePoolSize defines the maximum number of concurrent connections to Redis cache. @@ -72,14 +82,16 @@ func (s *Server) Setup(ctx context.Context) error { log.Debug("Redis cache initialized successfully") - store, err := mongo.NewStore(ctx, s.env.MongoURI, cache, options.RunMigatrions) + uri := pg.URI(s.env.PostgresHost, s.env.PostgresPort, s.env.PostgresUser, s.env.PostgresPassword, s.env.PostgresDB) + store, err := pg.New(ctx, uri) if err != nil { log. WithError(err). Fatal("failed to create the store") + return err } - log.Debug("MongoDB store connected successfully") + log.Debug("Posgres store connected successfully") apiClient, err := internalclient.NewClient(internalclient.WithAsynqWorker(s.env.RedisURI)) if err != nil { diff --git a/api/store/pg/api-key.go b/api/store/pg/api-key.go new file mode 100644 index 00000000000..61a22977e49 --- /dev/null +++ b/api/store/pg/api-key.go @@ -0,0 +1,38 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/pkg/models" +) + +func (pg *pg) APIKeyCreate(ctx context.Context, APIKey *models.APIKey) (string, error) { + return "", nil +} + +func (pg *pg) APIKeyConflicts(ctx context.Context, tenantID string, target *models.APIKeyConflicts) (conflicts []string, has bool, err error) { + return nil, false, nil +} + +func (pg *pg) APIKeyList(ctx context.Context, tenantID string, paginator query.Paginator, sorter query.Sorter) (apiKeys []models.APIKey, count int, err error) { + return nil, 0, nil +} + +func (pg *pg) APIKeyGet(ctx context.Context, id string) (apiKey *models.APIKey, err error) { + // TODO: unify get methods + return nil, nil +} + +func (pg *pg) APIKeyGetByName(ctx context.Context, tenantID string, name string) (apiKey *models.APIKey, err error) { + // TODO: unify get methods + return nil, nil +} + +func (pg *pg) APIKeyUpdate(ctx context.Context, tenantID, name string, changes *models.APIKeyChanges) (err error) { + return nil +} + +func (pg *pg) APIKeyDelete(ctx context.Context, tenantID, name string) (err error) { + return nil +} diff --git a/api/store/pg/device.go b/api/store/pg/device.go new file mode 100644 index 00000000000..6c6cb5bbdf3 --- /dev/null +++ b/api/store/pg/device.go @@ -0,0 +1,118 @@ +package pg + +import ( + "context" //nolint:gosec + + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/pkg/models" +) + +func (pg *pg) DeviceList(ctx context.Context, status models.DeviceStatus, paginator query.Paginator, filters query.Filters, sorter query.Sorter, acceptable store.DeviceAcceptable) ([]models.Device, int, error) { + return nil, 0, nil +} + +func (pg *pg) DeviceGet(ctx context.Context, uid models.UID) (*models.Device, error) { + return nil, nil +} + +func (pg *pg) DeviceDelete(ctx context.Context, uid models.UID) error { + return nil +} + +func (pg *pg) DeviceCreate(ctx context.Context, d models.Device, hostname string) error { + return nil +} + +func (pg *pg) DeviceRename(ctx context.Context, uid models.UID, hostname string) error { + return nil +} + +func (pg *pg) DeviceLookup(ctx context.Context, namespace, hostname string) (*models.Device, error) { + return nil, nil +} + +// DeviceUpdateStatus updates the status of a specific device in the devices collection +func (pg *pg) DeviceUpdateStatus(ctx context.Context, uid models.UID, status models.DeviceStatus) error { + return nil +} + +func (pg *pg) DeviceListByUsage(ctx context.Context, tenant string) ([]models.UID, error) { + return nil, nil +} + +func (pg *pg) DeviceGetByMac(ctx context.Context, mac string, tenantID string, status models.DeviceStatus) (*models.Device, error) { + return nil, nil +} + +func (pg *pg) DeviceGetByName(ctx context.Context, name string, tenantID string, status models.DeviceStatus) (*models.Device, error) { + return nil, nil +} + +func (pg *pg) DeviceGetByUID(ctx context.Context, uid models.UID, tenantID string) (*models.Device, error) { + return nil, nil +} + +func (pg *pg) DeviceSetPosition(ctx context.Context, uid models.UID, position models.DevicePosition) error { + return nil +} + +func (pg *pg) DeviceChooser(ctx context.Context, tenantID string, chosen []string) error { + return nil +} + +func (pg *pg) DeviceConflicts(ctx context.Context, target *models.DeviceConflicts) ([]string, bool, error) { + return nil, false, nil +} + +func (pg *pg) DeviceUpdate(ctx context.Context, tenantID, uid string, changepg *models.DeviceChanges) error { + return nil +} + +func (pg *pg) DeviceBulkUpdate(ctx context.Context, uids []string, changepg *models.DeviceChanges) (int64, error) { + return int64(0), nil +} + +func (pg *pg) DeviceRemovedCount(ctx context.Context, tenant string) (int64, error) { + return int64(0), nil +} + +func (pg *pg) DeviceRemovedGet(ctx context.Context, tenant string, uid models.UID) (*models.DeviceRemoved, error) { + return nil, nil +} + +func (pg *pg) DeviceRemovedInsert(ctx context.Context, tenant string, device *models.Device) error { //nolint:revive + return nil +} + +func (pg *pg) DeviceRemovedDelete(ctx context.Context, tenant string, uid models.UID) error { + return nil +} + +func (pg *pg) DeviceRemovedList(ctx context.Context, tenant string, paginator query.Paginator, filters query.Filters, sorter query.Sorter) ([]models.DeviceRemoved, int, error) { + return nil, 0, nil +} + +func (pg *pg) DevicePushTag(ctx context.Context, uid models.UID, tag string) error { + return nil +} + +func (pg *pg) DevicePullTag(ctx context.Context, uid models.UID, tag string) error { + return nil +} + +func (pg *pg) DeviceSetTags(ctx context.Context, uid models.UID, tags []string) (int64, int64, error) { + return int64(0), int64(0), nil +} + +func (pg *pg) DeviceBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (int64, error) { + return int64(0), nil +} + +func (pg *pg) DeviceBulkDeleteTag(ctx context.Context, tenant, tag string) (int64, error) { + return int64(0), nil +} + +func (pg *pg) DeviceGetTags(ctx context.Context, tenant string) ([]string, int, error) { + return nil, 0, nil +} diff --git a/api/store/pg/metrics.go b/api/store/pg/metrics.go new file mode 100644 index 00000000000..5474c4662ee --- /dev/null +++ b/api/store/pg/metrics.go @@ -0,0 +1,11 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/pkg/models" +) + +func (pg *pg) GetStats(ctx context.Context) (*models.Stats, error) { + return nil, nil +} diff --git a/api/store/pg/namespace.go b/api/store/pg/namespace.go new file mode 100644 index 00000000000..7b88763a640 --- /dev/null +++ b/api/store/pg/namespace.go @@ -0,0 +1,68 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/pkg/models" +) + +func (pg *pg) NamespaceCreate(ctx context.Context, namespace *models.Namespace) (*models.Namespace, error) { + return nil, nil +} + +func (pg *pg) NamespaceList(ctx context.Context, paginator query.Paginator, filters query.Filters, opts ...store.NamespaceQueryOption) ([]models.Namespace, int, error) { + return nil, 0, nil +} + +func (pg *pg) NamespaceGet(ctx context.Context, tenantID string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { + return nil, nil +} + +func (pg *pg) NamespaceGetByName(ctx context.Context, name string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { + // TODO: unify get methods + return nil, nil +} + +func (pg *pg) NamespaceGetPreferred(ctx context.Context, userID string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { + // TODO: unify get methods + return nil, nil +} + +func (pg *pg) NamespaceEdit(ctx context.Context, tenant string, changes *models.NamespaceChanges) error { + // TODO: unify update methods + return nil +} + +func (pg *pg) NamespaceUpdate(ctx context.Context, tenantID string, namespace *models.Namespace) error { + // TODO: unify update methods + return nil +} + +func (pg *pg) NamespaceDelete(ctx context.Context, tenantID string) error { + return nil +} + +// TODO: members must be an association N:N between users and namespaces now +func (pg *pg) NamespaceAddMember(ctx context.Context, tenantID string, member *models.Member) error { + return nil +} + +func (pg *pg) NamespaceUpdateMember(ctx context.Context, tenantID string, memberID string, changes *models.MemberChanges) error { + return nil +} + +func (pg *pg) NamespaceRemoveMember(ctx context.Context, tenantID string, memberID string) error { + return nil +} + +func (pg *pg) NamespaceSetSessionRecord(ctx context.Context, sessionRecord bool, tenantID string) error { + // TODO: these methods are not used anymore + return nil +} + +func (pg *pg) NamespaceGetSessionRecord(ctx context.Context, tenantID string) (bool, error) { + // TODO: these methods are not used anymore + return false, nil +} diff --git a/api/store/pg/options/options.go b/api/store/pg/options/options.go new file mode 100644 index 00000000000..e166dd55e24 --- /dev/null +++ b/api/store/pg/options/options.go @@ -0,0 +1,9 @@ +package options + +import ( + "context" + + "github.com/uptrace/bun" +) + +type Option func(ctx context.Context, db *bun.DB) error diff --git a/api/store/pg/pg.go b/api/store/pg/pg.go new file mode 100644 index 00000000000..829a24f1d8b --- /dev/null +++ b/api/store/pg/pg.go @@ -0,0 +1,53 @@ +package pg + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/stdlib" + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/api/store/pg/options" + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" +) + +type pg struct { + driver *bun.DB +} + +func URI(host, port, user, password, db string) string { + return fmt.Sprintf("postgres://%s:%s@%s:%s/%s", user, password, host, port, db) +} + +func New(ctx context.Context, uri string, opts ...options.Option) (store.Store, error) { + config, err := pgxpool.ParseConfig(uri) + if err != nil { + return nil, err + } + + config.ConnConfig.DefaultQueryExecMode = pgx.QueryExecModeSimpleProtocol + + pool, err := pgxpool.NewWithConfig(ctx, config) + if err != nil { + return nil, err + } + + pg := &pg{driver: bun.NewDB(stdlib.OpenDBFromPool(pool), pgdialect.New())} + if err := pg.driver.Ping(); err != nil { + return nil, err + } + + // We need to register models so we can apply fixtures and relations later + pg.driver.RegisterModel((*models.User)(nil)) + + for _, opt := range opts { + if err := opt(ctx, pg.driver); err != nil { + return nil, err + } + } + + return pg, nil +} diff --git a/api/store/pg/private-key.go b/api/store/pg/private-key.go new file mode 100644 index 00000000000..7db5a8528f5 --- /dev/null +++ b/api/store/pg/private-key.go @@ -0,0 +1,17 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/pkg/models" +) + +func (pg *pg) PrivateKeyCreate(ctx context.Context, key *models.PrivateKey) error { + // TODO: private keys are now saved only in the frontend and this can be removed + return nil +} + +func (pg *pg) PrivateKeyGet(ctx context.Context, fingerprint string) (*models.PrivateKey, error) { + // TODO: private keys are now saved only in the frontend and this can be removedV + return nil, nil +} diff --git a/api/store/pg/public-key.go b/api/store/pg/public-key.go new file mode 100644 index 00000000000..9335ec6c5d9 --- /dev/null +++ b/api/store/pg/public-key.go @@ -0,0 +1,58 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/pkg/models" +) + +func (pg *pg) PublicKeyCreate(ctx context.Context, key *models.PublicKey) error { + return nil +} + +func (pg *pg) PublicKeyList(ctx context.Context, paginator query.Paginator) ([]models.PublicKey, int, error) { + return nil, 0, nil +} + +func (pg *pg) PublicKeyGet(ctx context.Context, fingerprint string, tenantID string) (*models.PublicKey, error) { + return nil, nil +} + +func (pg *pg) PublicKeyUpdate(ctx context.Context, fingerprint string, tenantID string, key *models.PublicKeyUpdate) (*models.PublicKey, error) { + return nil, nil +} + +func (pg *pg) PublicKeyDelete(ctx context.Context, fingerprint string, tenantID string) error { + return nil +} + +func (pg *pg) PublicKeyPushTag(ctx context.Context, tenant, fingerprint, tag string) error { + // TODO: refactor tags entirely + return nil +} + +func (pg *pg) PublicKeyPullTag(ctx context.Context, tenant, fingerprint, tag string) error { + // TODO: refactor tags entirely + return nil +} + +func (pg *pg) PublicKeySetTags(ctx context.Context, tenant, fingerprint string, tags []string) (matchedCount int64, updatedCount int64, err error) { + // TODO: refactor tags entirely + return 0, 0, nil +} + +func (pg *pg) PublicKeyBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (updatedCount int64, err error) { + // TODO: refactor tags entirely + return 0, nil +} + +func (pg *pg) PublicKeyBulkDeleteTag(ctx context.Context, tenant, tag string) (updatedCount int64, err error) { + // TODO: refactor tags entirely + return 0, nil +} + +func (pg *pg) PublicKeyGetTags(ctx context.Context, tenant string) (tag []string, size int, err error) { + // TODO: refactor tags entirely + return nil, 0, nil +} diff --git a/api/store/pg/query-options.go b/api/store/pg/query-options.go new file mode 100644 index 00000000000..3a7ba30f4bc --- /dev/null +++ b/api/store/pg/query-options.go @@ -0,0 +1,17 @@ +package pg + +import "github.com/shellhub-io/shellhub/api/store" + +// TODO: maybe these methods can be deprecated with bun + +func (pg *pg) Options() store.QueryOptions { + return nil +} + +func (pg *pg) CountAcceptedDevices() store.NamespaceQueryOption { + return nil +} + +func (pg *pg) EnrichMembersData() store.NamespaceQueryOption { + return nil +} diff --git a/api/store/pg/session.go b/api/store/pg/session.go new file mode 100644 index 00000000000..62e17a1e409 --- /dev/null +++ b/api/store/pg/session.go @@ -0,0 +1,56 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/pkg/models" +) + +func (pg *pg) SessionList(ctx context.Context, paginator query.Paginator) ([]models.Session, int, error) { + return nil, 0, nil +} + +func (pg *pg) SessionGet(ctx context.Context, uid models.UID) (*models.Session, error) { + return nil, nil +} + +func (pg *pg) SessionUpdate(ctx context.Context, uid models.UID, model *models.Session) error { + return nil +} + +func (pg *pg) SessionSetRecorded(ctx context.Context, uid models.UID, recorded bool) error { + return nil +} + +func (pg *pg) SessionCreate(ctx context.Context, session models.Session) (*models.Session, error) { + return nil, nil +} + +func (pg *pg) SessionSetLastSeen(ctx context.Context, uid models.UID) error { + return nil +} + +func (pg *pg) SessionDeleteActives(ctx context.Context, uid models.UID) error { + return nil +} + +func (pg *pg) SessionUpdateDeviceUID(ctx context.Context, oldUID models.UID, newUID models.UID) error { + return nil +} + +func (pg *pg) SessionActiveCreate(ctx context.Context, uid models.UID, session *models.Session) error { + return nil +} + +func (pg *pg) SessionEvent(ctx context.Context, uid models.UID, event *models.SessionEvent) error { + return nil +} + +func (pg *pg) SessionListEvents(ctx context.Context, uid models.UID, seat int, event models.SessionEventType, paginator query.Paginator) ([]models.SessionEvent, int, error) { + return nil, 0, nil +} + +func (pg *pg) SessionDeleteEvents(ctx context.Context, uid models.UID, seat int, event models.SessionEventType) error { + return nil +} diff --git a/api/store/pg/system.go b/api/store/pg/system.go new file mode 100644 index 00000000000..0576d50ff74 --- /dev/null +++ b/api/store/pg/system.go @@ -0,0 +1,17 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/pkg/models" +) + +// TODO: maybe systems config can be an file? + +func (pg *pg) SystemGet(ctx context.Context) (*models.System, error) { + return nil, nil +} + +func (pg *pg) SystemSet(ctx context.Context, key string, value any) error { + return nil +} diff --git a/api/store/pg/tags.go b/api/store/pg/tags.go new file mode 100644 index 00000000000..334582e8979 --- /dev/null +++ b/api/store/pg/tags.go @@ -0,0 +1,17 @@ +package pg + +import "context" + +// TODO: refactor tags entirely + +func (pg *pg) TagsGet(ctx context.Context, tenant string) (tags []string, n int, err error) { + return nil, 0, nil +} + +func (pg *pg) TagsRename(ctx context.Context, tenant string, oldTag string, newTag string) (updatedCount int64, err error) { + return 0, nil +} + +func (pg *pg) TagsDelete(ctx context.Context, tenant string, tag string) (updatedCount int64, err error) { + return 0, nil +} diff --git a/api/store/pg/transaction.go b/api/store/pg/transaction.go new file mode 100644 index 00000000000..726ea9e181b --- /dev/null +++ b/api/store/pg/transaction.go @@ -0,0 +1,13 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/api/store" +) + +// TODO: this works with mongodb, but will works with bun? + +func (pg *pg) WithTransaction(ctx context.Context, cb store.TransactionCb) error { + return nil +} diff --git a/api/store/pg/user.go b/api/store/pg/user.go new file mode 100644 index 00000000000..a5a5d0bf996 --- /dev/null +++ b/api/store/pg/user.go @@ -0,0 +1,50 @@ +package pg + +import ( + "context" + + "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/pkg/models" +) + +func (pg *pg) UserCreate(ctx context.Context, user *models.User) (string, error) { + return "", nil +} + +func (pg *pg) UserCreateInvited(ctx context.Context, email string) (string, error) { + // TODO: unify create methods + return "", nil +} + +func (pg *pg) UserConflicts(ctx context.Context, target *models.UserConflicts) ([]string, bool, error) { + return nil, false, nil +} + +func (pg *pg) UserList(ctx context.Context, paginator query.Paginator, filters query.Filters) ([]models.User, int, error) { + return nil, 0, nil +} + +func (pg *pg) UserGetByID(ctx context.Context, id string, ns bool) (*models.User, int, error) { + return nil, 0, nil +} + +func (pg *pg) UserGetByUsername(ctx context.Context, username string) (*models.User, error) { + return nil, nil +} + +func (pg *pg) UserGetByEmail(ctx context.Context, email string) (*models.User, error) { + return nil, nil +} + +func (pg *pg) UserGetInfo(ctx context.Context, id string) (userInfo *models.UserInfo, err error) { + // TODO: unify get methods + return nil, nil +} + +func (pg *pg) UserUpdate(ctx context.Context, id string, changes *models.UserChanges) error { + return nil +} + +func (pg *pg) UserDelete(ctx context.Context, id string) error { + return nil +} diff --git a/api/store/pg/utils.go b/api/store/pg/utils.go new file mode 100644 index 00000000000..920499cc6f6 --- /dev/null +++ b/api/store/pg/utils.go @@ -0,0 +1,19 @@ +package pg + +import ( + "database/sql" + "io" + + "github.com/shellhub-io/shellhub/api/store" +) + +func fromSqlError(err error) error { + switch { + case err == nil: + return nil + case err == sql.ErrNoRows, err == io.EOF: + return store.ErrNoDocuments + default: + return err + } +} diff --git a/cli/go.mod b/cli/go.mod index 62fd33751de..d7b3e916ed5 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -13,73 +13,33 @@ require ( ) require ( - github.com/adhocore/gronx v1.8.1 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect - github.com/bodgit/plumbing v1.2.0 // indirect - github.com/bodgit/sevenzip v1.3.0 // indirect - github.com/bodgit/windows v1.0.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/connesc/cipherio v0.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.11.2 // indirect - github.com/go-redis/cache/v8 v8.4.4 // indirect - github.com/go-redis/redis/v8 v8.11.5 // indirect - github.com/go-resty/resty/v2 v2.7.0 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hibiken/asynq v0.24.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect - github.com/klauspost/pgzip v1.2.5 // indirect - github.com/labstack/echo/v4 v4.13.3 // indirect - github.com/labstack/gommon v0.4.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.4 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/leodido/go-urn v1.2.2 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mholt/archiver/v4 v4.0.0-alpha.8 // indirect - github.com/montanaflynn/stats v0.7.1 // indirect - github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect - github.com/oschwald/geoip2-golang v1.8.0 // indirect - github.com/oschwald/maxminddb-golang v1.10.0 // indirect - github.com/pierrec/lz4/v4 v4.1.17 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/redis/go-redis/v9 v9.0.3 // indirect - github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/sethvargo/go-envconfig v0.9.0 // indirect - github.com/spf13/cast v1.3.1 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/square/mongo-lock v0.0.0-20230808145049-cfcf499f6bf0 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/therootcompany/xz v1.0.1 // indirect - github.com/ulikunitz/xz v0.5.11 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/vmihailenco/go-tinylfu v0.2.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/uptrace/bun v1.2.11 // indirect + github.com/uptrace/bun/dialect/pgdialect v1.2.11 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/xakep666/mongo-migrate v0.3.2 // indirect - github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.2 // indirect - github.com/xdg-go/stringprep v1.0.4 // indirect - github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - go.mongodb.org/mongo-driver v1.17.3 // indirect - go4.org v0.0.0-20200411211856-f5505b9728dd // indirect golang.org/x/crypto v0.33.0 // indirect - golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.8.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/cli/go.sum b/cli/go.sum index 7200ef202c7..1bf4120cdbf 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,96 +1,7 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/adhocore/gronx v1.8.1 h1:F2mLTG5sB11z7vplwD4iydz3YCEjstSfYmCrdSm3t6A= -github.com/adhocore/gronx v1.8.1/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= -github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= -github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= -github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM= -github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA= -github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= -github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= -github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= -github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= -github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= -github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= -github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= -github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -99,506 +10,78 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/go-redis/cache/v8 v8.4.4 h1:Rm0wZ55X22BA2JMqVtRQNHYyzDd0I5f+Ec/C9Xx3mXY= -github.com/go-redis/cache/v8 v8.4.4/go.mod h1:JM6CkupsPvAu/LYEVGQy6UB4WDAzQSXkR0lUCbeIcKc= -github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= -github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw= -github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= -github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= -github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 h1:1KuuSOy4ZNgW0KA2oYIngXVFhQcXxhLqCVK7cBcldkk= -github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= -github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= -github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= -github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs= -github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw= -github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= -github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= -github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= -github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k= -github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE= github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= -github.com/shellhub-io/mongotest v0.0.0-20230928124937-e33b07010742 h1:sIFW1zdZvMTAvpHYOphDoWSh4tiGloK0El2GZni4E+U= -github.com/shellhub-io/mongotest v0.0.0-20230928124937-e33b07010742/go.mod h1:6J6yfW5oIvAZ6VjxmV9KyFZyPFVM3B4V3Epbb+1c0oo= -github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= -github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/square/mongo-lock v0.0.0-20230808145049-cfcf499f6bf0 h1:wnVho7xObpxuF7Lr0146VZtfOLfbkXGcvzfFUw2LXuM= -github.com/square/mongo-lock v0.0.0-20230808145049-cfcf499f6bf0/go.mod h1:bLPJcGVut+NBtZhrqY/jTnfluDrZeuIvf66VjuwU/eU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= -github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= -github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0 h1:i1Kh9fmXgHG9z3uzJv5Arz7pDKVaaNpLrqyd+0xhYMA= -github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0/go.mod h1:SD8nVMK1m7b/K2YJqYjYNzfHmZfqHtqNOlI44nfxjdg= -github.com/testcontainers/testcontainers-go/modules/redis v0.32.0 h1:HW5Qo9qfLi5iwfS7cbXwG6qe8ybXGePcgGPEmVlVDlo= -github.com/testcontainers/testcontainers-go/modules/redis v0.32.0/go.mod h1:5kltdxVKZG0aP1iegeqKz4K8HHyP0wbkW5o84qLyMjY= -github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= -github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= -github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= -github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= -github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= -github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q= -github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY= -github.com/tkuchiki/parsetime v0.3.0 h1:cvblFQlPeAPJL8g6MgIGCHnnmHSZvluuY+hexoZCNqc= -github.com/tkuchiki/parsetime v0.3.0/go.mod h1:OJkQmIrf5Ao7R+WYIdITPOfDVj8LmnHGCfQ8DTs3LCA= -github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= -github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI= -github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q= -github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= -github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun v1.2.11 h1:l9dTymsdZZAoSZ1+Qo3utms0RffgkDbIv+1UGk8N1wQ= +github.com/uptrace/bun v1.2.11/go.mod h1:ww5G8h59UrOnCHmZ8O1I/4Djc7M/Z3E+EWFS2KLB6dQ= +github.com/uptrace/bun/dialect/pgdialect v1.2.11 h1:n0VKWm1fL1dwJK5TRxYYLaRKRe14BOg2+AQgpvqzG/M= +github.com/uptrace/bun/dialect/pgdialect v1.2.11/go.mod h1:NvV1S/zwtwBnW8yhJ3XEKAQEw76SkeH7yUhfrx3W1Eo= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xakep666/mongo-migrate v0.3.2 h1:qmDtIGiMRIwMvc84fOlsDoP+08S6NWLJDPqa4wPfQ1U= -github.com/xakep666/mongo-migrate v0.3.2/go.mod h1:onPlsF/AvU9UZjlyX3PiC5iAPHYJuejPPPqlOvsCGhM= -github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= -github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= -go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= -go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= -go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= -go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= -go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= -go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cli/main.go b/cli/main.go index 075dbea77c2..0175ddfee26 100644 --- a/cli/main.go +++ b/cli/main.go @@ -3,10 +3,9 @@ package main import ( "context" - "github.com/shellhub-io/shellhub/api/store/mongo" + "github.com/shellhub-io/shellhub/api/store/pg" "github.com/shellhub-io/shellhub/cli/cmd" "github.com/shellhub-io/shellhub/cli/services" - "github.com/shellhub-io/shellhub/pkg/cache" "github.com/shellhub-io/shellhub/pkg/envs" "github.com/shellhub-io/shellhub/pkg/loglevel" log "github.com/sirupsen/logrus" @@ -15,6 +14,18 @@ import ( type config struct { MongoURI string `env:"MONGO_URI,default=mongodb://mongo:27017/main"` + + // PostgresHost specifies the host for PostgreSQL. + PostgresHost string `env:"POSTGRES_HOST,default=postgres"` + // PostgresPort specifies the port for PostgreSQL. + PostgresPort string `env:"POSTGRES_PORT,default=5432"` + // PostgresUser specifies the username for authenticate PostgreSQL. + PostgresUser string `env:"POSTGRES_USER,default=admin"` + // PostgresUser specifies the password for authenticate PostgreSQL. + PostgresPassword string `env:"POSTGRES_PASSWORD,default=admin"` + // PostgresDB especifica o nome do banco de dados PostgreSQL a ser utilizado. + PostgresDB string `env:"POSTGRES_DB,default=main"` + RedisURI string `env:"REDIS_URI,default=redis://redis:6379"` } @@ -30,18 +41,8 @@ func main() { log.Error(err.Error()) } - log.Info("Connecting to Redis") - - cache, err := cache.NewRedisCache(cfg.RedisURI, 0) - if err != nil { - log.Fatal(err) - } - - log.Info("Connected to Redis") - - log.Trace("Connecting to MongoDB") - - store, err := mongo.NewStore(ctx, cfg.MongoURI, cache) + uri := pg.URI(cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresUser, cfg.PostgresPassword, cfg.PostgresDB) + store, err := pg.New(ctx, uri) if err != nil { log. WithError(err). diff --git a/docker-compose.yml b/docker-compose.yml index e425712c9dc..62016f32cf4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,12 +49,19 @@ services: - ASYNQ_UNIQUENESS_TIMEOUT=${SHELLHUB_ASYNQ_UNIQUENESS_TIMEOUT} - REDIS_CACHE_POOL_SIZE=${SHELLHUB_REDIS_CACHE_POOL_SIZE} - MAXIMUM_ACCOUNT_LOCKOUT=${SHELLHUB_MAXIMUM_ACCOUNT_LOCKOUT} + - POSTGRES_HOST=${SHELLHUB_POSTGRES_HOST} + - POSTGRES_PORT=${SHELLHUB_POSTGRES_PORT} + - POSTGRES_USER=${SHELLHUB_POSTGRES_USER} + - POSTGRES_PASSWORD=${SHELLHUB_POSTGRES_PASSWORD} + - POSTGRES_DB=${SHELLHUB_POSTGRES_DB} depends_on: - mongo - redis + - postgres links: - mongo - redis + - postgres secrets: - api_private_key - api_public_key @@ -64,6 +71,8 @@ services: test: "curl -f http://api:8080/api/healthcheck || exit 1" interval: 30s start_period: 10s + volumes: + - ./api/store/pg/migrations:/migrations ui: image: shellhubio/ui:${SHELLHUB_VERSION} restart: unless-stopped @@ -119,6 +128,11 @@ services: environment: - SHELLHUB_LOG_LEVEL=${SHELLHUB_LOG_LEVEL} - SHELLHUB_LOG_FORMAT=${SHELLHUB_LOG_FORMAT} + - POSTGRES_HOST=${SHELLHUB_POSTGRES_HOST} + - POSTGRES_PORT=${SHELLHUB_POSTGRES_PORT} + - POSTGRES_USER=${SHELLHUB_POSTGRES_USER} + - POSTGRES_PASSWORD=${SHELLHUB_POSTGRES_PASSWORD} + - POSTGRES_DB=${SHELLHUB_POSTGRES_DB} networks: - shellhub mongo: @@ -137,6 +151,21 @@ services: command: ["redis-server", "--appendonly", "no", "--save", "\"\""] networks: - shellhub + postgres: + image: postgres:17 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${SHELLHUB_POSTGRES_USER} -d ${SHELLHUB_POSTGRES_DB}"] + interval: 5s + timeout: 5s + retries: 5 + environment: + - POSTGRES_USER=${SHELLHUB_POSTGRES_USER} + - POSTGRES_PASSWORD=${SHELLHUB_POSTGRES_PASSWORD} + - POSTGRES_DB=${SHELLHUB_POSTGRES_DB} + ports: + - "5432:5432" + networks: + - shellhub secrets: ssh_private_key: From a2df84ad2503b3f22b048d2e46be0bc6a0109fa4 Mon Sep 17 00:00:00 2001 From: Heitor Danilo Date: Mon, 12 May 2025 00:08:37 -0300 Subject: [PATCH 2/3] feat(api,cli): delete mongo store --- api/pkg/echo/handlers/errors.go | 10 - api/services/member.go | 3 - api/store/mongo/api-key.go | 154 -- api/store/mongo/api-key_test.go | 528 ------- api/store/mongo/device.go | 625 -------- api/store/mongo/device_tags.go | 64 - api/store/mongo/device_tags_test.go | 322 ---- api/store/mongo/device_test.go | 1375 ----------------- api/store/mongo/fixtures/active_sessions.json | 8 - api/store/mongo/fixtures/api-key.json | 22 - api/store/mongo/fixtures/devices.json | 80 - api/store/mongo/fixtures/firewall_rules.json | 48 - api/store/mongo/fixtures/namespaces.json | 88 -- api/store/mongo/fixtures/private_keys.json | 9 - api/store/mongo/fixtures/public_keys.json | 15 - .../mongo/fixtures/recorded_sessions.json | 16 - api/store/mongo/fixtures/recovery_tokens.json | 9 - api/store/mongo/fixtures/sessions.json | 76 - api/store/mongo/fixtures/users.json | 59 - api/store/mongo/migrations/main.go | 119 -- api/store/mongo/migrations/main_test.go | 58 - api/store/mongo/migrations/migration_1.go | 32 - api/store/mongo/migrations/migration_10.go | 42 - .../mongo/migrations/migration_10_test.go | 57 - api/store/mongo/migrations/migration_11.go | 43 - .../mongo/migrations/migration_11_test.go | 50 - api/store/mongo/migrations/migration_12.go | 47 - .../mongo/migrations/migration_12_test.go | 33 - api/store/mongo/migrations/migration_13.go | 124 -- .../mongo/migrations/migration_13_test.go | 116 -- api/store/mongo/migrations/migration_14.go | 118 -- .../mongo/migrations/migration_14_test.go | 64 - api/store/mongo/migrations/migration_15.go | 40 - .../mongo/migrations/migration_15_test.go | 35 - api/store/mongo/migrations/migration_16.go | 40 - .../mongo/migrations/migration_16_test.go | 33 - api/store/mongo/migrations/migration_17.go | 177 --- .../mongo/migrations/migration_17_test.go | 127 -- api/store/mongo/migrations/migration_18.go | 40 - .../mongo/migrations/migration_18_test.go | 39 - api/store/mongo/migrations/migration_19.go | 40 - .../mongo/migrations/migration_19_test.go | 48 - .../mongo/migrations/migration_1_test.go | 19 - api/store/mongo/migrations/migration_2.go | 32 - api/store/mongo/migrations/migration_20.go | 70 - .../mongo/migrations/migration_20_test.go | 39 - api/store/mongo/migrations/migration_21.go | 103 -- .../mongo/migrations/migration_21_test.go | 50 - api/store/mongo/migrations/migration_22.go | 80 - .../mongo/migrations/migration_22_test.go | 60 - api/store/mongo/migrations/migration_23.go | 56 - .../mongo/migrations/migration_23_test.go | 96 -- api/store/mongo/migrations/migration_24.go | 49 - .../mongo/migrations/migration_24_test.go | 133 -- api/store/mongo/migrations/migration_25.go | 64 - .../mongo/migrations/migration_25_test.go | 88 -- api/store/mongo/migrations/migration_26.go | 58 - .../mongo/migrations/migration_26_test.go | 81 - api/store/mongo/migrations/migration_27.go | 34 - .../mongo/migrations/migration_27_test.go | 63 - api/store/mongo/migrations/migration_28.go | 41 - .../mongo/migrations/migration_28_test.go | 53 - api/store/mongo/migrations/migration_29.go | 36 - .../mongo/migrations/migration_29_test.go | 56 - .../mongo/migrations/migration_2_test.go | 50 - api/store/mongo/migrations/migration_3.go | 32 - api/store/mongo/migrations/migration_30.go | 36 - .../mongo/migrations/migration_30_test.go | 39 - api/store/mongo/migrations/migration_31.go | 37 - .../mongo/migrations/migration_31_test.go | 39 - api/store/mongo/migrations/migration_32.go | 36 - .../mongo/migrations/migration_32_test.go | 49 - api/store/mongo/migrations/migration_33.go | 46 - .../mongo/migrations/migration_33_test.go | 49 - api/store/mongo/migrations/migration_34.go | 41 - .../mongo/migrations/migration_34_test.go | 57 - api/store/mongo/migrations/migration_35.go | 32 - .../mongo/migrations/migration_35_test.go | 50 - api/store/mongo/migrations/migration_36.go | 40 - .../mongo/migrations/migration_36_test.go | 112 -- api/store/mongo/migrations/migration_37.go | 125 -- .../mongo/migrations/migration_37_test.go | 80 - api/store/mongo/migrations/migration_38.go | 59 - .../mongo/migrations/migration_38_test.go | 108 -- api/store/mongo/migrations/migration_39.go | 45 - .../mongo/migrations/migration_39_test.go | 25 - .../mongo/migrations/migration_3_test.go | 42 - api/store/mongo/migrations/migration_4.go | 32 - api/store/mongo/migrations/migration_40.go | 58 - .../mongo/migrations/migration_40_test.go | 94 -- api/store/mongo/migrations/migration_41.go | 58 - .../mongo/migrations/migration_41_test.go | 91 -- api/store/mongo/migrations/migration_42.go | 73 - .../mongo/migrations/migration_42_test.go | 130 -- api/store/mongo/migrations/migration_43.go | 73 - .../mongo/migrations/migration_43_test.go | 122 -- api/store/mongo/migrations/migration_44.go | 65 - .../mongo/migrations/migration_44_test.go | 228 --- api/store/mongo/migrations/migration_45.go | 65 - .../mongo/migrations/migration_45_test.go | 159 -- api/store/mongo/migrations/migration_46.go | 67 - .../mongo/migrations/migration_46_test.go | 121 -- api/store/mongo/migrations/migration_47.go | 84 - .../mongo/migrations/migration_47_test.go | 85 - api/store/mongo/migrations/migration_48.go | 97 -- .../mongo/migrations/migration_48_test.go | 132 -- api/store/mongo/migrations/migration_49.go | 89 -- .../mongo/migrations/migration_49_test.go | 138 -- .../mongo/migrations/migration_4_test.go | 53 - api/store/mongo/migrations/migration_5.go | 40 - api/store/mongo/migrations/migration_50.go | 112 -- .../mongo/migrations/migration_50_test.go | 212 --- api/store/mongo/migrations/migration_51.go | 51 - .../mongo/migrations/migration_51_test.go | 108 -- api/store/mongo/migrations/migration_52.go | 67 - .../mongo/migrations/migration_52_test.go | 72 - api/store/mongo/migrations/migration_53.go | 55 - api/store/mongo/migrations/migration_54.go | 54 - .../mongo/migrations/migration_54_test.go | 101 -- api/store/mongo/migrations/migration_55.go | 93 -- .../mongo/migrations/migration_55_test.go | 118 -- api/store/mongo/migrations/migration_56.go | 55 - .../mongo/migrations/migration_56_test.go | 101 -- api/store/mongo/migrations/migration_57.go | 157 -- .../mongo/migrations/migration_57_test.go | 183 --- api/store/mongo/migrations/migration_58.go | 95 -- api/store/mongo/migrations/migration_59.go | 72 - .../mongo/migrations/migration_59_test.go | 114 -- .../mongo/migrations/migration_5_test.go | 51 - api/store/mongo/migrations/migration_6.go | 46 - api/store/mongo/migrations/migration_60.go | 48 - .../mongo/migrations/migration_60_test.go | 106 -- api/store/mongo/migrations/migration_61.go | 34 - .../mongo/migrations/migration_61_test.go | 102 -- api/store/mongo/migrations/migration_62.go | 62 - .../mongo/migrations/migration_62_test.go | 126 -- api/store/mongo/migrations/migration_63.go | 57 - .../mongo/migrations/migration_63_test.go | 52 - api/store/mongo/migrations/migration_64.go | 61 - .../mongo/migrations/migration_64_test.go | 78 - api/store/mongo/migrations/migration_65.go | 61 - .../mongo/migrations/migration_65_test.go | 77 - api/store/mongo/migrations/migration_66.go | 67 - .../mongo/migrations/migration_66_test.go | 84 - api/store/mongo/migrations/migration_67.go | 91 -- .../mongo/migrations/migration_67_test.go | 82 - api/store/mongo/migrations/migration_68.go | 81 - .../mongo/migrations/migration_68_test.go | 136 -- api/store/mongo/migrations/migration_69.go | 106 -- .../mongo/migrations/migration_69_test.go | 76 - .../mongo/migrations/migration_6_test.go | 38 - api/store/mongo/migrations/migration_7.go | 57 - api/store/mongo/migrations/migration_70.go | 61 - .../mongo/migrations/migration_70_test.go | 117 -- api/store/mongo/migrations/migration_71.go | 63 - .../mongo/migrations/migration_71_test.go | 122 -- api/store/mongo/migrations/migration_72.go | 118 -- .../mongo/migrations/migration_72_test.go | 135 -- api/store/mongo/migrations/migration_73.go | 120 -- .../mongo/migrations/migration_73_test.go | 135 -- api/store/mongo/migrations/migration_74.go | 68 - .../mongo/migrations/migration_74_test.go | 167 -- api/store/mongo/migrations/migration_75.go | 126 -- .../mongo/migrations/migration_75_test.go | 162 -- api/store/mongo/migrations/migration_76.go | 87 -- .../mongo/migrations/migration_76_test.go | 151 -- api/store/mongo/migrations/migration_77.go | 43 - .../mongo/migrations/migration_77_test.go | 59 - api/store/mongo/migrations/migration_78.go | 43 - .../mongo/migrations/migration_78_test.go | 59 - api/store/mongo/migrations/migration_79.go | 49 - .../mongo/migrations/migration_79_test.go | 79 - .../mongo/migrations/migration_7_test.go | 40 - api/store/mongo/migrations/migration_8.go | 46 - api/store/mongo/migrations/migration_80.go | 41 - api/store/mongo/migrations/migration_81.go | 41 - api/store/mongo/migrations/migration_82.go | 68 - .../mongo/migrations/migration_82_test.go | 78 - api/store/mongo/migrations/migration_83.go | 58 - .../mongo/migrations/migration_83_test.go | 119 -- api/store/mongo/migrations/migration_84.go | 48 - .../mongo/migrations/migration_84_test.go | 99 -- api/store/mongo/migrations/migration_85.go | 48 - .../mongo/migrations/migration_85_test.go | 99 -- api/store/mongo/migrations/migration_86.go | 62 - .../mongo/migrations/migration_86_test.go | 122 -- api/store/mongo/migrations/migration_87.go | 71 - .../mongo/migrations/migration_87_test.go | 119 -- api/store/mongo/migrations/migration_88.go | 73 - .../mongo/migrations/migration_88_test.go | 136 -- api/store/mongo/migrations/migration_89.go | 61 - .../mongo/migrations/migration_89_test.go | 117 -- .../mongo/migrations/migration_8_test.go | 38 - api/store/mongo/migrations/migration_9.go | 52 - api/store/mongo/migrations/migration_90.go | 65 - .../mongo/migrations/migration_90_test.go | 120 -- api/store/mongo/migrations/migration_91.go | 119 -- .../mongo/migrations/migration_91_test.go | 277 ---- api/store/mongo/migrations/migration_92.go | 89 -- .../mongo/migrations/migration_92_test.go | 276 ---- api/store/mongo/migrations/migration_93.go | 35 - .../mongo/migrations/migration_93_test.go | 71 - api/store/mongo/migrations/migration_94.go | 89 -- .../mongo/migrations/migration_94_test.go | 112 -- api/store/mongo/migrations/migration_95.go | 468 ------ .../mongo/migrations/migration_95_test.go | 435 ------ api/store/mongo/migrations/migration_96.go | 49 - .../mongo/migrations/migration_96_test.go | 261 ---- api/store/mongo/migrations/migration_97.go | 123 -- api/store/mongo/migrations/migration_98.go | 46 - .../mongo/migrations/migration_98_test.go | 150 -- api/store/mongo/migrations/migration_99.go | 37 - .../mongo/migrations/migration_99_test.go | 102 -- .../mongo/migrations/migration_9_test.go | 35 - api/store/mongo/namespace.go | 394 ----- api/store/mongo/namespace_test.go | 820 ---------- api/store/mongo/options/options.go | 106 -- api/store/mongo/privatekey.go | 23 - api/store/mongo/privatekey_test.go | 96 -- api/store/mongo/publickey.go | 100 -- api/store/mongo/publickey_tags.go | 63 - api/store/mongo/publickey_tags_test.go | 363 ----- api/store/mongo/publickey_test.go | 320 ---- api/store/mongo/queries/builder.go | 102 -- api/store/mongo/queries/builder_test.go | 324 ---- api/store/mongo/queries/internal/filters.go | 106 -- api/store/mongo/query-options.go | 61 - api/store/mongo/query-options_test.go | 125 -- api/store/mongo/session.go | 357 ----- api/store/mongo/session_test.go | 521 ------- api/store/mongo/stats.go | 141 -- api/store/mongo/stats_test.go | 57 - api/store/mongo/store.go | 71 - api/store/mongo/store_test.go | 97 -- api/store/mongo/system.go | 62 - api/store/mongo/tags.go | 132 -- api/store/mongo/tags_test.go | 143 -- api/store/mongo/transaction.go | 27 - api/store/mongo/transaction_test.go | 65 - api/store/mongo/user.go | 297 ---- api/store/mongo/user_test.go | 729 --------- api/store/mongo/utils.go | 103 -- 242 files changed, 25740 deletions(-) delete mode 100644 api/store/mongo/api-key.go delete mode 100644 api/store/mongo/api-key_test.go delete mode 100644 api/store/mongo/device.go delete mode 100644 api/store/mongo/device_tags.go delete mode 100644 api/store/mongo/device_tags_test.go delete mode 100644 api/store/mongo/device_test.go delete mode 100644 api/store/mongo/fixtures/active_sessions.json delete mode 100644 api/store/mongo/fixtures/api-key.json delete mode 100644 api/store/mongo/fixtures/devices.json delete mode 100644 api/store/mongo/fixtures/firewall_rules.json delete mode 100644 api/store/mongo/fixtures/namespaces.json delete mode 100644 api/store/mongo/fixtures/private_keys.json delete mode 100644 api/store/mongo/fixtures/public_keys.json delete mode 100644 api/store/mongo/fixtures/recorded_sessions.json delete mode 100644 api/store/mongo/fixtures/recovery_tokens.json delete mode 100644 api/store/mongo/fixtures/sessions.json delete mode 100644 api/store/mongo/fixtures/users.json delete mode 100644 api/store/mongo/migrations/main.go delete mode 100644 api/store/mongo/migrations/main_test.go delete mode 100644 api/store/mongo/migrations/migration_1.go delete mode 100644 api/store/mongo/migrations/migration_10.go delete mode 100644 api/store/mongo/migrations/migration_10_test.go delete mode 100644 api/store/mongo/migrations/migration_11.go delete mode 100644 api/store/mongo/migrations/migration_11_test.go delete mode 100644 api/store/mongo/migrations/migration_12.go delete mode 100644 api/store/mongo/migrations/migration_12_test.go delete mode 100644 api/store/mongo/migrations/migration_13.go delete mode 100644 api/store/mongo/migrations/migration_13_test.go delete mode 100644 api/store/mongo/migrations/migration_14.go delete mode 100644 api/store/mongo/migrations/migration_14_test.go delete mode 100644 api/store/mongo/migrations/migration_15.go delete mode 100644 api/store/mongo/migrations/migration_15_test.go delete mode 100644 api/store/mongo/migrations/migration_16.go delete mode 100644 api/store/mongo/migrations/migration_16_test.go delete mode 100644 api/store/mongo/migrations/migration_17.go delete mode 100644 api/store/mongo/migrations/migration_17_test.go delete mode 100644 api/store/mongo/migrations/migration_18.go delete mode 100644 api/store/mongo/migrations/migration_18_test.go delete mode 100644 api/store/mongo/migrations/migration_19.go delete mode 100644 api/store/mongo/migrations/migration_19_test.go delete mode 100644 api/store/mongo/migrations/migration_1_test.go delete mode 100644 api/store/mongo/migrations/migration_2.go delete mode 100644 api/store/mongo/migrations/migration_20.go delete mode 100644 api/store/mongo/migrations/migration_20_test.go delete mode 100644 api/store/mongo/migrations/migration_21.go delete mode 100644 api/store/mongo/migrations/migration_21_test.go delete mode 100644 api/store/mongo/migrations/migration_22.go delete mode 100644 api/store/mongo/migrations/migration_22_test.go delete mode 100644 api/store/mongo/migrations/migration_23.go delete mode 100644 api/store/mongo/migrations/migration_23_test.go delete mode 100644 api/store/mongo/migrations/migration_24.go delete mode 100644 api/store/mongo/migrations/migration_24_test.go delete mode 100644 api/store/mongo/migrations/migration_25.go delete mode 100644 api/store/mongo/migrations/migration_25_test.go delete mode 100644 api/store/mongo/migrations/migration_26.go delete mode 100644 api/store/mongo/migrations/migration_26_test.go delete mode 100644 api/store/mongo/migrations/migration_27.go delete mode 100644 api/store/mongo/migrations/migration_27_test.go delete mode 100644 api/store/mongo/migrations/migration_28.go delete mode 100644 api/store/mongo/migrations/migration_28_test.go delete mode 100644 api/store/mongo/migrations/migration_29.go delete mode 100644 api/store/mongo/migrations/migration_29_test.go delete mode 100644 api/store/mongo/migrations/migration_2_test.go delete mode 100644 api/store/mongo/migrations/migration_3.go delete mode 100644 api/store/mongo/migrations/migration_30.go delete mode 100644 api/store/mongo/migrations/migration_30_test.go delete mode 100644 api/store/mongo/migrations/migration_31.go delete mode 100644 api/store/mongo/migrations/migration_31_test.go delete mode 100644 api/store/mongo/migrations/migration_32.go delete mode 100644 api/store/mongo/migrations/migration_32_test.go delete mode 100644 api/store/mongo/migrations/migration_33.go delete mode 100644 api/store/mongo/migrations/migration_33_test.go delete mode 100644 api/store/mongo/migrations/migration_34.go delete mode 100644 api/store/mongo/migrations/migration_34_test.go delete mode 100644 api/store/mongo/migrations/migration_35.go delete mode 100644 api/store/mongo/migrations/migration_35_test.go delete mode 100644 api/store/mongo/migrations/migration_36.go delete mode 100644 api/store/mongo/migrations/migration_36_test.go delete mode 100644 api/store/mongo/migrations/migration_37.go delete mode 100644 api/store/mongo/migrations/migration_37_test.go delete mode 100644 api/store/mongo/migrations/migration_38.go delete mode 100644 api/store/mongo/migrations/migration_38_test.go delete mode 100644 api/store/mongo/migrations/migration_39.go delete mode 100644 api/store/mongo/migrations/migration_39_test.go delete mode 100644 api/store/mongo/migrations/migration_3_test.go delete mode 100644 api/store/mongo/migrations/migration_4.go delete mode 100644 api/store/mongo/migrations/migration_40.go delete mode 100644 api/store/mongo/migrations/migration_40_test.go delete mode 100644 api/store/mongo/migrations/migration_41.go delete mode 100644 api/store/mongo/migrations/migration_41_test.go delete mode 100644 api/store/mongo/migrations/migration_42.go delete mode 100644 api/store/mongo/migrations/migration_42_test.go delete mode 100644 api/store/mongo/migrations/migration_43.go delete mode 100644 api/store/mongo/migrations/migration_43_test.go delete mode 100644 api/store/mongo/migrations/migration_44.go delete mode 100644 api/store/mongo/migrations/migration_44_test.go delete mode 100644 api/store/mongo/migrations/migration_45.go delete mode 100644 api/store/mongo/migrations/migration_45_test.go delete mode 100644 api/store/mongo/migrations/migration_46.go delete mode 100644 api/store/mongo/migrations/migration_46_test.go delete mode 100644 api/store/mongo/migrations/migration_47.go delete mode 100644 api/store/mongo/migrations/migration_47_test.go delete mode 100644 api/store/mongo/migrations/migration_48.go delete mode 100644 api/store/mongo/migrations/migration_48_test.go delete mode 100644 api/store/mongo/migrations/migration_49.go delete mode 100644 api/store/mongo/migrations/migration_49_test.go delete mode 100644 api/store/mongo/migrations/migration_4_test.go delete mode 100644 api/store/mongo/migrations/migration_5.go delete mode 100644 api/store/mongo/migrations/migration_50.go delete mode 100644 api/store/mongo/migrations/migration_50_test.go delete mode 100644 api/store/mongo/migrations/migration_51.go delete mode 100644 api/store/mongo/migrations/migration_51_test.go delete mode 100644 api/store/mongo/migrations/migration_52.go delete mode 100644 api/store/mongo/migrations/migration_52_test.go delete mode 100644 api/store/mongo/migrations/migration_53.go delete mode 100644 api/store/mongo/migrations/migration_54.go delete mode 100644 api/store/mongo/migrations/migration_54_test.go delete mode 100644 api/store/mongo/migrations/migration_55.go delete mode 100644 api/store/mongo/migrations/migration_55_test.go delete mode 100644 api/store/mongo/migrations/migration_56.go delete mode 100644 api/store/mongo/migrations/migration_56_test.go delete mode 100644 api/store/mongo/migrations/migration_57.go delete mode 100644 api/store/mongo/migrations/migration_57_test.go delete mode 100644 api/store/mongo/migrations/migration_58.go delete mode 100644 api/store/mongo/migrations/migration_59.go delete mode 100644 api/store/mongo/migrations/migration_59_test.go delete mode 100644 api/store/mongo/migrations/migration_5_test.go delete mode 100644 api/store/mongo/migrations/migration_6.go delete mode 100644 api/store/mongo/migrations/migration_60.go delete mode 100644 api/store/mongo/migrations/migration_60_test.go delete mode 100644 api/store/mongo/migrations/migration_61.go delete mode 100644 api/store/mongo/migrations/migration_61_test.go delete mode 100644 api/store/mongo/migrations/migration_62.go delete mode 100644 api/store/mongo/migrations/migration_62_test.go delete mode 100644 api/store/mongo/migrations/migration_63.go delete mode 100644 api/store/mongo/migrations/migration_63_test.go delete mode 100644 api/store/mongo/migrations/migration_64.go delete mode 100644 api/store/mongo/migrations/migration_64_test.go delete mode 100644 api/store/mongo/migrations/migration_65.go delete mode 100644 api/store/mongo/migrations/migration_65_test.go delete mode 100644 api/store/mongo/migrations/migration_66.go delete mode 100644 api/store/mongo/migrations/migration_66_test.go delete mode 100644 api/store/mongo/migrations/migration_67.go delete mode 100644 api/store/mongo/migrations/migration_67_test.go delete mode 100644 api/store/mongo/migrations/migration_68.go delete mode 100644 api/store/mongo/migrations/migration_68_test.go delete mode 100644 api/store/mongo/migrations/migration_69.go delete mode 100644 api/store/mongo/migrations/migration_69_test.go delete mode 100644 api/store/mongo/migrations/migration_6_test.go delete mode 100644 api/store/mongo/migrations/migration_7.go delete mode 100644 api/store/mongo/migrations/migration_70.go delete mode 100644 api/store/mongo/migrations/migration_70_test.go delete mode 100644 api/store/mongo/migrations/migration_71.go delete mode 100644 api/store/mongo/migrations/migration_71_test.go delete mode 100644 api/store/mongo/migrations/migration_72.go delete mode 100644 api/store/mongo/migrations/migration_72_test.go delete mode 100644 api/store/mongo/migrations/migration_73.go delete mode 100644 api/store/mongo/migrations/migration_73_test.go delete mode 100644 api/store/mongo/migrations/migration_74.go delete mode 100644 api/store/mongo/migrations/migration_74_test.go delete mode 100644 api/store/mongo/migrations/migration_75.go delete mode 100644 api/store/mongo/migrations/migration_75_test.go delete mode 100644 api/store/mongo/migrations/migration_76.go delete mode 100644 api/store/mongo/migrations/migration_76_test.go delete mode 100644 api/store/mongo/migrations/migration_77.go delete mode 100644 api/store/mongo/migrations/migration_77_test.go delete mode 100644 api/store/mongo/migrations/migration_78.go delete mode 100644 api/store/mongo/migrations/migration_78_test.go delete mode 100644 api/store/mongo/migrations/migration_79.go delete mode 100644 api/store/mongo/migrations/migration_79_test.go delete mode 100644 api/store/mongo/migrations/migration_7_test.go delete mode 100644 api/store/mongo/migrations/migration_8.go delete mode 100644 api/store/mongo/migrations/migration_80.go delete mode 100644 api/store/mongo/migrations/migration_81.go delete mode 100644 api/store/mongo/migrations/migration_82.go delete mode 100644 api/store/mongo/migrations/migration_82_test.go delete mode 100644 api/store/mongo/migrations/migration_83.go delete mode 100644 api/store/mongo/migrations/migration_83_test.go delete mode 100644 api/store/mongo/migrations/migration_84.go delete mode 100644 api/store/mongo/migrations/migration_84_test.go delete mode 100644 api/store/mongo/migrations/migration_85.go delete mode 100644 api/store/mongo/migrations/migration_85_test.go delete mode 100644 api/store/mongo/migrations/migration_86.go delete mode 100644 api/store/mongo/migrations/migration_86_test.go delete mode 100644 api/store/mongo/migrations/migration_87.go delete mode 100644 api/store/mongo/migrations/migration_87_test.go delete mode 100644 api/store/mongo/migrations/migration_88.go delete mode 100644 api/store/mongo/migrations/migration_88_test.go delete mode 100644 api/store/mongo/migrations/migration_89.go delete mode 100644 api/store/mongo/migrations/migration_89_test.go delete mode 100644 api/store/mongo/migrations/migration_8_test.go delete mode 100644 api/store/mongo/migrations/migration_9.go delete mode 100644 api/store/mongo/migrations/migration_90.go delete mode 100644 api/store/mongo/migrations/migration_90_test.go delete mode 100644 api/store/mongo/migrations/migration_91.go delete mode 100644 api/store/mongo/migrations/migration_91_test.go delete mode 100644 api/store/mongo/migrations/migration_92.go delete mode 100644 api/store/mongo/migrations/migration_92_test.go delete mode 100644 api/store/mongo/migrations/migration_93.go delete mode 100644 api/store/mongo/migrations/migration_93_test.go delete mode 100644 api/store/mongo/migrations/migration_94.go delete mode 100644 api/store/mongo/migrations/migration_94_test.go delete mode 100644 api/store/mongo/migrations/migration_95.go delete mode 100644 api/store/mongo/migrations/migration_95_test.go delete mode 100644 api/store/mongo/migrations/migration_96.go delete mode 100644 api/store/mongo/migrations/migration_96_test.go delete mode 100644 api/store/mongo/migrations/migration_97.go delete mode 100644 api/store/mongo/migrations/migration_98.go delete mode 100644 api/store/mongo/migrations/migration_98_test.go delete mode 100644 api/store/mongo/migrations/migration_99.go delete mode 100644 api/store/mongo/migrations/migration_99_test.go delete mode 100644 api/store/mongo/migrations/migration_9_test.go delete mode 100644 api/store/mongo/namespace.go delete mode 100644 api/store/mongo/namespace_test.go delete mode 100644 api/store/mongo/options/options.go delete mode 100644 api/store/mongo/privatekey.go delete mode 100644 api/store/mongo/privatekey_test.go delete mode 100644 api/store/mongo/publickey.go delete mode 100644 api/store/mongo/publickey_tags.go delete mode 100644 api/store/mongo/publickey_tags_test.go delete mode 100644 api/store/mongo/publickey_test.go delete mode 100644 api/store/mongo/queries/builder.go delete mode 100644 api/store/mongo/queries/builder_test.go delete mode 100644 api/store/mongo/queries/internal/filters.go delete mode 100644 api/store/mongo/query-options.go delete mode 100644 api/store/mongo/query-options_test.go delete mode 100644 api/store/mongo/session.go delete mode 100644 api/store/mongo/session_test.go delete mode 100644 api/store/mongo/stats.go delete mode 100644 api/store/mongo/stats_test.go delete mode 100644 api/store/mongo/store.go delete mode 100644 api/store/mongo/store_test.go delete mode 100644 api/store/mongo/system.go delete mode 100644 api/store/mongo/tags.go delete mode 100644 api/store/mongo/tags_test.go delete mode 100644 api/store/mongo/transaction.go delete mode 100644 api/store/mongo/transaction_test.go delete mode 100644 api/store/mongo/user.go delete mode 100644 api/store/mongo/user_test.go delete mode 100644 api/store/mongo/utils.go diff --git a/api/pkg/echo/handlers/errors.go b/api/pkg/echo/handlers/errors.go index 8872c09903c..5477848d7c8 100644 --- a/api/pkg/echo/handlers/errors.go +++ b/api/pkg/echo/handlers/errors.go @@ -10,7 +10,6 @@ import ( routes "github.com/shellhub-io/shellhub/api/routes/errors" "github.com/shellhub-io/shellhub/api/services" "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo" "github.com/shellhub-io/shellhub/pkg/errors" ) @@ -36,15 +35,6 @@ func NewErrors(reporter *sentry.Client) func(error, echo.Context) { // happens in each case, avoiding the use of else statements, which would make the code more confusing or a big // switch statement, which would make the code less readable. - // Every Mongo error that isn't mapped as a store error must be reported to Sentry and responded with HTTP - // status code 500. - if errors.Is(err, mongo.ErrMongo) { - report(reporter, err, ctx.Request()) - ctx.NoContent(http.StatusInternalServerError) //nolint:errcheck - - return - } - // On HTTP errors, anything related to the HTTP protocol, we just return the error code, avoiding a 500 error. var herr *echo.HTTPError if ok := errors.As(err, &herr); ok { diff --git a/api/services/member.go b/api/services/member.go index 3844fff88ae..8059fa38040 100644 --- a/api/services/member.go +++ b/api/services/member.go @@ -7,7 +7,6 @@ import ( "time" "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo" "github.com/shellhub-io/shellhub/pkg/api/authorizer" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/clock" @@ -276,8 +275,6 @@ func (s *service) removeMember(ctx context.Context, ns *models.Namespace, userID switch { case errors.Is(err, store.ErrNoDocuments): return NewErrNamespaceNotFound(ns.TenantID, err) - case errors.Is(err, mongo.ErrUserNotFound): - return NewErrNamespaceMemberNotFound(userID, err) default: return err } diff --git a/api/store/mongo/api-key.go b/api/store/mongo/api-key.go deleted file mode 100644 index b49bc40c459..00000000000 --- a/api/store/mongo/api-key.go +++ /dev/null @@ -1,154 +0,0 @@ -package mongo - -import ( - "context" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo/queries" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - "go.mongodb.org/mongo-driver/bson" -) - -func (s *Store) APIKeyCreate(ctx context.Context, apiKey *models.APIKey) (string, error) { - now := clock.Now() - apiKey.CreatedAt = now - apiKey.UpdatedAt = now - - res, err := s.db.Collection("api_keys").InsertOne(ctx, apiKey) - if err != nil { - return "", FromMongoError(err) - } - - return res.InsertedID.(string), nil -} - -func (s *Store) APIKeyGet(ctx context.Context, id string) (*models.APIKey, error) { - apiKey := new(models.APIKey) - if err := s.db.Collection("api_keys").FindOne(ctx, bson.M{"_id": id}).Decode(apiKey); err != nil { - return nil, FromMongoError(err) - } - - return apiKey, nil -} - -func (s *Store) APIKeyGetByName(ctx context.Context, tenantID string, name string) (*models.APIKey, error) { - apiKey := new(models.APIKey) - if err := s.db.Collection("api_keys").FindOne(ctx, bson.M{"tenant_id": tenantID, "name": name}).Decode(&apiKey); err != nil { - return nil, FromMongoError(err) - } - - return apiKey, nil -} - -func (s *Store) APIKeyConflicts(ctx context.Context, tenantID string, target *models.APIKeyConflicts) ([]string, bool, error) { - pipeline := []bson.M{ - { - "$match": bson.M{ - "tenant_id": tenantID, - "$or": []bson.M{ - {"_id": target.ID}, - {"name": target.Name}, - }, - }, - }, - } - - cursor, err := s.db.Collection("api_keys").Aggregate(ctx, pipeline) - if err != nil { - return nil, false, FromMongoError(err) - } - defer cursor.Close(ctx) - - apiKey := new(models.APIKeyConflicts) - conflicts := make([]string, 0) - for cursor.Next(ctx) { - if err := cursor.Decode(&apiKey); err != nil { - return nil, false, FromMongoError(err) - } - - if apiKey.ID == target.ID { - conflicts = append(conflicts, "id") - } - - if apiKey.Name == target.Name { - conflicts = append(conflicts, "name") - } - } - - return conflicts, len(conflicts) > 0, nil -} - -func (s *Store) APIKeyList(ctx context.Context, tenantID string, paginator query.Paginator, sorter query.Sorter) ([]models.APIKey, int, error) { - query := []bson.M{ - { - "$match": bson.M{ - "tenant_id": tenantID, - }, - }, - } - - queryCount := append(query, bson.M{"$count": "count"}) - count, err := AggregateCount(ctx, s.db.Collection("api_keys"), queryCount) - if err != nil { - return nil, 0, FromMongoError(err) - } - - if count == 0 { - return []models.APIKey{}, 0, nil - } - - query = append(query, queries.FromSorter(&sorter)...) - query = append(query, queries.FromPaginator(&paginator)...) - - cursor, err := s.db.Collection("api_keys").Aggregate(ctx, query) - if err != nil { - return nil, 0, FromMongoError(err) - } - defer cursor.Close(ctx) - - apiKeys := make([]models.APIKey, 0) - for cursor.Next(ctx) { - apiKey := new(models.APIKey) - if err := cursor.Decode(apiKey); err != nil { - return nil, 0, FromMongoError(err) - } - - apiKeys = append(apiKeys, *apiKey) - } - - return apiKeys, count, nil -} - -func (s *Store) APIKeyUpdate(ctx context.Context, tenantID, name string, changes *models.APIKeyChanges) error { - changes.UpdatedAt = clock.Now() - - res, err := s.db. - Collection("api_keys"). - UpdateOne(ctx, bson.M{"tenant_id": tenantID, "name": name}, bson.M{"$set": changes}) - if err != nil { - return FromMongoError(err) - } - - if res.ModifiedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) APIKeyDelete(ctx context.Context, tenantID, name string) error { - result, err := s.db. - Collection("api_keys"). - DeleteOne(ctx, bson.M{"tenant_id": tenantID, "name": name}) - if err != nil { - return FromMongoError(err) - } - - if result.DeletedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} diff --git a/api/store/mongo/api-key_test.go b/api/store/mongo/api-key_test.go deleted file mode 100644 index 2995265d797..00000000000 --- a/api/store/mongo/api-key_test.go +++ /dev/null @@ -1,528 +0,0 @@ -package mongo_test - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" -) - -func TestAPIKeyCreate(t *testing.T) { - type Expected struct { - insertedID string - err error - } - - cases := []struct { - description string - apiKey *models.APIKey - expected Expected - }{ - { - description: "succeeds", - apiKey: &models.APIKey{ - ID: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a", - Name: "dev", - CreatedBy: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Role: "admin", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - ExpiresIn: 0, - }, - expected: Expected{ - insertedID: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a", - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - insertedID, err := s.APIKeyCreate(ctx, tc.apiKey) - require.Equal(t, tc.expected, Expected{insertedID, err}) - }) - } -} - -func TestAPIKeyGet(t *testing.T) { - type Expected struct { - apiKey *models.APIKey - err error - } - - cases := []struct { - description string - id string - fixtures []string - expected Expected - }{ - { - description: "fails when name and tenant id does not exists", - id: "nonexistent", - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds", - id: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a", - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKey: &models.APIKey{ - ID: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a", - Name: "dev", - CreatedBy: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Role: "admin", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - ExpiresIn: 0, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - apiKey, err := s.APIKeyGet(ctx, tc.id) - require.Equal(t, tc.expected, Expected{apiKey, err}) - }) - } -} - -func TestAPIKeyGetByName(t *testing.T) { - type Expected struct { - apiKey *models.APIKey - err error - } - - cases := []struct { - description string - name string - tenantID string - fixtures []string - expected Expected - }{ - { - description: "fails when name and tenant id does not exists", - tenantID: "nonexistent", - name: "nonexistent", - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when name is valid but tenant id not", - tenantID: "nonexistent", - name: "dev", - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when tenant id is valid but name not", - tenantID: "00000000-0000-4000-0000-000000000000", - name: "nonexistent", - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds", - tenantID: "00000000-0000-4000-0000-000000000000", - name: "dev", - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKey: &models.APIKey{ - ID: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a", - Name: "dev", - CreatedBy: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Role: "admin", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - ExpiresIn: 0, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - apiKey, err := s.APIKeyGetByName(ctx, tc.tenantID, tc.name) - require.Equal(t, tc.expected, Expected{apiKey, err}) - }) - } -} - -func TestAPIKeyConflicts(t *testing.T) { - type Expected struct { - conflicts []string - ok bool - err error - } - - cases := []struct { - description string - tenantID string - target *models.APIKeyConflicts - fixtures []string - expected Expected - }{ - { - description: "no conflicts when target is empty", - tenantID: "00000000-0000-4000-0000-000000000000", - target: &models.APIKeyConflicts{}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "no conflicts with non existing name", - tenantID: "00000000-0000-4000-0000-000000000000", - target: &models.APIKeyConflicts{Name: "nonexistent"}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "no conflict detected with existing attribute but different tenant id", - tenantID: "nonexistent", - target: &models.APIKeyConflicts{Name: "dev"}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "conflict detected with existing name", - tenantID: "00000000-0000-4000-0000-000000000000", - target: &models.APIKeyConflicts{Name: "dev"}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{[]string{"name"}, true, nil}, - }, - { - description: "conflict detected with existing id", - tenantID: "00000000-0000-4000-0000-000000000000", - target: &models.APIKeyConflicts{ID: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a"}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{[]string{"id"}, true, nil}, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - conflicts, ok, err := s.APIKeyConflicts(ctx, tc.tenantID, tc.target) - require.Equal(t, tc.expected, Expected{conflicts, ok, err}) - }) - } -} - -func TestAPIKeyList(t *testing.T) { - type Expected struct { - apiKeys []models.APIKey - count int - err error - } - - cases := []struct { - description string - tenantID string - paginator query.Paginator - sorter query.Sorter - fixtures []string - expected Expected - }{ - { - description: "succeeds when there are no api keys", - tenantID: "nonexistent", - paginator: query.Paginator{Page: 1, PerPage: 10}, - sorter: query.Sorter{By: "expires_in", Order: query.OrderAsc}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKeys: []models.APIKey{}, - count: 0, - err: nil, - }, - }, - { - description: "succeeds when there are api keys", - tenantID: "00000000-0000-4000-0000-000000000000", - paginator: query.Paginator{Page: 1, PerPage: 10}, - sorter: query.Sorter{By: "expires_in", Order: query.OrderAsc}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKeys: []models.APIKey{ - { - ID: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a", - Name: "dev", - CreatedBy: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Role: "admin", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - ExpiresIn: 0, - }, - { - ID: "a1b2c73ea41f70870c035283336d72228118213ed03ec78043ffee48d827af11", - Name: "prod", - CreatedBy: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Role: "operator", - CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - ExpiresIn: 10, - }, - }, - count: 2, - err: nil, - }, - }, - { - description: "succeeds when there are api keys and pagination", - tenantID: "00000000-0000-4000-0000-000000000000", - paginator: query.Paginator{Page: 1, PerPage: 1}, - sorter: query.Sorter{By: "expires_in", Order: query.OrderAsc}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKeys: []models.APIKey{ - { - ID: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a", - Name: "dev", - CreatedBy: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Role: "admin", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - ExpiresIn: 0, - }, - }, - count: 2, - err: nil, - }, - }, - { - description: "succeeds when there are api keys and sorter", - tenantID: "00000000-0000-4000-0000-000000000000", - paginator: query.Paginator{Page: 1, PerPage: 10}, - sorter: query.Sorter{By: "expires_in", Order: query.OrderDesc}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - apiKeys: []models.APIKey{ - { - ID: "a1b2c73ea41f70870c035283336d72228118213ed03ec78043ffee48d827af11", - Name: "prod", - CreatedBy: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Role: "operator", - CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - ExpiresIn: 10, - }, - { - ID: "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a", - Name: "dev", - CreatedBy: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Role: "admin", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - ExpiresIn: 0, - }, - }, - count: 2, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - apiKeys, count, err := s.APIKeyList(ctx, tc.tenantID, tc.paginator, tc.sorter) - require.Equal(t, tc.expected, Expected{apiKeys, count, err}) - }) - } -} - -func TestAPIKeyUpdate(t *testing.T) { - type Expected struct { - name string - err error - } - - cases := []struct { - description string - tenantID string - name string - changes *models.APIKeyChanges - fixtures []string - expected Expected - }{ - { - description: "fails when name and tenant id does not exists", - tenantID: "nonexistent", - name: "nonexistent", - changes: &models.APIKeyChanges{}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - name: "", - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when name is valid but tenant id not", - tenantID: "nonexistent", - name: "dev", - changes: &models.APIKeyChanges{}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - name: "", - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when tenant id is valid but name not", - tenantID: "00000000-0000-4000-0000-000000000000", - name: "nonexistent", - changes: &models.APIKeyChanges{}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - name: "", - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when changes is empty", - tenantID: "00000000-0000-4000-0000-000000000000", - name: "dev", - changes: &models.APIKeyChanges{}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - name: "dev", - err: nil, - }, - }, - { - description: "succeeds when changes is not empty", - tenantID: "00000000-0000-4000-0000-000000000000", - name: "dev", - changes: &models.APIKeyChanges{Name: "new"}, - fixtures: []string{fixtureAPIKeys}, - expected: Expected{ - name: "new", - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - err := s.APIKeyUpdate(ctx, tc.tenantID, tc.name, tc.changes) - if tc.expected.err != nil { - require.Equal(t, tc.expected.err, err) - - return - } - - filter := bson.M{"tenant_id": tc.tenantID} - if tc.expected.name != "" { - filter = bson.M{"name": tc.expected.name} - } - - apiKey := new(models.APIKey) - require.NoError(t, db.Collection("api_keys").FindOne(ctx, filter).Decode(apiKey)) - require.Equal(t, tc.expected.name, apiKey.Name) - require.WithinDuration(t, time.Now(), apiKey.UpdatedAt, 10*time.Second) - }) - } -} - -func TestDeleteAPIKey(t *testing.T) { - cases := []struct { - description string - tenantID string - name string - fixtures []string - expected error - }{ - { - description: "fails when name and tenant id does not exists", - tenantID: "nonexistent", - name: "nonexistent", - fixtures: []string{fixtureAPIKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "fails when name is valid but tenant id not", - tenantID: "nonexistent", - name: "dev", - fixtures: []string{fixtureAPIKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "fails when tenant id is valid but name not", - tenantID: "00000000-0000-4000-0000-000000000000", - name: "nonexistent", - fixtures: []string{fixtureAPIKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds", - tenantID: "00000000-0000-4000-0000-000000000000", - name: "dev", - fixtures: []string{fixtureAPIKeys}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - err := s.APIKeyDelete(ctx, tc.tenantID, tc.name) - require.Equal(t, tc.expected, err) - }) - } -} diff --git a/api/store/mongo/device.go b/api/store/mongo/device.go deleted file mode 100644 index 60af730a8ca..00000000000 --- a/api/store/mongo/device.go +++ /dev/null @@ -1,625 +0,0 @@ -package mongo - -import ( - "context" //nolint:gosec - "strings" - "time" - - "github.com/shellhub-io/shellhub/api/pkg/gateway" - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo/queries" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// DeviceList returns a list of devices based on the given filters, pagination and sorting. -func (s *Store) DeviceList(ctx context.Context, status models.DeviceStatus, paginator query.Paginator, filters query.Filters, sorter query.Sorter, acceptable store.DeviceAcceptable) ([]models.Device, int, error) { - query := []bson.M{ - { - "$match": bson.M{ - "uid": bson.M{ - "$ne": nil, - }, - }, - }, - { - "$addFields": bson.M{ - "online": bson.M{ - "$cond": bson.M{ - "if": bson.M{ - "$and": bson.A{ - bson.M{"$eq": bson.A{"$disconnected_at", nil}}, - bson.M{"$gt": bson.A{"$last_seen", primitive.NewDateTimeFromTime(time.Now().Add(-2 * time.Minute))}}, - }, - }, - "then": true, - "else": false, - }, - }, - }, - }, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append(query, bson.M{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }) - } - - if status != "" { - query = append([]bson.M{{ - "$match": bson.M{ - "status": status, - }, - }}, query...) - } - - // When the listing mode is [store.DeviceListModeMaxDeviceReached], we should evaluate the `removed_devices` - // collection to check its `accetable` status. - switch acceptable { - case store.DeviceAcceptableFromRemoved: - query = append(query, []bson.M{ - { - "$lookup": bson.M{ - "from": "removed_devices", - "localField": "uid", - "foreignField": "device.uid", - "as": "removed", - }, - }, - { - "$addFields": bson.M{ - "acceptable": bson.M{ - "$cond": bson.M{ - "if": bson.M{ - "$and": bson.A{ - bson.M{"$ne": bson.A{"$status", models.DeviceStatusAccepted}}, - bson.M{"$anyElementTrue": []interface{}{"$removed"}}, - }, - }, - "then": true, - "else": false, - }, - }, - }, - }, - { - "$unset": "removed", - }, - }...) - case store.DeviceAcceptableAsFalse: - query = append(query, bson.M{ - "$addFields": bson.M{ - "acceptable": false, - }, - }) - case store.DeviceAcceptableIfNotAccepted: - query = append(query, bson.M{ - "$addFields": bson.M{ - "acceptable": bson.M{ - "$cond": bson.M{ - "if": bson.M{"$ne": bson.A{"$status", models.DeviceStatusAccepted}}, - "then": true, - "else": false, - }, - }, - }, - }) - } - - queryMatch, err := queries.FromFilters(&filters) - if err != nil { - return nil, 0, FromMongoError(err) - } - query = append(query, queryMatch...) - - queryCount := query - queryCount = append(queryCount, bson.M{"$count": "count"}) - count, err := AggregateCount(ctx, s.db.Collection("devices"), queryCount) - if err != nil { - return nil, 0, FromMongoError(err) - } - - if sorter.By == "" { - sorter.By = "last_seen" - } - - query = append(query, queries.FromSorter(&sorter)...) - query = append(query, queries.FromPaginator(&paginator)...) - - query = append(query, []bson.M{ - { - "$lookup": bson.M{ - "from": "namespaces", - "localField": "tenant_id", - "foreignField": "tenant_id", - "as": "namespace", - }, - }, - { - "$addFields": bson.M{ - "namespace": "$namespace.name", - }, - }, - { - "$unwind": "$namespace", - }, - }...) - - devices := make([]models.Device, 0) - - cursor, err := s.db.Collection("devices").Aggregate(ctx, query) - if err != nil { - return devices, count, FromMongoError(err) - } - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - device := new(models.Device) - - if err = cursor.Decode(&device); err != nil { - return devices, count, err - } - - devices = append(devices, *device) - } - - return devices, count, FromMongoError(err) -} - -func (s *Store) DeviceGet(ctx context.Context, uid models.UID) (*models.Device, error) { - query := []bson.M{ - { - "$match": bson.M{"uid": uid}, - }, - { - "$addFields": bson.M{ - "online": bson.M{ - "$cond": bson.M{ - "if": bson.M{ - "$and": bson.A{ - bson.M{"$eq": bson.A{"$disconnected_at", nil}}, - bson.M{"$gt": bson.A{"$last_seen", primitive.NewDateTimeFromTime(time.Now().Add(-2 * time.Minute))}}, - }, - }, - "then": true, - "else": false, - }, - }, - }, - }, - { - "$lookup": bson.M{ - "from": "namespaces", - "localField": "tenant_id", - "foreignField": "tenant_id", - "as": "namespace", - }, - }, - { - "$addFields": bson.M{ - "namespace": "$namespace.name", - }, - }, - { - "$unwind": "$namespace", - }, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append(query, bson.M{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }) - } - - device := new(models.Device) - - cursor, err := s.db.Collection("devices").Aggregate(ctx, query) - if err != nil { - return nil, FromMongoError(err) - } - defer cursor.Close(ctx) - cursor.Next(ctx) - - err = cursor.Decode(&device) - if err != nil { - return nil, FromMongoError(err) - } - - return device, nil -} - -func (s *Store) DeviceDelete(ctx context.Context, uid models.UID) error { - mongoSession, err := s.db.Client().StartSession() - if err != nil { - return FromMongoError(err) - } - defer mongoSession.EndSession(ctx) - - _, err = mongoSession.WithTransaction(ctx, func(_ mongo.SessionContext) (interface{}, error) { - dev, err := s.db.Collection("devices").DeleteOne(ctx, bson.M{"uid": uid}) - if err != nil { - return nil, FromMongoError(err) - } - - if dev.DeletedCount < 1 { - return nil, store.ErrNoDocuments - } - - if err := s.cache.Delete(ctx, strings.Join([]string{"device", string(uid)}, "/")); err != nil { - logrus.Error(err) - } - - if _, err := s.db.Collection("sessions").DeleteMany(ctx, bson.M{"device_uid": uid}); err != nil { - return nil, FromMongoError(err) - } - - if _, err := s.db.Collection("tunnels").DeleteMany(ctx, bson.M{"device": uid}); err != nil { - return nil, FromMongoError(err) - } - - return nil, nil - }) - - return err -} - -func (s *Store) DeviceCreate(ctx context.Context, d models.Device, hostname string) error { - if hostname == "" { - hostname = strings.ReplaceAll(d.Identity.MAC, ":", "-") - } - - var dev *models.Device - if err := s.cache.Get(ctx, strings.Join([]string{"device", d.UID}, "/"), &dev); err != nil { - logrus.Error(err) - } - - q := bson.M{ - "$setOnInsert": bson.M{ - "name": hostname, - "status": "pending", - "status_updated_at": time.Now(), - "created_at": clock.Now(), - "tags": []string{}, - }, - "$set": d, - } - opts := options.Update().SetUpsert(true) - _, err := s.db.Collection("devices").UpdateOne(ctx, bson.M{"uid": d.UID}, q, opts) - - return FromMongoError(err) -} - -func (s *Store) DeviceRename(ctx context.Context, uid models.UID, hostname string) error { - dev, err := s.db.Collection("devices").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$set": bson.M{"name": hostname}}) - if err != nil { - return FromMongoError(err) - } - - if dev.MatchedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) DeviceLookup(ctx context.Context, namespace, hostname string) (*models.Device, error) { - ns := new(models.Namespace) - if err := s.db.Collection("namespaces").FindOne(ctx, bson.M{"name": namespace}).Decode(&ns); err != nil { - return nil, FromMongoError(err) - } - - device := new(models.Device) - if err := s.db.Collection("devices").FindOne(ctx, bson.M{"tenant_id": ns.TenantID, "name": hostname, "status": "accepted"}).Decode(&device); err != nil { - return nil, FromMongoError(err) - } - - return device, nil -} - -// DeviceUpdateStatus updates the status of a specific device in the devices collection -func (s *Store) DeviceUpdateStatus(ctx context.Context, uid models.UID, status models.DeviceStatus) error { - updateOptions := options.FindOneAndUpdate().SetReturnDocument(options.After) - result := s.db.Collection("devices", options.Collection()). - FindOneAndUpdate(ctx, bson.M{"uid": uid}, bson.M{"$set": bson.M{"status": status, "status_updated_at": clock.Now()}}, updateOptions) - - if result.Err() != nil { - return FromMongoError(result.Err()) - } - - device := new(models.Device) - if err := result.Decode(&device); err != nil { - return FromMongoError(err) - } - - return nil -} - -func (s *Store) DeviceListByUsage(ctx context.Context, tenant string) ([]models.UID, error) { - query := []bson.M{ - { - "$match": bson.M{ - "tenant_id": tenant, - }, - }, - { - "$group": bson.M{ - "_id": "$device_uid", - "count": bson.M{ - "$sum": 1, - }, - }, - }, - { - "$sort": bson.M{ - "count": -1, - }, - }, - { - "$limit": 3, - }, - } - - uids := make([]models.UID, 0) - - cursor, err := s.db.Collection("sessions").Aggregate(ctx, query) - if err != nil { - return uids, FromMongoError(err) - } - - for cursor.Next(ctx) { - var dev map[string]interface{} - - err = cursor.Decode(&dev) - if err != nil { - return uids, err - } - - uids = append(uids, models.UID(dev["_id"].(string))) - } - - return uids, nil -} - -func (s *Store) DeviceGetByMac(ctx context.Context, mac string, tenantID string, status models.DeviceStatus) (*models.Device, error) { - device := new(models.Device) - - switch status { - case "": - if err := s.db.Collection("devices").FindOne(ctx, bson.M{"tenant_id": tenantID, "identity": bson.M{"mac": mac}}).Decode(&device); err != nil { - return nil, FromMongoError(err) - } - default: - if err := s.db.Collection("devices").FindOne(ctx, bson.M{"tenant_id": tenantID, "status": status, "identity": bson.M{"mac": mac}}).Decode(&device); err != nil { - return nil, FromMongoError(err) - } - } - - return device, nil -} - -func (s *Store) DeviceGetByName(ctx context.Context, name string, tenantID string, status models.DeviceStatus) (*models.Device, error) { - device := new(models.Device) - - if err := s.db.Collection("devices").FindOne(ctx, bson.M{"tenant_id": tenantID, "name": name, "status": string(status)}).Decode(&device); err != nil { - return nil, FromMongoError(err) - } - - return device, nil -} - -func (s *Store) DeviceGetByUID(ctx context.Context, uid models.UID, tenantID string) (*models.Device, error) { - var device *models.Device - if err := s.cache.Get(ctx, strings.Join([]string{"device", string(uid)}, "/"), &device); err != nil { - logrus.Error(err) - } - - if device != nil { - return device, nil - } - - if err := s.db.Collection("devices").FindOne(ctx, bson.M{"tenant_id": tenantID, "uid": uid}).Decode(&device); err != nil { - return nil, FromMongoError(err) - } - - if err := s.cache.Set(ctx, strings.Join([]string{"device", string(uid)}, "/"), device, time.Minute); err != nil { - logrus.Error(err) - } - - return device, nil -} - -func (s *Store) DeviceSetPosition(ctx context.Context, uid models.UID, position models.DevicePosition) error { - dev, err := s.db.Collection("devices").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$set": bson.M{"position": position}}) - if err != nil { - return FromMongoError(err) - } - - if dev.MatchedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) DeviceChooser(ctx context.Context, tenantID string, chosen []string) error { - filter := bson.M{ - "status": "accepted", - "tenant_id": tenantID, - "uid": bson.M{ - "$nin": chosen, - }, - } - - update := bson.M{ - "$set": bson.M{ - "status": "pending", - }, - } - - _, err := s.db.Collection("devices").UpdateMany(ctx, filter, update) - if err != nil { - return err - } - - return nil -} - -func (s *Store) DeviceConflicts(ctx context.Context, target *models.DeviceConflicts) ([]string, bool, error) { - pipeline := []bson.M{ - { - "$match": bson.M{ - "$or": []bson.M{ - {"name": target.Name}, - }, - }, - }, - } - - cursor, err := s.db.Collection("devices").Aggregate(ctx, pipeline) - if err != nil { - return nil, false, FromMongoError(err) - } - defer cursor.Close(ctx) - - conflicts := make([]string, 0) - for cursor.Next(ctx) { - device := new(models.DeviceConflicts) - if err := cursor.Decode(&device); err != nil { - return nil, false, FromMongoError(err) - } - - if device.Name == target.Name { - conflicts = append(conflicts, "name") - } - } - - return conflicts, len(conflicts) > 0, nil -} - -func (s *Store) DeviceUpdate(ctx context.Context, tenantID, uid string, changes *models.DeviceChanges) error { - filter := bson.M{"uid": uid} - if tenantID != "" { - filter["tenant_id"] = tenantID - } - - r, err := s.db.Collection("devices").UpdateMany(ctx, filter, bson.M{"$set": changes}) - if err != nil { - return FromMongoError(err) - } - - if r.MatchedCount < 1 { - return store.ErrNoDocuments - } - - if err := s.cache.Delete(ctx, "device"+uid+"/"); err != nil { - logrus.WithError(err).WithField("uid", uid).Error("cannot delete device from cache") - } - - return nil -} - -func (s *Store) DeviceBulkUpdate(ctx context.Context, uids []string, changes *models.DeviceChanges) (int64, error) { - res, err := s.db.Collection("devices").UpdateMany(ctx, bson.M{"uid": bson.M{"$in": uids}}, bson.M{"$set": changes}) - if err != nil { - return 0, FromMongoError(err) - } - - return res.ModifiedCount, nil -} - -func (s *Store) DeviceRemovedCount(ctx context.Context, tenant string) (int64, error) { - count, err := s.db.Collection("removed_devices").CountDocuments(ctx, bson.M{"device.tenant_id": tenant}) - if err != nil { - return 0, FromMongoError(err) - } - - return count, nil -} - -func (s *Store) DeviceRemovedGet(ctx context.Context, tenant string, uid models.UID) (*models.DeviceRemoved, error) { - var slot models.DeviceRemoved - err := s.db.Collection("removed_devices").FindOne(ctx, bson.M{"device.tenant_id": tenant, "device.uid": uid}).Decode(&slot) - if err != nil { - return nil, FromMongoError(err) - } - - return &slot, nil -} - -func (s *Store) DeviceRemovedInsert(ctx context.Context, tenant string, device *models.Device) error { //nolint:revive - now := time.Now() - - device.Status = models.DeviceStatusRemoved - device.StatusUpdatedAt = now - - _, err := s.db.Collection("removed_devices").InsertOne(ctx, models.DeviceRemoved{ - Timestamp: now, - Device: device, - }) - if err != nil { - return FromMongoError(err) - } - - return nil -} - -func (s *Store) DeviceRemovedDelete(ctx context.Context, tenant string, uid models.UID) error { - _, err := s.db.Collection("removed_devices").DeleteOne(ctx, bson.M{"device.tenant_id": tenant, "device.uid": uid}) - if err != nil { - return FromMongoError(err) - } - - return nil -} - -func (s *Store) DeviceRemovedList(ctx context.Context, tenant string, paginator query.Paginator, filters query.Filters, sorter query.Sorter) ([]models.DeviceRemoved, int, error) { - pipeline := []bson.M{ - { - "$match": bson.M{ - "device.tenant_id": tenant, - }, - }, - } - - pipeline = append(pipeline, queries.FromPaginator(&paginator)...) - - queryFilter, err := queries.FromFilters(&filters) - if err != nil { - return nil, 0, FromMongoError(err) - } - - pipeline = append(pipeline, queryFilter...) - - if sorter.By == "" { - sorter.By = "timestamp" - } - if sorter.Order == "" { - sorter.Order = query.OrderDesc - } - pipeline = append(pipeline, queries.FromSorter(&sorter)...) - - aggregation, err := s.db.Collection("removed_devices").Aggregate(ctx, pipeline) - if err != nil { - return nil, 0, FromMongoError(err) - } - - var devices []models.DeviceRemoved - if err := aggregation.All(ctx, &devices); err != nil { - return nil, 0, FromMongoError(err) - } - - return devices, len(devices), nil -} diff --git a/api/store/mongo/device_tags.go b/api/store/mongo/device_tags.go deleted file mode 100644 index 83ad2763c2e..00000000000 --- a/api/store/mongo/device_tags.go +++ /dev/null @@ -1,64 +0,0 @@ -package mongo - -import ( - "context" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/models" - "go.mongodb.org/mongo-driver/bson" -) - -func (s *Store) DevicePushTag(ctx context.Context, uid models.UID, tag string) error { - t, err := s.db.Collection("devices").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$push": bson.M{"tags": tag}}) - if err != nil { - return FromMongoError(err) - } - - if t.ModifiedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) DevicePullTag(ctx context.Context, uid models.UID, tag string) error { - t, err := s.db.Collection("devices").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$pull": bson.M{"tags": tag}}) - if err != nil { - return FromMongoError(err) - } - - if t.ModifiedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) DeviceSetTags(ctx context.Context, uid models.UID, tags []string) (int64, int64, error) { - tag, err := s.db.Collection("devices").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$set": bson.M{"tags": tags}}) - - return tag.MatchedCount, tag.ModifiedCount, FromMongoError(err) -} - -func (s *Store) DeviceBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (int64, error) { - res, err := s.db.Collection("devices").UpdateMany(ctx, bson.M{"tenant_id": tenant, "tags": currentTag}, bson.M{"$set": bson.M{"tags.$": newTag}}) - - return res.ModifiedCount, FromMongoError(err) -} - -func (s *Store) DeviceBulkDeleteTag(ctx context.Context, tenant, tag string) (int64, error) { - res, err := s.db.Collection("devices").UpdateMany(ctx, bson.M{"tenant_id": tenant}, bson.M{"$pull": bson.M{"tags": tag}}) - - return res.ModifiedCount, FromMongoError(err) -} - -func (s *Store) DeviceGetTags(ctx context.Context, tenant string) ([]string, int, error) { - list, err := s.db.Collection("devices").Distinct(ctx, "tags", bson.M{"tenant_id": tenant}) - - tags := make([]string, len(list)) - for i, item := range list { - tags[i] = item.(string) //nolint:forcetypeassert - } - - return tags, len(tags), FromMongoError(err) -} diff --git a/api/store/mongo/device_tags_test.go b/api/store/mongo/device_tags_test.go deleted file mode 100644 index 44e963c515e..00000000000 --- a/api/store/mongo/device_tags_test.go +++ /dev/null @@ -1,322 +0,0 @@ -package mongo_test - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" -) - -func TestDevicePushTag(t *testing.T) { - cases := []struct { - description string - uid models.UID - tag string - fixtures []string - expected error - }{ - { - description: "fails when device doesn't exist", - uid: models.UID("nonexistent"), - tag: "tag4", - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "successfully creates single tag for an existing device", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - tag: "tag4", - fixtures: []string{fixtureDevices}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DevicePushTag(ctx, tc.uid, tc.tag) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestDevicePullTag(t *testing.T) { - cases := []struct { - description string - uid models.UID - tag string - fixtures []string - expected error - }{ - { - description: "fails when device doesn't exist", - uid: models.UID("nonexistent"), - tag: "tag-1", - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "fails when device's tag doesn't exist", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - tag: "nonexistent", - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "successfully remove a single tag for an existing device", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - tag: "tag-1", - fixtures: []string{fixtureDevices}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DevicePullTag(ctx, tc.uid, tc.tag) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestDeviceSetTags(t *testing.T) { - type Expected struct { - matchedCount int64 - updatedCount int64 - err error - } - cases := []struct { - description string - uid models.UID - tags []string - fixtures []string - expected Expected - }{ - { - description: "successfully when device doesn't exist", - uid: models.UID("nonexistent"), - tags: []string{"new-tag"}, - fixtures: []string{fixtureDevices}, - expected: Expected{ - matchedCount: 0, - updatedCount: 0, - err: nil, - }, - }, - { - description: "successfully when tags are equal than current device's tags", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - tags: []string{"tag-1"}, - fixtures: []string{fixtureDevices}, - expected: Expected{ - matchedCount: 1, - updatedCount: 0, - err: nil, - }, - }, - { - description: "successfully update tags for an existing device", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - tags: []string{"new-tag"}, - fixtures: []string{fixtureDevices}, - expected: Expected{ - matchedCount: 1, - updatedCount: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - matchedCount, updatedCount, err := s.DeviceSetTags(ctx, tc.uid, tc.tags) - assert.Equal(t, tc.expected, Expected{matchedCount, updatedCount, err}) - }) - } -} - -func TestDeviceBulkRenameTag(t *testing.T) { - type Expected struct { - count int64 - err error - } - - cases := []struct { - description string - tenant string - oldTag string - newTag string - fixtures []string - expected Expected - }{ - { - description: "fails when tenant doesn't exist", - tenant: "nonexistent", - oldTag: "tag-1", - newTag: "newtag", - fixtures: []string{fixtureDevices}, - expected: Expected{ - count: 0, - err: nil, - }, - }, - { - description: "fails when device's tag doesn't exist", - tenant: "00000000-0000-4000-0000-000000000000", - oldTag: "nonexistent", - newTag: "newtag", - fixtures: []string{fixtureDevices}, - expected: Expected{ - count: 0, - err: nil, - }, - }, - { - description: "successfully rename tag for an existing device", - tenant: "00000000-0000-4000-0000-000000000000", - oldTag: "tag-1", - newTag: "newtag", - fixtures: []string{fixtureDevices}, - expected: Expected{ - count: 2, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - count, err := s.DeviceBulkRenameTag(ctx, tc.tenant, tc.oldTag, tc.newTag) - assert.Equal(t, tc.expected, Expected{count, err}) - }) - } -} - -func TestDeviceBulkDeleteTag(t *testing.T) { - type Expected struct { - count int64 - err error - } - - cases := []struct { - description string - tenant string - tag string - fixtures []string - expected Expected - }{ - { - description: "fails when tenant doesn't exist", - tenant: "nonexistent", - tag: "tag-1", - fixtures: []string{fixtureDevices}, - expected: Expected{ - count: 0, - err: nil, - }, - }, - { - description: "fails when device's tag doesn't exist", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "nonexistent", - fixtures: []string{fixtureDevices}, - expected: Expected{ - count: 0, - err: nil, - }, - }, - { - description: "successfully delete single tag for an existing device", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "tag-1", - fixtures: []string{fixtureDevices}, - expected: Expected{ - count: 2, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - count, err := s.DeviceBulkDeleteTag(ctx, tc.tenant, tc.tag) - assert.Equal(t, tc.expected, Expected{count, err}) - }) - } -} - -func TestDeviceGetTags(t *testing.T) { - type Expected struct { - tags []string - len int - err error - } - - cases := []struct { - description string - tenant string - fixtures []string - expected Expected - }{ - { - description: "succeeds when tags list is greater than 1", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixtureDevices}, - expected: Expected{ - tags: []string{"tag-1"}, - len: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - tags, count, err := s.DeviceGetTags(ctx, tc.tenant) - assert.Equal(t, tc.expected, Expected{tags: tags, len: count, err: err}) - }) - } -} diff --git a/api/store/mongo/device_test.go b/api/store/mongo/device_test.go deleted file mode 100644 index 008047e9601..00000000000 --- a/api/store/mongo/device_test.go +++ /dev/null @@ -1,1375 +0,0 @@ -package mongo_test - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" -) - -func TestDeviceList(t *testing.T) { - type Expected struct { - dev []models.Device - len int - err error - } - cases := []struct { - description string - paginator query.Paginator - sorter query.Sorter - filters query.Filters - status models.DeviceStatus - fixtures []string - expected Expected - }{ - { - description: "succeeds when no devices are found", - sorter: query.Sorter{By: "last_seen", Order: query.OrderAsc}, - paginator: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{}, - status: models.DeviceStatus(""), - fixtures: []string{}, - expected: Expected{ - dev: []models.Device{}, - len: 0, - err: nil, - }, - }, - { - description: "succeeds when devices are found", - sorter: query.Sorter{By: "last_seen", Order: query.OrderAsc}, - paginator: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{}, - status: models.DeviceStatus(""), - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: []models.Device{ - { - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UID: "5300530e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809f", - Name: "device-1", - Identity: &models.DeviceIdentity{MAC: "mac-1"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - UID: "4300430e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809e", - Name: "device-2", - Identity: &models.DeviceIdentity{MAC: "mac-2"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - UID: "3300330e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809d", - Name: "device-4", - Identity: &models.DeviceIdentity{MAC: "mac-4"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "pending", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: true, - }, - }, - len: 4, - err: nil, - }, - }, - { - description: "succeeds when devices are found with limited page and page size", - sorter: query.Sorter{By: "last_seen", Order: query.OrderAsc}, - paginator: query.Paginator{Page: 2, PerPage: 2}, - filters: query.Filters{}, - status: models.DeviceStatus(""), - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: []models.Device{ - { - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - UID: "3300330e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809d", - Name: "device-4", - Identity: &models.DeviceIdentity{MAC: "mac-4"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "pending", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: true, - }, - }, - len: 4, - err: nil, - }, - }, - { - description: "succeeds when devices are found with sort created_at", - sorter: query.Sorter{By: "last_seen", Order: query.OrderAsc}, - paginator: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{}, - status: models.DeviceStatus(""), - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: []models.Device{ - { - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UID: "5300530e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809f", - Name: "device-1", - Identity: &models.DeviceIdentity{MAC: "mac-1"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - UID: "4300430e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809e", - Name: "device-2", - Identity: &models.DeviceIdentity{MAC: "mac-2"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - UID: "3300330e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809d", - Name: "device-4", - Identity: &models.DeviceIdentity{MAC: "mac-4"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "pending", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: true, - }, - }, - len: 4, - err: nil, - }, - }, - { - description: "succeeds when devices are found with order asc", - sorter: query.Sorter{By: "last_seen", Order: query.OrderAsc}, - paginator: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{}, - status: models.DeviceStatus(""), - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: []models.Device{ - { - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UID: "5300530e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809f", - Name: "device-1", - Identity: &models.DeviceIdentity{MAC: "mac-1"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - UID: "4300430e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809e", - Name: "device-2", - Identity: &models.DeviceIdentity{MAC: "mac-2"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - UID: "3300330e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809d", - Name: "device-4", - Identity: &models.DeviceIdentity{MAC: "mac-4"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "pending", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: true, - }, - }, - len: 4, - err: nil, - }, - }, - { - description: "succeeds when devices are found with order desc", - sorter: query.Sorter{By: "last_seen", Order: query.OrderDesc}, - paginator: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{}, - status: models.DeviceStatus(""), - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: []models.Device{ - { - CreatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - UID: "3300330e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809d", - Name: "device-4", - Identity: &models.DeviceIdentity{MAC: "mac-4"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "pending", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: true, - }, - { - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - UID: "4300430e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809e", - Name: "device-2", - Identity: &models.DeviceIdentity{MAC: "mac-2"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: false, - }, - { - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UID: "5300530e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809f", - Name: "device-1", - Identity: &models.DeviceIdentity{MAC: "mac-1"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - }, - len: 4, - err: nil, - }, - }, - { - description: "succeeds when devices are found filtering status", - sorter: query.Sorter{By: "last_seen", Order: query.OrderAsc}, - paginator: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{}, - status: models.DeviceStatusPending, - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: []models.Device{ - { - CreatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - UID: "3300330e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809d", - Name: "device-4", - Identity: &models.DeviceIdentity{MAC: "mac-4"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "pending", - RemoteAddr: "", - Position: nil, - Tags: []string{}, - Acceptable: true, - }, - }, - len: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - dev, count, err := s.DeviceList( - ctx, - tc.status, - tc.paginator, - tc.filters, - tc.sorter, - store.DeviceAcceptableIfNotAccepted, - ) - assert.Equal(t, tc.expected, Expected{dev: dev, len: count, err: err}) - }) - } -} - -func TestDeviceListByUsage(t *testing.T) { - type Expected struct { - uid []models.UID - len int - err error - } - cases := []struct { - description string - tenant string - fixtures []string - expected Expected - }{ - { - description: "returns an empty list when tenant not exist", - tenant: "nonexistent", - fixtures: []string{fixtureSessions}, - expected: Expected{ - uid: []models.UID{}, - len: 0, - err: nil, - }, - }, - { - description: "succeeds when has 1 or more device sessions", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixtureSessions}, - expected: Expected{ - uid: []models.UID{"2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"}, - len: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - uids, err := s.DeviceListByUsage(ctx, tc.tenant) - assert.Equal(t, tc.expected, Expected{uid: uids, len: len(uids), err: err}) - }) - } -} - -func TestDeviceGet(t *testing.T) { - type Expected struct { - dev *models.Device - err error - } - cases := []struct { - description string - uid models.UID - fixtures []string - expected Expected - }{ - { - description: "fails when namespace is not found", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when device is not found", - uid: models.UID("nonexistent"), - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when device is not found due to tenant", - uid: models.UID("5600560h6ed5h960969e7f358g4568491247198ge8537e9g448609fff1b231f"), - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when device is found", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - dev, err := s.DeviceGet(ctx, tc.uid) - assert.Equal(t, tc.expected, Expected{dev: dev, err: err}) - }) - } -} - -func TestDeviceGetByMac(t *testing.T) { - type Expected struct { - dev *models.Device - err error - } - cases := []struct { - description string - mac string - tenant string - status models.DeviceStatus - fixtures []string - expected Expected - }{ - { - description: "fails when device is not found due to mac", - mac: "nonexistent", - tenant: "00000000-0000-4000-0000-000000000000", - status: models.DeviceStatus(""), - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when device is not found due to tenant", - mac: "mac-3", - tenant: "nonexistent", - status: models.DeviceStatus(""), - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when device is found", - mac: "mac-3", - tenant: "00000000-0000-4000-0000-000000000000", - status: models.DeviceStatus(""), - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - err: nil, - }, - }, - { - description: "succeeds when device with status is found", - mac: "mac-3", - tenant: "00000000-0000-4000-0000-000000000000", - status: models.DeviceStatus("accepted"), - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - dev, err := s.DeviceGetByMac(ctx, tc.mac, tc.tenant, tc.status) - assert.Equal(t, tc.expected, Expected{dev: dev, err: err}) - }) - } -} - -func TestDeviceGetByName(t *testing.T) { - type Expected struct { - dev *models.Device - err error - } - cases := []struct { - description string - hostname string - tenant string - status models.DeviceStatus - fixtures []string - expected Expected - }{ - { - description: "fails when device is not found due to name", - hostname: "nonexistent", - tenant: "00000000-0000-4000-0000-000000000000", - status: models.DeviceStatusAccepted, - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when device is not found due to tenant", - hostname: "device-3", - tenant: "nonexistent", - status: models.DeviceStatusAccepted, - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when device is found", - hostname: "device-3", - tenant: "00000000-0000-4000-0000-000000000000", - status: models.DeviceStatusAccepted, - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - dev, err := s.DeviceGetByName(ctx, tc.hostname, tc.tenant, tc.status) - assert.Equal(t, tc.expected, Expected{dev: dev, err: err}) - }) - } -} - -func TestDeviceGetByUID(t *testing.T) { - type Expected struct { - dev *models.Device - err error - } - cases := []struct { - description string - uid models.UID - tenant string - fixtures []string - expected Expected - }{ - { - description: "fails when device is not found due to UID", - uid: models.UID("nonexistent"), - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when device is not found due to tenant", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - tenant: "nonexistent", - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when device is found", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixtureDevices}, - expected: Expected{ - dev: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - dev, err := s.DeviceGetByUID(ctx, tc.uid, tc.tenant) - assert.Equal(t, tc.expected, Expected{dev: dev, err: err}) - }) - } -} - -func TestDeviceLookup(t *testing.T) { - type Expected struct { - dev *models.Device - err error - } - cases := []struct { - description string - namespace string - hostname string - fixtures []string - expected Expected - }{ - { - description: "fails when namespace does not exist", - namespace: "nonexistent", - hostname: "device-3", - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when device does not exist due to name", - namespace: "namespace-1", - hostname: "nonexistent", - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when device does not exist due to tenant-id", - namespace: "namespace-1", - hostname: "invalid_tenant", - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "fails when device does not exist due to status other than accepted", - namespace: "namespace-1", - hostname: "pending", - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when namespace exists and hostname status is accepted", - namespace: "namespace-1", - hostname: "device-3", - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - dev: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - dev, err := s.DeviceLookup(ctx, tc.namespace, tc.hostname) - assert.Equal(t, tc.expected, Expected{dev: dev, err: err}) - }) - } -} - -func TestDeviceCreate(t *testing.T) { - cases := []struct { - description string - hostname string - device models.Device - fixtures []string - expected error - }{ - { - description: "succeeds when all data is valid", - hostname: "device-3", - device: models.Device{ - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Identity: &models.DeviceIdentity{ - MAC: "mac-3", - }, - TenantID: "00000000-0000-4000-0000-000000000000", - LastSeen: clock.Now(), - }, - fixtures: []string{}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DeviceCreate(ctx, tc.device, tc.hostname) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestDeviceRename(t *testing.T) { - cases := []struct { - description string - uid models.UID - hostname string - fixtures []string - expected error - }{ - { - description: "fails when the device is not found", - uid: models.UID("nonexistent"), - hostname: "new_hostname", - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when the device is found", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - hostname: "new_hostname", - fixtures: []string{fixtureDevices}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DeviceRename(ctx, tc.uid, tc.hostname) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestDeviceUpdateStatus(t *testing.T) { - cases := []struct { - description string - uid models.UID - status string - fixtures []string - expected error - }{ - { - description: "fails when the device is not found", - uid: models.UID("nonexistent"), - status: "accepted", - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when the device is found", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - status: "accepted", - fixtures: []string{fixtureDevices}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DeviceUpdateStatus(ctx, tc.uid, models.DeviceStatus(tc.status)) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestDeviceSetPosition(t *testing.T) { - cases := []struct { - description string - uid models.UID - position models.DevicePosition - fixtures []string - expected error - }{ - { - description: "fails when the device is not found", - uid: models.UID("nonexistent"), - position: models.DevicePosition{ - Longitude: 1, - Latitude: 1, - }, - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when the device is found", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - position: models.DevicePosition{ - Longitude: 1, - Latitude: 1, - }, - fixtures: []string{fixtureDevices}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DeviceSetPosition(ctx, tc.uid, tc.position) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestDeviceChooser(t *testing.T) { - cases := []struct { - description string - tenant string - chosen []string - fixtures []string - expected error - }{ - { - description: "", - tenant: "00000000-0000-4000-0000-000000000000", - chosen: []string{""}, - fixtures: []string{fixtureDevices}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DeviceChooser(ctx, tc.tenant, tc.chosen) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestDeviceConflicts(t *testing.T) { - type Expected struct { - conflicts []string - ok bool - err error - } - - cases := []struct { - description string - target *models.DeviceConflicts - fixtures []string - expected Expected - }{ - { - description: "no conflicts when target is empty", - target: &models.DeviceConflicts{}, - fixtures: []string{fixtureDevices}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "no conflicts with non existing email", - target: &models.DeviceConflicts{Name: "nonexistent"}, - fixtures: []string{fixtureDevices}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "conflict detected with existing email", - target: &models.DeviceConflicts{Name: "device-1"}, - fixtures: []string{fixtureDevices}, - expected: Expected{[]string{"name"}, true, nil}, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - conflicts, ok, err := s.DeviceConflicts(ctx, tc.target) - require.Equal(t, tc.expected, Expected{conflicts, ok, err}) - }) - } -} - -func TestDeviceUpdate(t *testing.T) { - cases := []struct { - description string - tenantID string - uid string - changes *models.DeviceChanges - fixtures []string - expected error - }{ - { - description: "fails when the device is not found due to uid", - tenantID: "00000000-0000-4000-0000-000000000000", - uid: "nonexistent", - changes: &models.DeviceChanges{}, - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "fails when the device is not found due to tenantID", - tenantID: "nonexistent", - uid: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - changes: &models.DeviceChanges{}, - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when the device is found with tenant", - tenantID: "00000000-0000-4000-0000-000000000000", - uid: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - changes: &models.DeviceChanges{}, - fixtures: []string{fixtureDevices}, - expected: nil, - }, - { - description: "succeeds when the device is found without tenant", - tenantID: "", - uid: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - changes: &models.DeviceChanges{}, - fixtures: []string{fixtureDevices}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DeviceUpdate(ctx, tc.tenantID, tc.uid, tc.changes) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestDeviceBulkUpdate(t *testing.T) { - type Expected struct { - modifiedCount int64 - err error - } - - cases := []struct { - description string - uids []string - changes *models.DeviceChanges - fixtures []string - expected Expected - }{ - { - description: "succeeds when a device does not matches", - uids: []string{"0000000000000000000000000000000000000000000000000000000000000000"}, - changes: &models.DeviceChanges{}, - fixtures: []string{fixtureDevices}, - expected: Expected{int64(0), nil}, - }, - { - description: "succeeds when devices matches but nothing is updated", - uids: []string{"2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", "4300430e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809e"}, - changes: &models.DeviceChanges{}, - fixtures: []string{fixtureDevices}, - expected: Expected{int64(0), nil}, - }, - { - description: "succeeds when devices matches", - uids: []string{"2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", "4300430e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809e"}, - changes: &models.DeviceChanges{LastSeen: time.Now()}, - fixtures: []string{fixtureDevices}, - expected: Expected{int64(2), nil}, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - modifiedCount, err := s.DeviceBulkUpdate(ctx, tc.uids, tc.changes) - require.Equal(t, Expected{modifiedCount: modifiedCount, err: err}, tc.expected) - - cursor, err := db.Collection("devices").Find(ctx, bson.M{"uid": bson.M{"$in": tc.uids}}) - require.NoError(t, err) - - for cursor.Next(ctx) { - device := new(models.Device) - require.NoError(t, cursor.Decode(device)) - - if tc.changes.LastSeen != (time.Time{}) { - require.WithinDuration(t, tc.changes.LastSeen, device.LastSeen, 2*time.Second) - } - } - }) - } -} - -func TestDeviceDelete(t *testing.T) { - cases := []struct { - description string - uid models.UID - fixtures []string - expected error - }{ - { - description: "fails when device is not found", - uid: models.UID("nonexistent"), - fixtures: []string{fixtureDevices}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when device is found", - uid: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - fixtures: []string{fixtureDevices}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.DeviceDelete(ctx, tc.uid) - assert.Equal(t, tc.expected, err) - }) - } -} diff --git a/api/store/mongo/fixtures/active_sessions.json b/api/store/mongo/fixtures/active_sessions.json deleted file mode 100644 index f957a14ecca..00000000000 --- a/api/store/mongo/fixtures/active_sessions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "active_sessions": { - "650a1c1b3b3bb3a0f8e9bf43": { - "last_seen": "2023-01-01T12:00:00.000Z", - "uid": "a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68" - } - } -} diff --git a/api/store/mongo/fixtures/api-key.json b/api/store/mongo/fixtures/api-key.json deleted file mode 100644 index 313904cd156..00000000000 --- a/api/store/mongo/fixtures/api-key.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "api_keys": { - "f23a2e56cd3fcfba002c72675c870e1e7813292adc40bbf14cea479a2e07976a": { - "name": "dev", - "created_by": "507f1f77bcf86cd799439011", - "tenant_id": "00000000-0000-4000-0000-000000000000", - "role": "admin", - "created_at": "2023-01-01T12:00:00.000Z", - "updated_at": "2023-01-01T12:00:00.000Z", - "expires_in": 0 - }, - "a1b2c73ea41f70870c035283336d72228118213ed03ec78043ffee48d827af11": { - "name": "prod", - "created_by": "507f1f77bcf86cd799439011", - "tenant_id": "00000000-0000-4000-0000-000000000000", - "role": "operator", - "created_at": "2023-01-02T12:00:00.000Z", - "updated_at": "2023-01-02T12:00:00.000Z", - "expires_in": 10 - } - } -} diff --git a/api/store/mongo/fixtures/devices.json b/api/store/mongo/fixtures/devices.json deleted file mode 100644 index d4477bc83f2..00000000000 --- a/api/store/mongo/fixtures/devices.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "devices": { - "656f605bafb652df9927adef": { - "created_at": "2023-01-01T12:00:00.000Z", - "last_seen": "2023-01-01T12:00:00.000Z", - "disconnected_at": null, - "status_updated_at": "2023-01-01T12:00:00.000Z", - "identity": { - "mac": "mac-1" - }, - "info": null, - "name": "device-1", - "position": null, - "public_key": "", - "remote_addr": "", - "status": "accepted", - "tags": [ - "tag-1" - ], - "tenant_id": "00000000-0000-4000-0000-000000000000", - "uid": "5300530e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809f" - }, - "656f608671625f495fe70037": { - "created_at": "2023-01-02T12:00:00.000Z", - "last_seen": "2023-01-02T12:00:00.000Z", - "disconnected_at": null, - "status_updated_at": "2023-01-02T12:00:00.000Z", - "identity": { - "mac": "mac-2" - }, - "info": null, - "name": "device-2", - "position": null, - "public_key": "", - "remote_addr": "", - "status": "accepted", - "tags": [], - "tenant_id": "00000000-0000-4000-0000-000000000000", - "uid": "4300430e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809e" - }, - "6500c8f026e1e911042ee820": { - "created_at": "2023-01-03T12:00:00.000Z", - "last_seen": "2023-01-03T12:00:00.000Z", - "disconnected_at": null, - "status_updated_at": "2023-01-03T12:00:00.000Z", - "identity": { - "mac": "mac-3" - }, - "info": null, - "name": "device-3", - "position": null, - "public_key": "", - "remote_addr": "", - "status": "accepted", - "tags": [ - "tag-1" - ], - "tenant_id": "00000000-0000-4000-0000-000000000000", - "uid": "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c" - }, - "656f60cada27dad292686e34": { - "created_at": "2023-01-04T12:00:00.000Z", - "last_seen": "2023-01-04T12:00:00.000Z", - "disconnected_at": null, - "status_updated_at": "2023-01-04T12:00:00.000Z", - "identity": { - "mac": "mac-4" - }, - "info": null, - "name": "device-4", - "position": null, - "public_key": "", - "remote_addr": "", - "status": "pending", - "tags": [], - "tenant_id": "00000000-0000-4000-0000-000000000000", - "uid": "3300330e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809d" - } - } -} diff --git a/api/store/mongo/fixtures/firewall_rules.json b/api/store/mongo/fixtures/firewall_rules.json deleted file mode 100644 index fb1f687eaba..00000000000 --- a/api/store/mongo/fixtures/firewall_rules.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "firewall_rules": { - "6504b7bd9b6c4a63a9ccc053": { - "action": "allow", - "active": true, - "filter": { - "tags": ["tag-1"] - }, - "priority": 1, - "source_ip": ".*", - "tenant_id": "00000000-0000-4000-0000-000000000000", - "username": ".*" - }, - "e92f4a5d3e1a4f7b8b2b6e9a": { - "action": "allow", - "active": true, - "filter": { - "tags": ["tag-1"] - }, - "priority": 2, - "source_ip": "192.168.1.10", - "tenant_id": "00000000-0000-4000-0000-000000000000", - "username": "john.doe" - }, - "78c96f0a2e5b4dca8d78f00c": { - "action": "allow", - "active": true, - "filter": { - "tags": [] - }, - "priority": 3, - "source_ip": "10.0.0.0/24", - "tenant_id": "00000000-0000-4000-0000-000000000000", - "username": "admin" - }, - "3fd759a1ecb64ec5a07c8c0f": { - "action": "deny", - "active": true, - "filter": { - "tags": ["tag-1"] - }, - "priority": 4, - "source_ip": "172.16.0.0/16", - "tenant_id": "00000000-0000-4000-0000-000000000000", - "username": ".*" - } - } -} diff --git a/api/store/mongo/fixtures/namespaces.json b/api/store/mongo/fixtures/namespaces.json deleted file mode 100644 index 2b3c4e859c8..00000000000 --- a/api/store/mongo/fixtures/namespaces.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "namespaces": { - "6500c8f086353a36732d544a": { - "created_at": "2023-01-01T12:00:00.000Z", - "max_devices": -1, - "members": [ - { - "id": "507f1f77bcf86cd799439011", - "added_at": "2023-01-01T12:00:00.000Z", - "role": "owner", - "status": "accepted" - }, - { - "id": "6509e169ae6144b2f56bf288", - "added_at": "2023-01-01T12:00:00.000Z", - "role": "observer", - "status": "pending" - } - ], - "name": "namespace-1", - "owner": "507f1f77bcf86cd799439011", - "settings": { - "session_record": true - }, - "tenant_id": "00000000-0000-4000-0000-000000000000" - }, - "e5b45d9a2c0a4f8dbb7f4e5d": { - "created_at": "2023-01-01T12:00:00.000Z", - "max_devices": 10, - "members": [ - { - "id": "6509e169ae6144b2f56bf288", - "added_at": "2023-01-01T12:00:00.000Z", - "role": "owner", - "status": "accepted" - }, - { - "id": "907f1f77bcf86cd799439022", - "added_at": "2023-01-01T12:00:00.000Z", - "role": "operator", - "status": "accepted" - } - ], - "name": "namespace-2", - "owner": "6509e169ae6144b2f56bf288", - "settings": { - "session_record": false - }, - "tenant_id": "00000000-0000-4001-0000-000000000000" - }, - "3c7f09a5b46c4a63a9ccc071": { - "created_at": "2023-01-01T12:00:00.000Z", - "max_devices": 3, - "members": [ - { - "id": "657b0e3bff780d625f74e49a", - "added_at": "2023-01-01T12:00:00.000Z", - "role": "owner", - "status": "accepted" - } - ], - "name": "namespace-3", - "owner": "657b0e3bff780d625f74e49a", - "settings": { - "session_record": true - }, - "tenant_id": "00000000-0000-4002-0000-000000000000" - }, - "6577271b9f5a02f3bc8f5400": { - "created_at": "2023-01-01T12:00:00.000Z", - "max_devices": -1, - "members": [ - { - "id": "6577267d8752d05270a4c07d", - "added_at": "2023-01-01T12:00:00.000Z", - "role": "owner", - "status": "accepted" - } - ], - "name": "namespace-4", - "owner": "6577267d8752d05270a4c07d", - "settings": { - "session_record": true - }, - "tenant_id": "00000000-0000-4003-0000-000000000000" - } - } -} diff --git a/api/store/mongo/fixtures/private_keys.json b/api/store/mongo/fixtures/private_keys.json deleted file mode 100644 index eca20e1ffe8..00000000000 --- a/api/store/mongo/fixtures/private_keys.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "private_keys": { - "65088c97a3efce71bf6e1f32": { - "created_at": "2023-01-01T12:00:00.000Z", - "data": "test", - "fingerprint": "fingerprint" - } - } -} diff --git a/api/store/mongo/fixtures/public_keys.json b/api/store/mongo/fixtures/public_keys.json deleted file mode 100644 index 306b88fde1d..00000000000 --- a/api/store/mongo/fixtures/public_keys.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "public_keys": { - "65088c97a3efce71bf6e1f32": { - "created_at": "2023-01-01T12:00:00.000Z", - "data": "test", - "filter": { - "hostname": ".*", - "tags": ["tag-1"] - }, - "fingerprint": "fingerprint", - "name": "public_key", - "tenant_id": "00000000-0000-4000-0000-000000000000" - } - } -} diff --git a/api/store/mongo/fixtures/recorded_sessions.json b/api/store/mongo/fixtures/recorded_sessions.json deleted file mode 100644 index 87d4ad0272e..00000000000 --- a/api/store/mongo/fixtures/recorded_sessions.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "recorded_sessions": { - "650a29b8ed1050a22b2c862e": { - "time": "2023-01-02T12:00:00.000Z", - "uid": "e7f3a56d8b9e1dc4c285c98c8ea9c33032a17bda5b6c6b05a6213c2a02f97824", - "tenant_id": "00000000-0000-4000-0000-000000000000", - "message": "message" - }, - "657b5c2926722519a839a4b9": { - "time": "2023-01-04T12:00:00.000Z", - "uid": "bc3d75821a29cfe70bf7986f9ee5629e384b2d3a21e0c3d90f6e35b0c946178a", - "tenant_id": "00000000-0000-4000-0000-000000000000", - "message": "message" - } - } -} diff --git a/api/store/mongo/fixtures/recovery_tokens.json b/api/store/mongo/fixtures/recovery_tokens.json deleted file mode 100644 index 9d44ff2b459..00000000000 --- a/api/store/mongo/fixtures/recovery_tokens.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "recovery_tokens": { - "6509b24ea36fcf8c2d4f589e": { - "created_at": "2023-01-01T12:00:00.000Z", - "token": "token", - "user": "507f1f77bcf86cd799439011" - } - } -} diff --git a/api/store/mongo/fixtures/sessions.json b/api/store/mongo/fixtures/sessions.json deleted file mode 100644 index a0a092a189b..00000000000 --- a/api/store/mongo/fixtures/sessions.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "sessions": { - "650a16617ab1243960ee4c8c": { - "tenant_id": "00000000-0000-4000-0000-000000000000", - "uid": "a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68", - "device_uid": "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - "started_at": "2023-01-01T12:00:00.000Z", - "last_seen": "2023-01-01T12:00:00.000Z", - "authenticated": true, - "recorded": false, - "closed": true, - "ip_address": "0.0.0.0", - "position": { - "longitude": 0, - "latitude": 0 - }, - "term": "xterm", - "type": "shell", - "username": "john_doe" - }, - "712e2f8c0b8e632c4f8dc8a2": { - "tenant_id": "00000000-0000-4000-0000-000000000000", - "uid": "e7f3a56d8b9e1dc4c285c98c8ea9c33032a17bda5b6c6b05a6213c2a02f97824", - "device_uid": "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - "started_at": "2023-01-02T12:00:00.000Z", - "last_seen": "2023-01-02T12:00:00.000Z", - "authenticated": true, - "recorded": true, - "closed": true, - "ip_address": "0.0.0.0", - "position": { - "longitude": 45.6789, - "latitude": -12.3456 - }, - "term": "xterm", - "type": "shell", - "username": "john_doe" - }, - "657b5d280df7c547ae17f656": { - "tenant_id": "00000000-0000-4000-0000-000000000000", - "uid": "fc2e1493d8b6a4c17bf6a2f7f9e55629e384b2d3a21e0c3d90f6e35b0c946178a", - "device_uid": "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - "started_at": "2023-01-03T12:00:00.000Z", - "last_seen": "2023-01-03T12:00:00.000Z", - "authenticated": true, - "recorded": false, - "closed": true, - "ip_address": "0.0.0.0", - "position": { - "longitude": -78.9012, - "latitude": 23.4567 - }, - "term": "", - "type": "exec", - "username": "john_doe" - }, - "6f81eab9d73f4a2e8c45011b": { - "tenant_id": "00000000-0000-4000-0000-000000000000", - "uid": "bc3d75821a29cfe70bf7986f9ee5629e384b2d3a21e0c3d90f6e35b0c946178a", - "device_uid": "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - "started_at": "2023-01-04T12:00:00.000Z", - "last_seen": "2023-01-04T12:00:00.000Z", - "authenticated": true, - "recorded": true, - "closed": true, - "ip_address": "0.0.0.0", - "position": { - "longitude": -56.7890, - "latitude": 34.5678 - }, - "term": "xterm", - "type": "shell", - "username": "john_doe" - } - } -} diff --git a/api/store/mongo/fixtures/users.json b/api/store/mongo/fixtures/users.json deleted file mode 100644 index 675ff542734..00000000000 --- a/api/store/mongo/fixtures/users.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "users": { - "507f1f77bcf86cd799439011": { - "status": "confirmed", - "created_at": "2023-01-01T12:00:00.000Z", - "last_login": "2023-01-01T12:00:00.000Z", - "email": "john.doe@test.com", - "email_marketing": true, - "max_namespaces": 0, - "name": "john doe", - "password": "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - "username": "john_doe" - }, - "608f32a2c7351f001f6475e0": { - "status": "confirmed", - "created_at": "2023-01-02T12:00:00.000Z", - "last_login": "2023-01-02T12:00:00.000Z", - "email": "jane.smith@test.com", - "email_marketing": true, - "max_namespaces": 3, - "name": "Jane Smith", - "password": "a0b8c29f4c8d57e542f5e81d35ebe801fd27f569f116fe670e8962d798512a1d", - "username": "jane_smith" - }, - "709f45b5e812c1002f3a67e7": { - "status": "confirmed", - "created_at": "2023-01-03T12:00:00.000Z", - "last_login": "2023-01-03T12:00:00.000Z", - "email": "bob.johnson@test.com", - "email_marketing": true, - "max_namespaces": 10, - "name": "Bob Johnson", - "password": "5f3b3956a1a150b73e6b27e674f27d7aeb01ab1a40c179c3e1aa6026a36655a2", - "username": "bob_johnson" - }, - "80fdcea1d7299c002f3a67e8": { - "status": "not-confirmed", - "created_at": "2023-01-04T12:00:00.000Z", - "last_login": null, - "email": "alex.rodriguez@test.com", - "email_marketing": false, - "max_namespaces": 3, - "name": "Alex Rodriguez", - "password": "c5093eb98678c7a3324825b84c6b67c1127b93786482ddbbd356e67e29b2763f", - "username": "alex_rodriguez" - }, - "6509e169ae6144b2f56bf288": { - "status": "confirmed", - "created_at": "2023-01-05T12:00:00.000Z", - "last_login": "2023-01-05T12:00:00.000Z", - "email": "maria.garcia@test.com", - "email_marketing": true, - "max_namespaces": 5, - "name": "Maria Garcia", - "password": "c2301b2b7e872843b473d2c301e4fb2e6e9f27f2e7a1b6ad44a3b2c97f1670b3", - "username": "maria_garcia" - } - } -} diff --git a/api/store/mongo/migrations/main.go b/api/store/mongo/migrations/main.go deleted file mode 100644 index 8b78c61333e..00000000000 --- a/api/store/mongo/migrations/main.go +++ /dev/null @@ -1,119 +0,0 @@ -package migrations - -import ( - "context" - - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -func GenerateMigrations() []migrate.Migration { - return []migrate.Migration{ - migration1, - migration2, - migration3, - migration4, - migration5, - migration6, - migration7, - migration8, - migration9, - migration10, - migration11, - migration12, - migration13, - migration14, - migration15, - migration16, - migration17, - migration18, - migration19, - migration20, - migration21, - migration22, - migration23, - migration24, - migration25, - migration26, - migration27, - migration28, - migration29, - migration30, - migration31, - migration32, - migration33, - migration34, - migration35, - migration36, - migration37, - migration38, - migration39, - migration40, - migration41, - migration42, - migration43, - migration44, - migration45, - migration46, - migration47, - migration48, - migration49, - migration50, - migration51, - migration52, - migration53, - migration54, - migration55, - migration56, - migration57, - migration58, - migration59, - migration60, - migration61, - migration62, - migration63, - migration64, - migration65, - migration66, - migration67, - migration68, - migration69, - migration70, - migration71, - migration72, - migration73, - migration74, - migration75, - migration76, - migration77, - migration78, - migration79, - migration80, - migration81, - migration82, - migration83, - migration84, - migration85, - migration86, - migration87, - migration88, - migration89, - migration90, - migration91, - migration92, - migration93, - migration94, - migration95, - migration96, - migration97, - migration98, - migration99, - } -} - -func renameField(db *mongo.Database, coll, from, to string) error { - _, err := db.Collection(coll).UpdateMany(context.Background(), bson.M{}, bson.M{"$rename": bson.M{from: to}}) - - return err -} diff --git a/api/store/mongo/migrations/main_test.go b/api/store/mongo/migrations/main_test.go deleted file mode 100644 index e352a8d1c45..00000000000 --- a/api/store/mongo/migrations/main_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package migrations - -import ( - "context" - "os" - "testing" - - "github.com/shellhub-io/shellhub/api/pkg/dbtest" - log "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/mongo" - mongooptions "go.mongodb.org/mongo-driver/mongo/options" -) - -var ( - srv = &dbtest.Server{} - c *mongo.Client -) - -func TestMain(m *testing.M) { - os.Setenv("SHELLHUB_ENTERPRISE", "true") - os.Setenv("SHELLHUB_CLOUD", "true") - - log.Info("Starting migration tests") - - ctx := context.Background() - - srv.Container.Database = "test" - - if err := srv.Up(ctx); err != nil { - log.WithError(err).Error("Failed to UP the mongodb container") - os.Exit(1) - } - - log.Info("Connecting to ", srv.Container.ConnectionString) - - var err error - - c, err = mongo.Connect(ctx, mongooptions.Client().ApplyURI(srv.Container.ConnectionString+"/"+srv.Container.Database)) - if err != nil { - log.WithError(err).Error("Unable to connect to MongoDB") - os.Exit(1) - } - - if err := c.Ping(ctx, nil); err != nil { - log.WithError(err).Error("Unable to ping MongoDB") - os.Exit(1) - } - - code := m.Run() - - log.Info("Stopping migration tests") - if err := srv.Down(ctx); err != nil { - log.WithError(err).Error("Failed to DOWN the mongodb container") - os.Exit(1) - } - - os.Exit(code) -} diff --git a/api/store/mongo/migrations/migration_1.go b/api/store/mongo/migrations/migration_1.go deleted file mode 100644 index 10551820960..00000000000 --- a/api/store/mongo/migrations/migration_1.go +++ /dev/null @@ -1,32 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration1 = migrate.Migration{ - Version: 1, - Description: "Create the database for the system", - Up: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 1, - "action": "Up", - }).Info("Applying migration") - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 1, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_10.go b/api/store/mongo/migrations/migration_10.go deleted file mode 100644 index 3cbfc77d696..00000000000 --- a/api/store/mongo/migrations/migration_10.go +++ /dev/null @@ -1,42 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration10 = migrate.Migration{ - Version: 10, - Description: "Unset unique on session_record in the users collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 10, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"session_record", 1}}, - Options: options.Index().SetName("session_record").SetUnique(false), - } - if _, err := db.Collection("users").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - _, err := db.Collection("users").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"session_record": true}}) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 10, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_10_test.go b/api/store/mongo/migrations/migration_10_test.go deleted file mode 100644 index e426f8a0d92..00000000000 --- a/api/store/mongo/migrations/migration_10_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration10(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:9]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - user1 := struct { - Name string `json:"name" validate:"required,min=1"` - Email string `json:"email" bson:",omitempty" validate:"required,email"` - Username string `json:"username" bson:",omitempty" validate:"required,min=3,max=30,alphanum,ascii"` - Password string `json:"password" bson:",omitempty"` - SessionRecord bool `json:"session_record"` - }{ - Name: "user1", - Email: "email1", - Username: "username1", - Password: "password", - SessionRecord: true, - } - - user2 := struct { - Name string `json:"name" validate:"required,min=1"` - Email string `json:"email" bson:",omitempty" validate:"required,email"` - Username string `json:"username" bson:",omitempty" validate:"required,min=3,max=30,alphanum,ascii"` - Password string `json:"password" bson:",omitempty"` - SessionRecord bool `json:"session_record"` - }{ - Name: "user2", - Email: "email2", - Username: "username2", - Password: "password", - SessionRecord: true, - } - - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user2) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:10]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_11.go b/api/store/mongo/migrations/migration_11.go deleted file mode 100644 index 4bbdf83148b..00000000000 --- a/api/store/mongo/migrations/migration_11.go +++ /dev/null @@ -1,43 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration11 = migrate.Migration{ - Version: 11, - Description: "Create a ttl for the private_keys collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 11, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"created_at", 1}}, - Options: options.Index().SetName("ttl").SetExpireAfterSeconds(60), - } - _, err := db.Collection("private_keys").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 11, - "action": "Down", - }).Info("Applying migration") - _, err := db.Collection("private_keys").Indexes().DropOne(ctx, "ttl") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_11_test.go b/api/store/mongo/migrations/migration_11_test.go deleted file mode 100644 index 8e9a86db2fd..00000000000 --- a/api/store/mongo/migrations/migration_11_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration11(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:11]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - pk := models.PrivateKey{ - CreatedAt: clock.Now(), - } - - _, err = c.Database("test").Collection("private_keys").InsertOne(context.TODO(), pk) - assert.NoError(t, err) - - index := c.Database("test").Collection("private_keys").Indexes() - - cursor, err := index.List(context.TODO()) - assert.NoError(t, err) - - var results []bson.M - err = cursor.All(context.TODO(), &results) - assert.NoError(t, err) - - keyField, ok := results[1]["key"].(primitive.M) - if !ok { - panic("type assertion failed") - } - - assert.Equal(t, int32(1), keyField["created_at"]) - - value, key := results[1]["expireAfterSeconds"] - assert.Equal(t, true, key) - assert.Equal(t, int32(60), value) -} diff --git a/api/store/mongo/migrations/migration_12.go b/api/store/mongo/migrations/migration_12.go deleted file mode 100644 index 945702eaf7b..00000000000 --- a/api/store/mongo/migrations/migration_12.go +++ /dev/null @@ -1,47 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration12 = migrate.Migration{ - Version: 12, - Description: "Set the tenant_id as unique in the namespaces collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 12, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"tenant_id", 1}}, - Options: options.Index().SetName("tenant_id").SetUnique(true), - } - if _, err := db.Collection("namespaces").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - mod = mongo.IndexModel{ - Keys: bson.D{{"name", 1}}, - Options: options.Index().SetName("name").SetUnique(true), - } - _, err := db.Collection("namespaces").Indexes().CreateOne(ctx, mod) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 12, - "action": "Down", - }).Info("Applying migration") - _, err := db.Collection("namespaces").Indexes().DropOne(ctx, "tenant_id") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_12_test.go b/api/store/mongo/migrations/migration_12_test.go deleted file mode 100644 index df236d59f74..00000000000 --- a/api/store/mongo/migrations/migration_12_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration12(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - ns1 := models.Namespace{Name: "name", TenantID: "1"} - ns2 := models.Namespace{Name: "name", TenantID: "1"} - - _, err := c.Database("test").Collection("namespaces").InsertOne(context.TODO(), ns1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), ns2) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:11]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:12]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.Error(t, err) -} diff --git a/api/store/mongo/migrations/migration_13.go b/api/store/mongo/migrations/migration_13.go deleted file mode 100644 index a3836d0b993..00000000000 --- a/api/store/mongo/migrations/migration_13.go +++ /dev/null @@ -1,124 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration13 = migrate.Migration{ - Version: 13, - Description: "Change on several collections", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 13, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"uid", 1}}, - Options: options.Index().SetName("uid").SetUnique(true), - } - _, err := db.Collection("devices").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - mod = mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(30), - } - _, err = db.Collection("connected_devices").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - mod = mongo.IndexModel{ - Keys: bson.D{{"uid", 1}}, - Options: options.Index().SetName("uid").SetUnique(false), - } - _, err = db.Collection("connected_devices").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - mod = mongo.IndexModel{ - Keys: bson.D{{"uid", 1}}, - Options: options.Index().SetName("uid").SetUnique(true), - } - _, err = db.Collection("sessions").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - mod = mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(30), - } - _, err = db.Collection("active_sessions").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - mod = mongo.IndexModel{ - Keys: bson.D{{"uid", 1}}, - Options: options.Index().SetName("uid").SetUnique(false), - } - _, err = db.Collection("active_sessions").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - mod = mongo.IndexModel{ - Keys: bson.D{{"username", 1}}, - Options: options.Index().SetName("username").SetUnique(true), - } - _, err = db.Collection("users").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - mod = mongo.IndexModel{ - Keys: bson.D{{"tenant_id", 1}}, - Options: options.Index().SetName("tenant_id").SetUnique(true), - } - _, err = db.Collection("users").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 13, - "action": "Down", - }).Info("Applying migration") - if _, err := db.Collection("devices").Indexes().DropOne(ctx, "uid"); err != nil { - return err - } - if _, err := db.Collection("connected_devices").Indexes().DropOne(ctx, "last_seen"); err != nil { - return err - } - if _, err := db.Collection("connected_devices").Indexes().DropOne(ctx, "uid"); err != nil { - return err - } - if _, err := db.Collection("sessions").Indexes().DropOne(ctx, "uid"); err != nil { - return err - } - if _, err := db.Collection("active_sessions").Indexes().DropOne(ctx, "last_seen"); err != nil { - return err - } - if _, err := db.Collection("users").Indexes().DropOne(ctx, "username"); err != nil { - return err - } - _, err := db.Collection("users").Indexes().DropOne(ctx, "tenant_id") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_13_test.go b/api/store/mongo/migrations/migration_13_test.go deleted file mode 100644 index 13736552ec1..00000000000 --- a/api/store/mongo/migrations/migration_13_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration13(t *testing.T) { - type ConnectedDevice struct { - UID string `json:"uid"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - LastSeen time.Time `json:"last_seen" bson:"last_seen"` - } - - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - logrus.Info("Test if the UID is unique in the devices collection") - - device1 := models.Device{ - UID: "1", - } - - device2 := models.Device{ - UID: "1", - } - - _, err := c.Database("test").Collection("devices").InsertOne(context.TODO(), device1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device2) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:13]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.Error(t, err) - - logrus.Info("Test if the uid in the connected_devices collection is not unique") - - connectedDevice1 := ConnectedDevice{ - UID: "1", - } - - connectedDevice2 := ConnectedDevice{ - UID: "1", - } - - _, err = c.Database("test").Collection("connected_devices").InsertOne(context.TODO(), connectedDevice1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("connected_devices").InsertOne(context.TODO(), connectedDevice2) - assert.NoError(t, err) - - logrus.Info("Test if the uid in the sessions collection is unique") - - session1 := models.Session{ - UID: "1", - } - - session2 := models.Session{ - UID: "1", - } - - _, err = c.Database("test").Collection("sessions").InsertOne(context.TODO(), session1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("sessions").InsertOne(context.TODO(), session2) - assert.NoError(t, err) - - activeSession1 := models.ActiveSession{ - UID: "1", - } - - activeSession2 := models.ActiveSession{ - UID: "1", - } - - _, err = c.Database("test").Collection("active_sessions").InsertOne(context.TODO(), activeSession1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("active_sessions").InsertOne(context.TODO(), activeSession2) - assert.NoError(t, err) - - logrus.Info("Test if the tenant_id in the users collection is unique") - - type user struct { - Username string `json:"username" bson:",omitempty"` - Email string `json:"email" bson:",omitempty" validate:"required,email"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - ID string `json:"id,omitempty" bson:"_id,omitempty"` - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - user1 := user{ - TenantID: "1", - Email: "test1", - } - - user2 := user{ - TenantID: "1", - Email: "test2", - } - - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user2) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_14.go b/api/store/mongo/migrations/migration_14.go deleted file mode 100644 index 58f2e29b7ff..00000000000 --- a/api/store/mongo/migrations/migration_14.go +++ /dev/null @@ -1,118 +0,0 @@ -package migrations - -import ( - "context" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration14 = migrate.Migration{ - Version: 14, - Description: "Set the right tenant_id in the users collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 14, - "action": "Up", - }).Info("Applying migration") - type user struct { - Username string `json:"username" bson:",omitempty"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - ID string `json:"id,omitempty" bson:"_id,omitempty"` - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - if _, err := db.Collection("users").Indexes().DropOne(ctx, "tenant_id"); err != nil { - return err - } - if _, err := db.Collection("users").Indexes().DropOne(ctx, "session_record"); err != nil { - return err - } - - cursor, err := db.Collection("users").Find(ctx, bson.D{}) - if err != nil { - return err - } - defer cursor.Close(ctx) - for cursor.Next(ctx) { - user := new(user) - err := cursor.Decode(&user) - if err != nil { - return err - } - - type NamespaceSettings struct { - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - type Namespace struct { - Name string `json:"name" validate:"required,hostname_rfc1123,excludes=."` - Owner string `json:"owner"` - TenantID string `json:"tenant_id" bson:"tenant_id,omitempty"` - Members []interface{} `json:"members" bson:"members"` - Settings *NamespaceSettings `json:"settings"` - Devices int `json:"devices" bson:",omitempty"` - Sessions int `json:"sessions" bson:",omitempty"` - MaxDevices int `json:"max_devices" bson:"max_devices"` - DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - } - - settings := NamespaceSettings{ - SessionRecord: true, - } - - namespace := Namespace{ - Owner: user.ID, - Members: []interface{}{user.ID}, - TenantID: user.TenantID, - Name: user.Username, - Settings: &settings, - } - - _, err = db.Collection("namespaces").InsertOne(ctx, &namespace) - if err != nil { - return nil - } - - if _, err := db.Collection("users").UpdateOne(ctx, bson.M{"tenant_id": user.TenantID}, bson.M{"$unset": bson.M{"tenant_id": ""}}); err != nil { - return err - } - if _, err := db.Collection("users").UpdateOne(ctx, bson.M{"tenant_id": user.TenantID}, bson.M{"$unset": bson.M{"session_record": ""}}); err != nil { - return err - } - } - - return cursor.Err() - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 14, - "action": "Down", - }).Info("Applying migration") - cursor, err := db.Collection("namespaces").Find(ctx, bson.D{}) - if err != nil { - return err - } - defer cursor.Close(ctx) - for cursor.Next(ctx) { - namespace := new(models.Namespace) - err := cursor.Decode(&namespace) - if err != nil { - return err - } - - _, err = db.Collection("users").UpdateOne(ctx, bson.M{"_id": namespace.Owner}, bson.M{"$set": bson.M{"tenant": namespace.TenantID}}) - if err != nil { - return err - } - } - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_14_test.go b/api/store/mongo/migrations/migration_14_test.go deleted file mode 100644 index 012a82c272e..00000000000 --- a/api/store/mongo/migrations/migration_14_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration14(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - type user struct { - Username string `json:"username" bson:",omitempty"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - ID string `json:"id,omitempty" bson:"_id,omitempty"` - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - user1 := user{ - TenantID: "1", - ID: "1", - } - - type NamespaceSettings struct { - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - type Namespace struct { - Name string `json:"name" validate:"required,hostname_rfc1123,excludes=."` - Owner string `json:"owner"` - TenantID string `json:"tenant_id" bson:"tenant_id,omitempty"` - Members []interface{} `json:"members" bson:"members"` - Settings *NamespaceSettings `json:"settings"` - Devices int `json:"devices" bson:",omitempty"` - Sessions int `json:"sessions" bson:",omitempty"` - MaxDevices int `json:"max_devices" bson:"max_devices"` - DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - } - - ns := Namespace{ - Owner: "1", - TenantID: "1", - } - - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), ns) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:14]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"tenant_id": "1"}).Decode(&user1) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_15.go b/api/store/mongo/migrations/migration_15.go deleted file mode 100644 index 4d28469266d..00000000000 --- a/api/store/mongo/migrations/migration_15.go +++ /dev/null @@ -1,40 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration15 = migrate.Migration{ - Version: 15, - Description: "Set all names to lowercase in the namespaces", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 15, - "action": "Up", - }).Info("Applying migration") - _, err := db.Collection("namespaces").UpdateMany(ctx, bson.M{}, []bson.M{ - { - "$set": bson.M{ - "name": bson.M{"$toLower": "$name"}, - }, - }, - }) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 15, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_15_test.go b/api/store/mongo/migrations/migration_15_test.go deleted file mode 100644 index 4d80c08f937..00000000000 --- a/api/store/mongo/migrations/migration_15_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration15(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:14]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - ns := models.Namespace{ - Name: "Test", - } - - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), ns) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:15]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"name": "test"}).Decode(&ns) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_16.go b/api/store/mongo/migrations/migration_16.go deleted file mode 100644 index bfe5997facf..00000000000 --- a/api/store/mongo/migrations/migration_16.go +++ /dev/null @@ -1,40 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration16 = migrate.Migration{ - Version: 16, - Description: "Set the fingerprint as unique on public_keys collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 16, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"fingerprint", 1}}, - Options: options.Index().SetName("fingerprint").SetUnique(true), - } - _, err := db.Collection("public_keys").Indexes().CreateOne(ctx, mod) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 16, - "action": "Down", - }).Info("Applying migration") - _, err := db.Collection("public_keys").Indexes().DropOne(ctx, "fingerprint") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_16_test.go b/api/store/mongo/migrations/migration_16_test.go deleted file mode 100644 index 0ea40b8d8c0..00000000000 --- a/api/store/mongo/migrations/migration_16_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration16(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - pk1 := models.PublicKey{Fingerprint: "test"} - pk2 := models.PublicKey{Fingerprint: "test"} - - _, err := c.Database("test").Collection("public_keys").InsertOne(context.TODO(), pk1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), pk2) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:15]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:16]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.Error(t, err) -} diff --git a/api/store/mongo/migrations/migration_17.go b/api/store/mongo/migrations/migration_17.go deleted file mode 100644 index b7c7aae4df0..00000000000 --- a/api/store/mongo/migrations/migration_17.go +++ /dev/null @@ -1,177 +0,0 @@ -package migrations - -import ( - "context" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration17 = migrate.Migration{ - Version: 17, - Description: "Remove the namespaces, devices, session, connected_devices, firewall_rules and public_keys in the users", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 17, - "action": "Up", - }).Info("Applying migration") - cursor, err := db.Collection("namespaces").Find(ctx, bson.D{}) - if err != nil { - return err - } - - type NamespaceSettings struct { - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - type Namespace struct { - Name string `json:"name" validate:"required,hostname_rfc1123,excludes=."` - Owner string `json:"owner"` - TenantID string `json:"tenant_id" bson:"tenant_id,omitempty"` - Members []interface{} `json:"members" bson:"members"` - Settings *NamespaceSettings `json:"settings"` - Devices int `json:"devices" bson:",omitempty"` - Sessions int `json:"sessions" bson:",omitempty"` - MaxDevices int `json:"max_devices" bson:"max_devices"` - DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - } - - for cursor.Next(ctx) { - namespace := Namespace{} - - err = cursor.Decode(&namespace) - if err != nil { - return err - } - - objID, err := primitive.ObjectIDFromHex(namespace.Owner) - if err != nil { - return err - } - - user := new(models.User) - if err := db.Collection("users").FindOne(ctx, bson.M{"_id": objID}).Decode(&user); err != nil { - if err != mongo.ErrNoDocuments { - return err - } - if _, err := db.Collection("namespaces").DeleteOne(ctx, bson.M{"tenant_id": namespace.TenantID}); err != nil { - return err - } - } - } - - if err := cursor.Err(); err != nil { - return err - } - - cursor.Close(ctx) - - cursor, err = db.Collection("devices").Find(ctx, bson.D{}) - if err != nil { - return err - } - - for cursor.Next(ctx) { - device := new(models.Device) - err = cursor.Decode(&device) - if err != nil { - return err - } - - namespace := Namespace{} - if err := db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": device.TenantID}).Decode(&namespace); err != nil { - if err != mongo.ErrNoDocuments { - return err - } - if _, err := db.Collection("devices").DeleteOne(ctx, bson.M{"uid": device.UID}); err != nil { - return err - } - - if _, err := db.Collection("sessions").DeleteMany(ctx, bson.M{"device_uid": device.UID}); err != nil { - return err - } - - if _, err := db.Collection("connected_devices").DeleteMany(ctx, bson.M{"uid": device.UID}); err != nil { - return err - } - } - } - if err := cursor.Err(); err != nil { - return err - } - - cursor.Close(ctx) - - cursor, err = db.Collection("firewall_rules").Find(ctx, bson.D{}) - if err != nil { - return err - } - for cursor.Next(ctx) { - rule := new(models.FirewallRule) - err := cursor.Decode(&rule) - if err != nil { - return err - } - - namespace := Namespace{} - if err := db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": rule.TenantID}).Decode(&namespace); err != nil { - if err != mongo.ErrNoDocuments { - return err - } - if _, err := db.Collection("firewall_rules").DeleteOne(ctx, bson.M{"tenant_id": rule.TenantID}); err != nil { - return err - } - } - } - if err := cursor.Err(); err != nil { - return err - } - - cursor.Close(ctx) - - cursor, err = db.Collection("public_keys").Find(ctx, bson.D{}) - if err != nil { - return err - } - - for cursor.Next(ctx) { - key := new(models.PublicKey) - err := cursor.Decode(&key) - if err != nil { - return err - } - namespace := Namespace{} - if err := db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": key.TenantID}).Decode(&namespace); err != nil { - if err != mongo.ErrNoDocuments { - return err - } - if _, err := db.Collection("public_keys").DeleteOne(ctx, bson.M{"tenant_id": key.TenantID}); err != nil { - return err - } - } - } - if err := cursor.Err(); err != nil { - return err - } - - cursor.Close(ctx) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 17, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_17_test.go b/api/store/mongo/migrations/migration_17_test.go deleted file mode 100644 index f3995348e3d..00000000000 --- a/api/store/mongo/migrations/migration_17_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration17(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user := models.User{ - UserData: models.UserData{ - Name: "name", - Username: "username", - Email: "email", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - - type NamespaceSettings struct { - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - type Member struct { - ID string `json:"id" bson:"id"` - Name string `json:"name,omitempty" bson:"-"` - } - - type ConnectedDevice struct { - UID string `json:"uid"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - LastSeen time.Time `json:"last_seen" bson:"last_seen"` - } - - type Namespace struct { - Name string `json:"name" validate:"required,hostname_rfc1123,excludes=."` - Owner string `json:"owner"` - TenantID string `json:"tenant_id" bson:"tenant_id,omitempty"` - Members []Member `json:"members" bson:"members"` - Settings *NamespaceSettings `json:"settings"` - Devices int `json:"devices" bson:",omitempty"` - Sessions int `json:"sessions" bson:",omitempty"` - MaxDevices int `json:"max_devices" bson:"max_devices"` - DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - } - - namespace := Namespace{ - Name: "name", - Owner: "60df59bc65f88d92b974a60f", - TenantID: "tenant", - } - - device := models.Device{ - UID: "1", - TenantID: "tenant", - } - - session := models.Session{ - DeviceUID: "1", - } - - connectedDevice := ConnectedDevice{ - UID: "1", - } - - firewallRules := models.FirewallRule{ - TenantID: "tenant", - } - - pk := models.PublicKey{ - TenantID: "tenant", - } - - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("sessions").InsertOne(context.TODO(), session) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("connected_devices").InsertOne(context.TODO(), connectedDevice) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("firewall_rules").InsertOne(context.TODO(), firewallRules) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), pk) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:17]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": namespace.TenantID}).Decode(&namespace) - assert.Error(t, err) - - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"tenant_id": device.TenantID}).Decode(&device) - assert.Error(t, err) - - err = c.Database("test").Collection("sessions").FindOne(context.TODO(), bson.M{"device_uid": session.DeviceUID}).Decode(&session) - assert.Error(t, err) - - err = c.Database("test").Collection("connected_devices").FindOne(context.TODO(), bson.M{"uid": connectedDevice.UID}).Decode(&connectedDevice) - assert.Error(t, err) - - err = c.Database("test").Collection("firewall_rules").FindOne(context.TODO(), bson.M{"tenant_id": firewallRules.TenantID}).Decode(&firewallRules) - assert.Error(t, err) - - err = c.Database("test").Collection("public_keys").FindOne(context.TODO(), bson.M{"tenant_id": pk.TenantID}).Decode(&pk) - assert.Error(t, err) -} diff --git a/api/store/mongo/migrations/migration_18.go b/api/store/mongo/migrations/migration_18.go deleted file mode 100644 index aa6b910c130..00000000000 --- a/api/store/mongo/migrations/migration_18.go +++ /dev/null @@ -1,40 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/envs" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration18 = migrate.Migration{ - Version: 18, - Description: "Set the max_devices value in the namespaces collection to 3 on enterprise", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 18, - "action": "Up", - }).Info("Applying migration") - if envs.IsEnterprise() { - _, err := db.Collection("namespaces").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"max_devices": 3}}) - - return err - } - _, err := db.Collection("namespaces").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"max_devices": -1}}) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 18, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_18_test.go b/api/store/mongo/migrations/migration_18_test.go deleted file mode 100644 index c0eb33520fd..00000000000 --- a/api/store/mongo/migrations/migration_18_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration18(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - namespace := models.Namespace{ - Name: "name", - Owner: "60df59bc65f88d92b974a60f", - TenantID: "tenant", - } - - migrations := GenerateMigrations()[:17] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[17]) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&namespace) - assert.NoError(t, err) - assert.Equal(t, 3, namespace.MaxDevices) -} diff --git a/api/store/mongo/migrations/migration_19.go b/api/store/mongo/migrations/migration_19.go deleted file mode 100644 index 132f518eb41..00000000000 --- a/api/store/mongo/migrations/migration_19.go +++ /dev/null @@ -1,40 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration19 = migrate.Migration{ - Version: 19, - Description: "Remove all fingerprint associated with a public keys collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 19, - "action": "Up", - }).Info("Applying migration") - _, err := db.Collection("public_keys").Indexes().DropOne(ctx, "fingerprint") - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 19, - "action": "Down", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"fingerprint", 1}}, - Options: options.Index().SetName("fingerprint").SetUnique(true), - } - _, err := db.Collection("public_keys").Indexes().CreateOne(ctx, mod) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_19_test.go b/api/store/mongo/migrations/migration_19_test.go deleted file mode 100644 index 06778a6d020..00000000000 --- a/api/store/mongo/migrations/migration_19_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration19(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:19]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - type PublicKeyFields struct { - Name string `json:"name"` - Username string `json:"username" bson:"username,omitempty" validate:"regexp"` - Hostname string `json:"hostname" bson:"hostname" validate:"regexp"` - } - - type PublicKey struct { - Data []byte `json:"data"` - Fingerprint string `json:"fingerprint"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - PublicKeyFields `bson:",inline"` - } - - pk := PublicKey{ - Data: []byte("teste"), - TenantID: "tenant", - PublicKeyFields: PublicKeyFields{Name: "teste1", Hostname: ".*"}, - } - - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), pk) - assert.NoError(t, err) - - err = c.Database("test").Collection("public_keys").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&pk) - assert.NoError(t, err) - assert.Equal(t, pk.Fingerprint, "") -} diff --git a/api/store/mongo/migrations/migration_1_test.go b/api/store/mongo/migrations/migration_1_test.go deleted file mode 100644 index fe5169db2a8..00000000000 --- a/api/store/mongo/migrations/migration_1_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration1(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:1]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_2.go b/api/store/mongo/migrations/migration_2.go deleted file mode 100644 index a839311ef1f..00000000000 --- a/api/store/mongo/migrations/migration_2.go +++ /dev/null @@ -1,32 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration2 = migrate.Migration{ - Version: 2, - Description: "Rename the column device to device_uid", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 2, - "action": "Up", - }).Info("Applying migration") - - return renameField(db, "sessions", "device", "device_uid") - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 2, - "action": "Down", - }).Info("Applying migration") - - return renameField(db, "sessions", "device_uid", "device") - }), -} diff --git a/api/store/mongo/migrations/migration_20.go b/api/store/mongo/migrations/migration_20.go deleted file mode 100644 index 63913017b6c..00000000000 --- a/api/store/mongo/migrations/migration_20.go +++ /dev/null @@ -1,70 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration20 = migrate.Migration{ - Version: 20, - Description: "Change the model on db for firewall_rules collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 20, - "action": "Up", - }).Info("Applying migration") - cursor, err := db.Collection("firewall_rules").Find(ctx, bson.D{}) - if err != nil { - return err - } - - type firewallRule struct { - ID primitive.ObjectID `json:"id" bson:"_id"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - models.FirewallRuleFields `bson:",inline"` - } - - defer cursor.Close(ctx) - for cursor.Next(ctx) { - firewall := new(models.FirewallRule) - err := cursor.Decode(&firewall) - if err != nil { - return err - } - objID, err := primitive.ObjectIDFromHex(firewall.ID) - replacedRule := firewallRule{ - TenantID: firewall.TenantID, - ID: objID, - FirewallRuleFields: firewall.FirewallRuleFields, - } - - if err == nil { - if errDelete := db.Collection("firewall_rules").FindOneAndDelete(ctx, bson.M{"_id": firewall.ID}); errDelete.Err() != nil { - continue - } - - if _, err := db.Collection("firewall_rules").InsertOne(ctx, replacedRule); err != nil { - return err - } - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 20, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_20_test.go b/api/store/mongo/migrations/migration_20_test.go deleted file mode 100644 index a7efc67e028..00000000000 --- a/api/store/mongo/migrations/migration_20_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration20(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - type firewallRule struct { - ID primitive.ObjectID `json:"id" bson:"_id"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - models.FirewallRuleFields `bson:",inline"` - } - - fRule := firewallRule{ - TenantID: "tenant", - } - - _, err := c.Database("test").Collection("firewall_rules").InsertOne(context.TODO(), fRule) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[19:20]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - var migratedFirewallRules *models.FirewallRule - err = c.Database("test").Collection("firewall_rules").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&migratedFirewallRules) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_21.go b/api/store/mongo/migrations/migration_21.go deleted file mode 100644 index 5e2060b5544..00000000000 --- a/api/store/mongo/migrations/migration_21.go +++ /dev/null @@ -1,103 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration21 = migrate.Migration{ - Version: 21, - Description: "Remove all sessions, recorded_sessions for the devices", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 21, - "action": "Up", - }).Info("Applying migration") - cursor, err := db.Collection("sessions").Find(ctx, bson.D{}) - if err != nil { - return err - } - - for cursor.Next(ctx) { - session := new(models.Session) - err = cursor.Decode(&session) - if err != nil { - return err - } - - device := new(models.Device) - if err := db.Collection("devices").FindOne(ctx, bson.M{"uid": session.DeviceUID}).Decode(&device); err != nil { - if err != mongo.ErrNoDocuments { - return err - } - - if _, err := db.Collection("sessions").DeleteMany(ctx, bson.M{"device_uid": session.DeviceUID}); err != nil { - return err - } - } - } - - if err := cursor.Err(); err != nil { - return err - } - - cursor.Close(ctx) - - cursor, err = db.Collection("recorded_sessions").Find(ctx, bson.D{}) - if err != nil { - return err - } - - for cursor.Next(ctx) { - record := new(models.RecordedSession) - err = cursor.Decode(&record) - if err != nil { - return err - } - - namespace := new(models.Namespace) - if err := db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": record.TenantID}).Decode(&namespace); err != nil { - if err != mongo.ErrNoDocuments { - return err - } - - if _, err := db.Collection("recorded_sessions").DeleteMany(ctx, bson.M{"tenant_id": record.TenantID}); err != nil { - return err - } - } - session := new(models.Session) - if err := db.Collection("sessions").FindOne(ctx, bson.M{"uid": record.UID}).Decode(&session); err != nil { - if err != mongo.ErrNoDocuments { - return err - } - - if _, err := db.Collection("recorded_sessions").DeleteMany(ctx, bson.M{"uid": record.UID}); err != nil { - return err - } - } - - } - if err := cursor.Err(); err != nil { - return err - } - - cursor.Close(ctx) - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 21, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_21_test.go b/api/store/mongo/migrations/migration_21_test.go deleted file mode 100644 index 28521e43e06..00000000000 --- a/api/store/mongo/migrations/migration_21_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration21(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - device := models.Device{ - UID: "1", - } - - recordedSession := models.RecordedSession{ - TenantID: "tenant", - } - - session := models.Session{ - UID: "1", - } - - _, err := c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("recorded_sessions").InsertOne(context.TODO(), recordedSession) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("sessions").InsertOne(context.TODO(), session) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:21]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - var migratedRecordedSession *models.RecordedSession - err = c.Database("test").Collection("recorded_sessions").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&migratedRecordedSession) - assert.Error(t, err) - - var migratedSession *models.Session - err = c.Database("test").Collection("sessions").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&migratedSession) - assert.Error(t, err) -} diff --git a/api/store/mongo/migrations/migration_22.go b/api/store/mongo/migrations/migration_22.go deleted file mode 100644 index 16119e8e609..00000000000 --- a/api/store/mongo/migrations/migration_22.go +++ /dev/null @@ -1,80 +0,0 @@ -package migrations - -import ( - "context" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration22 = migrate.Migration{ - Version: 22, - Description: "Insert the user on the members group for the namespace", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 22, - "action": "Up", - }).Info("Applying migration") - cursor, err := db.Collection("namespaces").Find(ctx, bson.D{}) - if err != nil { - return err - } - - type NamespaceSettings struct { - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - type Namespace struct { - Name string `json:"name" validate:"required,hostname_rfc1123,excludes=."` - Owner string `json:"owner"` - TenantID string `json:"tenant_id" bson:"tenant_id,omitempty"` - Members []interface{} `json:"members" bson:"members"` - Settings *NamespaceSettings `json:"settings"` - Devices int `json:"devices" bson:",omitempty"` - Sessions int `json:"sessions" bson:",omitempty"` - MaxDevices int `json:"max_devices" bson:"max_devices"` - DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - } - - for cursor.Next(ctx) { - - namespace := Namespace{} - - err = cursor.Decode(&namespace) - if err != nil { - return err - } - - for _, memberID := range namespace.Members { - user := new(models.User) - objID, err := primitive.ObjectIDFromHex(memberID.(string)) - if err != nil { - return err - } - if err := db.Collection("users").FindOne(ctx, bson.M{"_id": objID}).Decode(&user); err != nil { - if _, err := db.Collection("namespaces").UpdateOne(ctx, bson.M{"tenant_id": namespace.TenantID}, bson.M{"$pull": bson.M{"members": memberID}}); err != nil { - return err - } - } - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 22, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_22_test.go b/api/store/mongo/migrations/migration_22_test.go deleted file mode 100644 index 320a170b192..00000000000 --- a/api/store/mongo/migrations/migration_22_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration22(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user := models.User{ - ID: "1", - } - - type NamespaceSettings struct { - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - type Namespace struct { - Name string `json:"name" validate:"required,hostname_rfc1123,excludes=."` - Owner string `json:"owner"` - TenantID string `json:"tenant_id" bson:"tenant_id,omitempty"` - Members []interface{} `json:"members" bson:"members"` - Settings *NamespaceSettings `json:"settings"` - Devices int `json:"devices" bson:",omitempty"` - Sessions int `json:"sessions" bson:",omitempty"` - MaxDevices int `json:"max_devices" bson:"max_devices"` - DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - } - - ns := Namespace{ - Name: "namespace", - Owner: "60df59bc65f88d92b974a60f", - TenantID: "tenant", - Members: []interface{}{"60df59bc65f88d92b974a60f"}, - MaxDevices: -1, - } - _, err := c.Database("test").Collection("devices").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), ns) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[21:22]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - var migratedNamespace *models.Namespace - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&migratedNamespace) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_23.go b/api/store/mongo/migrations/migration_23.go deleted file mode 100644 index ab401ab77d1..00000000000 --- a/api/store/mongo/migrations/migration_23.go +++ /dev/null @@ -1,56 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration23 = migrate.Migration{ - Version: 23, - Description: "change dot in namespace name and hostname to -", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 23, - "action": "Up", - }).Info("Applying migration") - if _, err := db.Collection("namespaces").UpdateMany(ctx, bson.D{}, []bson.M{ - { - "$set": bson.M{ - "name": bson.M{ - "$replaceAll": bson.M{"input": "$name", "find": ".", "replacement": "-"}, - }, - }, - }, - }); err != nil { - return err - } - - if _, err := db.Collection("devices").UpdateMany(ctx, bson.D{}, []bson.M{ - { - "$set": bson.M{ - "name": bson.M{ - "$replaceAll": bson.M{"input": "$name", "find": ".", "replacement": "-"}, - }, - }, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 23, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_23_test.go b/api/store/mongo/migrations/migration_23_test.go deleted file mode 100644 index d4c55e95e77..00000000000 --- a/api/store/mongo/migrations/migration_23_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -func TestMigration23(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:22] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(22), version) - - namespace := models.Namespace{ - Name: "namespace.test", - Owner: "owner", - TenantID: "tenant", - } - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - namespace = models.Namespace{ - Name: "namespacetest", - Owner: "owner", - TenantID: "tenant2", - } - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - device := models.Device{ - Name: "device.test", - UID: "uid", - Identity: &models.DeviceIdentity{MAC: "mac"}, - TenantID: "tenant", - LastSeen: clock.Now(), - } - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - device = models.Device{ - Name: "devicetest", - UID: "uid2", - Identity: &models.DeviceIdentity{MAC: "mac"}, - TenantID: "tenant2", - LastSeen: clock.Now(), - } - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - migration := GenerateMigrations()[22] - - migrates = migrate.NewMigrate(c.Database("test"), migration) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err = migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(23), version) - - var migratedNamespace *models.Namespace - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&migratedNamespace) - assert.NoError(t, err) - assert.Equal(t, "namespace-test", migratedNamespace.Name) - - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": "tenant2"}).Decode(&migratedNamespace) - assert.NoError(t, err) - assert.Equal(t, "namespacetest", migratedNamespace.Name) - - var migratedDevice *models.Device - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&migratedDevice) - assert.NoError(t, err) - assert.Equal(t, "device-test", migratedDevice.Name) - - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"tenant_id": "tenant2"}).Decode(&migratedDevice) - assert.NoError(t, err) - assert.Equal(t, "devicetest", migratedDevice.Name) - - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"name": "name.test"}).Decode(&models.Namespace{}) - assert.EqualError(t, mongo.ErrNoDocuments, err.Error()) -} diff --git a/api/store/mongo/migrations/migration_24.go b/api/store/mongo/migrations/migration_24.go deleted file mode 100644 index d3f8e8402d9..00000000000 --- a/api/store/mongo/migrations/migration_24.go +++ /dev/null @@ -1,49 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration24 = migrate.Migration{ - Version: 24, - Description: "convert names and emails to lowercase", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 24, - "action": "Up", - }).Info("Applying migration") - if _, err := db.Collection("users").UpdateMany(ctx, bson.D{}, []bson.M{ - { - "$set": bson.M{ - "username": bson.M{"$toLower": "$username"}, - "email": bson.M{"$toLower": "$email"}, - }, - }, - }); err != nil { - return err - } - - _, err := db.Collection("namespaces").UpdateMany(ctx, bson.D{}, []bson.M{ - { - "$set": bson.M{"name": bson.M{"$toLower": "$name"}}, - }, - }) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 24, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_24_test.go b/api/store/mongo/migrations/migration_24_test.go deleted file mode 100644 index 24148699420..00000000000 --- a/api/store/mongo/migrations/migration_24_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -func TestMigration24(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:23] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(23), version) - - user := models.User{ - UserData: models.UserData{ - Name: "name", - Username: "USERNAME", - Email: "EMAIL@MAIL.COM", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - user = models.User{ - UserData: models.UserData{ - Name: "name2", - Username: "Username2", - Email: "email@MAIL-TEST.com", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - user = models.User{ - UserData: models.UserData{ - Name: "name3", - Username: "username3", - Email: "email@e-mail.com", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - namespace := models.Namespace{ - Name: "NaMe", - Owner: "owner", - TenantID: "tenant", - } - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - namespace = models.Namespace{ - Name: "TEST", - Owner: "owner", - TenantID: "tenant2", - } - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - namespace = models.Namespace{ - Name: "teste", - Owner: "owner", - TenantID: "tenant3", - } - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - migration := GenerateMigrations()[23] - - migrates = migrate.NewMigrate(c.Database("test"), migration) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err = migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(24), version) - - var migratedUser *models.User - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"name": "name"}).Decode(&migratedUser) - assert.NoError(t, err) - assert.Equal(t, "username", migratedUser.Username) - assert.Equal(t, "email@mail.com", migratedUser.Email) - - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"name": "name2"}).Decode(&migratedUser) - assert.NoError(t, err) - assert.Equal(t, "username2", migratedUser.Username) - assert.Equal(t, "email@mail-test.com", migratedUser.Email) - - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"name": "name3"}).Decode(&migratedUser) - assert.NoError(t, err) - assert.Equal(t, "username3", migratedUser.Username) - assert.Equal(t, "email@e-mail.com", migratedUser.Email) - - var migratedNamespace *models.Namespace - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&migratedNamespace) - assert.NoError(t, err) - assert.Equal(t, "name", migratedNamespace.Name) - - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": "tenant2"}).Decode(&migratedNamespace) - assert.NoError(t, err) - assert.Equal(t, "test", migratedNamespace.Name) - - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": "tenant3"}).Decode(&migratedNamespace) - assert.NoError(t, err) - assert.Equal(t, "teste", migratedNamespace.Name) - - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"username": "USERNAME"}).Decode(&models.Namespace{}) - assert.EqualError(t, mongo.ErrNoDocuments, err.Error()) -} diff --git a/api/store/mongo/migrations/migration_25.go b/api/store/mongo/migrations/migration_25.go deleted file mode 100644 index fd5b01a68be..00000000000 --- a/api/store/mongo/migrations/migration_25.go +++ /dev/null @@ -1,64 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration25 = migrate.Migration{ - Version: 25, - Description: "remove devices with no namespaces related", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 25, - "action": "Up", - }).Info("Applying migration") - query := []bson.M{ - { - "$lookup": bson.M{ - "from": "namespaces", - "localField": "tenant_id", - "foreignField": "tenant_id", - "as": "namespace", - }, - }, - { - "$addFields": bson.M{ - "namespace": bson.M{"$anyElementTrue": []interface{}{"$namespace"}}, - }, - }, - - { - "$match": bson.M{ - "namespace": bson.M{"$eq": true}, - }, - }, - - { - "$unset": "namespace", - }, - - { - "$out": "devices", - }, - } - - _, err := db.Collection("devices").Aggregate(ctx, query) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 25, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_25_test.go b/api/store/mongo/migrations/migration_25_test.go deleted file mode 100644 index d5f0ac61931..00000000000 --- a/api/store/mongo/migrations/migration_25_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -func TestMigration25(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:24] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(24), version) - - namespace := models.Namespace{ - Name: "name", - Owner: "owner", - TenantID: "tenant", - } - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - device := models.Device{ - Name: "device", - UID: "uid", - Identity: &models.DeviceIdentity{MAC: "mac"}, - TenantID: "tenant", - LastSeen: time.Now(), - } - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - device = models.Device{ - Name: "device2", - UID: "uid2", - Identity: &models.DeviceIdentity{MAC: "mac"}, - TenantID: "tenant2", - LastSeen: time.Now(), - } - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - device = models.Device{ - Name: "device3", - UID: "uid3", - Identity: &models.DeviceIdentity{MAC: "mac"}, - TenantID: "tenant3", - LastSeen: time.Now(), - } - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - migration := GenerateMigrations()[24] - - migrates = migrate.NewMigrate(c.Database("test"), migration) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err = migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(25), version) - - var migratedDevice *models.Device - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"tenant_id": "tenant"}).Decode(&migratedDevice) - assert.NoError(t, err) - assert.Equal(t, "device", migratedDevice.Name) - - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"tenant_id": "tenant2"}).Decode(&models.Namespace{}) - assert.EqualError(t, mongo.ErrNoDocuments, err.Error()) - - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"tenant_id": "tenant3"}).Decode(&models.Namespace{}) - assert.EqualError(t, mongo.ErrNoDocuments, err.Error()) -} diff --git a/api/store/mongo/migrations/migration_26.go b/api/store/mongo/migrations/migration_26.go deleted file mode 100644 index fe58d5061ea..00000000000 --- a/api/store/mongo/migrations/migration_26.go +++ /dev/null @@ -1,58 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration26 = migrate.Migration{ - Version: 26, - Description: "Create collection used to recover password and activate account", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 26, - "action": "Up", - }).Info("Applying migration") - indexModel := mongo.IndexModel{ - Keys: bson.D{{"created_at", 1}}, - Options: options.Index().SetName("ttl").SetExpireAfterSeconds(86400), - } - _, err := db.Collection("recovery_tokens").Indexes().CreateOne(ctx, indexModel) - if err != nil { - return err - } - - indexModel = mongo.IndexModel{ - Keys: bson.D{{"token", 1}}, - Options: options.Index().SetName("token").SetUnique(false), - } - if _, err := db.Collection("recovery_tokens").Indexes().CreateOne(ctx, indexModel); err != nil { - return err - } - - indexModel = mongo.IndexModel{ - Keys: bson.D{{"user", 1}}, - Options: options.Index().SetName("user").SetUnique(false), - } - if _, err := db.Collection("recovery_tokens").Indexes().CreateOne(ctx, indexModel); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 26, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_26_test.go b/api/store/mongo/migrations/migration_26_test.go deleted file mode 100644 index 2bb27e89d8b..00000000000 --- a/api/store/mongo/migrations/migration_26_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/shellhub-io/shellhub/pkg/uuid" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration26(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:26] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(26), version) - - userToken := models.UserTokenRecover{ - Token: uuid.Generate(), - User: "user", - CreatedAt: clock.Now(), - } - _, err = c.Database("test").Collection("recovery_tokens").InsertOne(context.TODO(), userToken) - assert.NoError(t, err) - - var migratedUserToken *models.UserTokenRecover - err = c.Database("test").Collection("recovery_tokens").FindOne(context.TODO(), bson.M{"user": userToken.User}).Decode(&migratedUserToken) - assert.NoError(t, err) - assert.Equal(t, userToken.Token, migratedUserToken.Token) - - index := c.Database("test").Collection("recovery_tokens").Indexes() - - cursor, err := index.List(context.TODO()) - assert.NoError(t, err) - - var results []bson.M - err = cursor.All(context.TODO(), &results) - assert.NoError(t, err) - - keyField, ok := results[1]["key"].(primitive.M) - if !ok { - panic("type assertion failed") - } - - assert.Equal(t, int32(1), keyField["created_at"]) - - keyField, ok = results[2]["key"].(primitive.M) - if !ok { - panic("type assertion failed") - } - assert.Equal(t, int32(1), keyField["token"]) - - value, key := results[1]["expireAfterSeconds"] - assert.Equal(t, true, key) - assert.Equal(t, int32(86400), value) - - value, key = results[1]["name"] - assert.Equal(t, true, key) - assert.Equal(t, "ttl", value) - - value, key = results[2]["name"] - assert.Equal(t, true, key) - assert.Equal(t, "token", value) - - value, key = results[3]["name"] - assert.Equal(t, true, key) - assert.Equal(t, "user", value) -} diff --git a/api/store/mongo/migrations/migration_27.go b/api/store/mongo/migrations/migration_27.go deleted file mode 100644 index 40bd06c6673..00000000000 --- a/api/store/mongo/migrations/migration_27.go +++ /dev/null @@ -1,34 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration27 = migrate.Migration{ - Version: 27, - Description: "Set closed field in the sessions", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 27, - "action": "Up", - }).Info("Applying migration") - _, err := db.Collection("sessions").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"closed": true}}) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 27, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_27_test.go b/api/store/mongo/migrations/migration_27_test.go deleted file mode 100644 index ba16e44e48a..00000000000 --- a/api/store/mongo/migrations/migration_27_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration27(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:26]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - sessionsToBeMigrated := []struct { - UID string `json:"uid"` - }{ - { - UID: "uid1", - }, - { - UID: "uid2", - }, - { - UID: "uid3", - }, - } - - sessions := make([]interface{}, len(sessionsToBeMigrated)) - for i, v := range sessionsToBeMigrated { - sessions[i] = v - } - - _, err = c.Database("test").Collection("sessions").InsertMany(context.TODO(), sessions) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:27]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - migratedSessions := []models.Session{} - cur, err := c.Database("test").Collection("sessions").Find(context.TODO(), bson.D{}) - assert.NoError(t, err) - for cur.Next(context.TODO()) { - var ses models.Session - err := cur.Decode(&ses) - if err != nil { - panic(err.Error()) - } - migratedSessions = append(migratedSessions, ses) - } - - for _, ses := range migratedSessions { - assert.Equal(t, true, ses.Closed) - } -} diff --git a/api/store/mongo/migrations/migration_28.go b/api/store/mongo/migrations/migration_28.go deleted file mode 100644 index 0f333a62369..00000000000 --- a/api/store/mongo/migrations/migration_28.go +++ /dev/null @@ -1,41 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration28 = migrate.Migration{ - Version: 28, - Description: "add timestamps fields to collections users and devices", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 28, - "action": "Up", - }).Info("Applying migration") - if _, err := db.Collection("users").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"created_at": clock.Now()}}); err != nil { - return err - } - - if _, err := db.Collection("devices").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"created_at": clock.Now()}}); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 28, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_28_test.go b/api/store/mongo/migrations/migration_28_test.go deleted file mode 100644 index aaf8b89a284..00000000000 --- a/api/store/mongo/migrations/migration_28_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration28(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user := models.User{ - UserData: models.UserData{ - Name: "Test", - }, - } - - device := models.Device{ - UID: "1", - } - - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - migrations := GenerateMigrations()[27:28] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(28), version) - - var migratedUser *models.User - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"name": user.Name}).Decode(&migratedUser) - assert.NoError(t, err) - assert.NotNil(t, migratedUser.CreatedAt) - - var migratedDevice *models.Device - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"uid": device.UID}).Decode(&migratedDevice) - assert.NoError(t, err) - assert.NotNil(t, migratedDevice.CreatedAt) -} diff --git a/api/store/mongo/migrations/migration_29.go b/api/store/mongo/migrations/migration_29.go deleted file mode 100644 index 4b7c5028883..00000000000 --- a/api/store/mongo/migrations/migration_29.go +++ /dev/null @@ -1,36 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration29 = migrate.Migration{ - Version: 29, - Description: "add last_login field to collection users", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 29, - "action": "Up", - }).Info("Applying migration") - if _, err := db.Collection("users").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"last_login": nil}}); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 29, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_29_test.go b/api/store/mongo/migrations/migration_29_test.go deleted file mode 100644 index 12309f4ae95..00000000000 --- a/api/store/mongo/migrations/migration_29_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration29(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user := models.User{ - UserData: models.UserData{ - Name: "Test", - }, - } - - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - migrations := GenerateMigrations()[:29] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(29), version) - - var migratedUser *models.User - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"name": user.Name}).Decode(&migratedUser) - assert.NoError(t, err) - - index := c.Database("test").Collection("users").Indexes() - - cursor, err := index.List(context.TODO()) - assert.NoError(t, err) - - var results []bson.M - err = cursor.All(context.TODO(), &results) - assert.NoError(t, err) - - keyField, ok := results[1]["key"].(primitive.M) - if !ok { - panic("type assertion failed") - } - assert.Equal(t, nil, keyField["last_login"]) -} diff --git a/api/store/mongo/migrations/migration_2_test.go b/api/store/mongo/migrations/migration_2_test.go deleted file mode 100644 index baacffef39f..00000000000 --- a/api/store/mongo/migrations/migration_2_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration2(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - type Session struct { - UID string `json:"uid"` - DeviceUID string `json:"device,omitempty" bson:"device"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - Username string `json:"username"` - IPAddress string `json:"ip_address" bson:"ip_address"` - } - - session := Session{ - Username: "user", - UID: "uid", - DeviceUID: "deviceUID", - IPAddress: "0.0.0.0", - } - - _, err := c.Database("test").Collection("sessions").InsertOne(context.TODO(), session) - assert.NoError(t, err) - - var afterMigrationSession *Session - err = c.Database("test").Collection("sessions").FindOne(context.TODO(), bson.M{"device": "deviceUID"}).Decode(&afterMigrationSession) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:2]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("sessions").InsertOne(context.TODO(), session) - assert.NoError(t, err) - - var migratedSession *models.Session - err = c.Database("test").Collection("sessions").FindOne(context.TODO(), bson.M{"device_uid": "deviceUID"}).Decode(&migratedSession) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_3.go b/api/store/mongo/migrations/migration_3.go deleted file mode 100644 index 819e87541b3..00000000000 --- a/api/store/mongo/migrations/migration_3.go +++ /dev/null @@ -1,32 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration3 = migrate.Migration{ - Version: 3, - Description: "Rename the column attributes to info", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 3, - "action": "Up", - }).Info("Applying migration") - - return renameField(db, "devices", "attributes", "info") - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 3, - "action": "Down", - }).Info("Applying migration") - - return renameField(db, "devices", "info", "attributes") - }), -} diff --git a/api/store/mongo/migrations/migration_30.go b/api/store/mongo/migrations/migration_30.go deleted file mode 100644 index bcd5f6f888a..00000000000 --- a/api/store/mongo/migrations/migration_30.go +++ /dev/null @@ -1,36 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration30 = migrate.Migration{ - Version: 30, - Description: "add remote_addr field to collection devices", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 30, - "action": "Up", - }).Info("Applying migration") - if _, err := db.Collection("devices").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"remote_addr": ""}}); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 30, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_30_test.go b/api/store/mongo/migrations/migration_30_test.go deleted file mode 100644 index 3a538788d12..00000000000 --- a/api/store/mongo/migrations/migration_30_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration30(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - device := models.Device{ - UID: "1", - } - - _, err := c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - migrations := GenerateMigrations()[29:30] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(30), version) - - var migratedDevice *models.Device - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"uid": device.UID}).Decode(&migratedDevice) - assert.NoError(t, err) - assert.Equal(t, migratedDevice.RemoteAddr, "") -} diff --git a/api/store/mongo/migrations/migration_31.go b/api/store/mongo/migrations/migration_31.go deleted file mode 100644 index f3f8c3d23e1..00000000000 --- a/api/store/mongo/migrations/migration_31.go +++ /dev/null @@ -1,37 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration31 = migrate.Migration{ - Version: 31, - Description: "add last_login field to collection namespaces", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 31, - "action": "Up", - }).Info("Applying migration") - if _, err := db.Collection("namespaces").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"created_at": clock.Now()}}); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 31, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_31_test.go b/api/store/mongo/migrations/migration_31_test.go deleted file mode 100644 index e9157dba0e2..00000000000 --- a/api/store/mongo/migrations/migration_31_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration31(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - namespace := models.Namespace{ - Name: "Test", - } - - _, err := c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - - migrations := GenerateMigrations()[30:31] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(31), version) - - var migratedNamespace *models.Namespace - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"name": namespace.Name}).Decode(&migratedNamespace) - assert.NoError(t, err) - assert.NotNil(t, migratedNamespace.CreatedAt) -} diff --git a/api/store/mongo/migrations/migration_32.go b/api/store/mongo/migrations/migration_32.go deleted file mode 100644 index 7d97ab1f0b9..00000000000 --- a/api/store/mongo/migrations/migration_32.go +++ /dev/null @@ -1,36 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration32 = migrate.Migration{ - Version: 32, - Description: "add authenticated field to collection users", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 32, - "action": "Up", - }).Info("Applying migration") - if _, err := db.Collection("users").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"authenticated": true}}); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 32, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_32_test.go b/api/store/mongo/migrations/migration_32_test.go deleted file mode 100644 index 1fe63c9db05..00000000000 --- a/api/store/mongo/migrations/migration_32_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration32(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:31] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(31), version) - - user := models.User{ - UserData: models.UserData{ - Name: "name", - Username: "username", - Email: "email@mail.com", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - migration := GenerateMigrations()[31] - - migrates = migrate.NewMigrate(c.Database("test"), migration) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err = migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(32), version) -} diff --git a/api/store/mongo/migrations/migration_33.go b/api/store/mongo/migrations/migration_33.go deleted file mode 100644 index 802232add8b..00000000000 --- a/api/store/mongo/migrations/migration_33.go +++ /dev/null @@ -1,46 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration33 = migrate.Migration{ - Version: 33, - Description: "add tags field to collection devices", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 33, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"tags", 1}}, - Options: options.Index().SetName("tags").SetUnique(false), - } - _, err := db.Collection("devices").Indexes().CreateOne(ctx, mod) - if err != nil { - return err - } - - if _, err := db.Collection("devices").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"tags": []string{}}}); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 33, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_33_test.go b/api/store/mongo/migrations/migration_33_test.go deleted file mode 100644 index 9af00338cc8..00000000000 --- a/api/store/mongo/migrations/migration_33_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration33(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:32] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(32), version) - - device := models.Device{ - UID: "1", - TenantID: "tenant", - } - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), &device) - assert.NoError(t, err) - - migration := GenerateMigrations()[32:33] - - migrates = migrate.NewMigrate(c.Database("test"), migration...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err = migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(33), version) - - var migratedDevice *models.Device - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"uid": device.UID}).Decode(&migratedDevice) - assert.NoError(t, err) - assert.Equal(t, 0, len(migratedDevice.Tags)) -} diff --git a/api/store/mongo/migrations/migration_34.go b/api/store/mongo/migrations/migration_34.go deleted file mode 100644 index 081dc12de46..00000000000 --- a/api/store/mongo/migrations/migration_34.go +++ /dev/null @@ -1,41 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration34 = migrate.Migration{ - Version: 34, - Description: "create online index in devices collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 34, - "action": "Up", - }).Info("Applying migration") - - indexModel := mongo.IndexModel{ - Keys: bson.D{{"online", 1}}, - } - - _, err := db.Collection("devices").Indexes().CreateOne(ctx, indexModel) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 34, - "action": "Down", - }).Info("Applying migration") - - _, err := db.Collection("devices").Indexes().DropOne(ctx, "online") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_34_test.go b/api/store/mongo/migrations/migration_34_test.go deleted file mode 100644 index 5ecf6ba45a3..00000000000 --- a/api/store/mongo/migrations/migration_34_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration34(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:33] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(33), version) - - migrations = GenerateMigrations()[:34] - - migrates = migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err = migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(34), version) - - cursor, err := c.Database("test").Collection("devices").Indexes().List(context.TODO()) - assert.NoError(t, err) - - var results []bson.M - err = cursor.All(context.TODO(), &results) - assert.NoError(t, err) - - indexes := []string{} - - for _, index := range results { - if v, ok := index["key"].(primitive.M); ok { - for key := range v { - indexes = append(indexes, key) - } - } - } - - assert.Contains(t, indexes, "online") -} diff --git a/api/store/mongo/migrations/migration_35.go b/api/store/mongo/migrations/migration_35.go deleted file mode 100644 index 7c47f6abb3c..00000000000 --- a/api/store/mongo/migrations/migration_35.go +++ /dev/null @@ -1,32 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration35 = migrate.Migration{ - Version: 35, - Description: "Rename the column authenticated to confirmed", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 35, - "action": "Up", - }).Info("Applying migration") - - return renameField(db, "users", "authenticated", "confirmed") - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 35, - "action": "Down", - }).Info("Applying migration") - - return renameField(db, "users", "confirmed", "authenticated") - }), -} diff --git a/api/store/mongo/migrations/migration_35_test.go b/api/store/mongo/migrations/migration_35_test.go deleted file mode 100644 index 658b707f350..00000000000 --- a/api/store/mongo/migrations/migration_35_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration35(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - type User struct { - ID string `json:"id,omitempty" bson:"_id,omitempty"` - Namespaces int `json:"namespaces" bson:"namespaces,omitempty"` - Authenticated bool `json:"authenticated"` - UserData models.UserData `bson:",inline"` - } - - user := User{ - ID: "0", - Namespaces: 0, - Authenticated: true, - UserData: models.UserData{ - Name: "user", - Email: "test@shellhub.com", - Username: "username", - }, - } - - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - var afterMigrationUser *User - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"username": "username"}).Decode(&afterMigrationUser) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[34:35]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - var migratedUser *models.User - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"username": "username"}).Decode(&migratedUser) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_36.go b/api/store/mongo/migrations/migration_36.go deleted file mode 100644 index 2f07cbb781f..00000000000 --- a/api/store/mongo/migrations/migration_36.go +++ /dev/null @@ -1,40 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/envs" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration36 = migrate.Migration{ - Version: 36, - Description: "update max_devices to 3", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 36, - "action": "Up", - }).Info("Applying migration") - - if envs.IsCloud() { - if _, err := db.Collection("namespaces").UpdateMany(ctx, bson.M{"billing": nil}, bson.M{"$set": bson.M{"max_devices": 3}}); err != nil { - return err - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 36, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_36_test.go b/api/store/mongo/migrations/migration_36_test.go deleted file mode 100644 index 2f87a0e5868..00000000000 --- a/api/store/mongo/migrations/migration_36_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration36(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:35] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(35), version) - - cases := []struct { - description string - isCloud bool - toBeMigrated models.Namespace - migratedNamespace models.Namespace - expected int - }{ - { - description: "migrate cloud instance", - isCloud: true, - toBeMigrated: models.Namespace{ - Name: "ns1", - TenantID: "xxx1", - MaxDevices: -1, - }, - expected: 3, - }, - { - description: "do not apply migration for cloud disabled", - isCloud: false, - toBeMigrated: models.Namespace{ - Name: "ns2", - TenantID: "xxx2", - MaxDevices: -1, - }, - expected: -1, - }, - { - description: "avoid update active instance", - isCloud: true, - toBeMigrated: models.Namespace{ - Name: "ns3", - TenantID: "xxx3", - Billing: &models.Billing{ - SubscriptionID: "sub_123", - }, - MaxDevices: -1, - }, - expected: -1, - }, - } - - namespaces := make([]interface{}, len(cases)) - for i, v := range cases { - namespaces[i] = v.toBeMigrated - } - - _, err = c.Database("test").Collection("namespaces").InsertMany(context.TODO(), namespaces) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[35]) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err = migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(36), version) - - cur, err := c.Database("test").Collection("namespaces").Find(context.TODO(), bson.D{}) - assert.NoError(t, err) - - index := 0 - - for cur.Next(context.TODO()) { - var ns models.Namespace - err := cur.Decode(&ns) - if err != nil { - panic(err.Error()) - } - - cases[index].migratedNamespace = ns - index++ - } - - instance := envs.IsCloud() - - for _, tc := range cases { - if instance == tc.isCloud { - t.Run(tc.description, func(t *testing.T) { - assert.Equal(t, tc.expected, tc.migratedNamespace.MaxDevices) - }) - } - } -} diff --git a/api/store/mongo/migrations/migration_37.go b/api/store/mongo/migrations/migration_37.go deleted file mode 100644 index 7fa649e8f81..00000000000 --- a/api/store/mongo/migrations/migration_37.go +++ /dev/null @@ -1,125 +0,0 @@ -package migrations - -import ( - "context" - "time" - - "github.com/shellhub-io/shellhub/pkg/api/authorizer" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration37 = migrate.Migration{ - Version: 37, - Description: "Change member's role from array of ID to a list of members' object", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 37, - "action": "Up", - }).Info("Applying migration") - cursor, err := db.Collection("namespaces").Find(ctx, bson.D{}) - if err != nil { - return err - } - - type NamespaceSettings struct { - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - type Namespace struct { - Name string `json:"name" validate:"required,hostname_rfc1123,excludes=."` - Owner string `json:"owner"` - TenantID string `json:"tenant_id" bson:"tenant_id,omitempty"` - Members []interface{} `json:"members" bson:"members"` - Settings *NamespaceSettings `json:"settings"` - Devices int `json:"devices" bson:",omitempty"` - Sessions int `json:"sessions" bson:",omitempty"` - MaxDevices int `json:"max_devices" bson:"max_devices"` - DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - Billing interface{} `json:"billing" bson:"billing,omitempty"` - } - - for cursor.Next(ctx) { - namespace := new(Namespace) - err = cursor.Decode(&namespace) - if err != nil { - return err - } - - owner := namespace.Owner - members := namespace.Members - memberList := []models.Member{} - - for _, member := range members { - if owner != member { - m := models.Member{ - ID: member.(string), - Role: authorizer.RoleObserver, - } - - memberList = append(memberList, m) - } else if owner == member { - m := models.Member{ - ID: member.(string), - Role: authorizer.RoleOwner, - } - - memberList = append(memberList, m) - } - } - - if _, err := db.Collection("namespaces").UpdateOne(ctx, bson.M{"tenant_id": namespace.TenantID}, bson.M{"$set": bson.M{"members": memberList}}); err != nil { - return err - } - } - - if err := cursor.Err(); err != nil { - return err - } - - cursor.Close(ctx) - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 37, - "action": "Down", - }).Info("Applying migration") - cursor, err := db.Collection("namespaces").Find(ctx, bson.D{}) - if err != nil { - return err - } - - for cursor.Next(ctx) { - namespace := new(models.Namespace) - err = cursor.Decode(&namespace) - if err != nil { - return err - } - - var membersList []interface{} - for _, member := range namespace.Members { - membersList = append(membersList, member.ID) - } - - if _, err := db.Collection("namespaces").UpdateOne(ctx, bson.M{"tenant_id": namespace.TenantID}, bson.M{"$set": bson.M{"members": membersList}}); err != nil { - return err - } - } - - if err := cursor.Err(); err != nil { - return err - } - - cursor.Close(ctx) - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_37_test.go b/api/store/mongo/migrations/migration_37_test.go deleted file mode 100644 index 2a71b1a274a..00000000000 --- a/api/store/mongo/migrations/migration_37_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/api/authorizer" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration37(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user := models.User{ - ID: "60df59bc65f88d92b974a60f", - } - - type NamespaceSettings struct { - SessionRecord bool `json:"session_record" bson:"session_record,omitempty"` - } - - type Namespace struct { - Name string `json:"name" validate:"required,hostname_rfc1123,excludes=."` - Owner string `json:"owner"` - TenantID string `json:"tenant_id" bson:"tenant_id,omitempty"` - Members []interface{} `json:"members" bson:"members"` - Settings *NamespaceSettings `json:"settings"` - Devices int `json:"devices" bson:",omitempty"` - Sessions int `json:"sessions" bson:",omitempty"` - MaxDevices int `json:"max_devices" bson:"max_devices"` - DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - } - - ns := Namespace{ - Name: "userspace", - Owner: user.ID, - TenantID: "tenant", - Members: []interface{}{user.ID}, - Devices: -1, - } - migrations := GenerateMigrations()[36:37] - - _, err := c.Database("test").Collection("namespaces").InsertOne(context.TODO(), ns) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - migratedNamespace := &models.Namespace{} - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.D{{"tenant_id", "tenant"}}).Decode(migratedNamespace) - assert.NoError(t, err) - assert.Equal(t, []models.Member{{ID: user.ID, Role: authorizer.RoleOwner}}, migratedNamespace.Members) - - namespace := models.Namespace{ - Name: "userspace", - Owner: user.ID, - TenantID: "tenant", - Members: []models.Member{{ID: user.ID, Role: authorizer.RoleOwner}}, - Devices: -1, - } - - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace) - assert.NoError(t, err) - migrates = migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Down(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - migratedNamespaceDown := &Namespace{} - err = c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.D{{"tenant_id", namespace.TenantID}}).Decode(migratedNamespaceDown) - assert.NoError(t, err) - assert.Equal(t, []interface{}{user.ID}, migratedNamespaceDown.Members) -} diff --git a/api/store/mongo/migrations/migration_38.go b/api/store/mongo/migrations/migration_38.go deleted file mode 100644 index a2941020b46..00000000000 --- a/api/store/mongo/migrations/migration_38.go +++ /dev/null @@ -1,59 +0,0 @@ -package migrations - -import ( - "context" - "time" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration38 = migrate.Migration{ - Version: 38, - Description: "Set last_login to created_at, when created_at is a zero value", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 38, - "action": "Up", - }).Info("Applying migration") - zeroTime := time.Time{}.UTC() - _, err := db.Collection("users").Aggregate(ctx, - mongo.Pipeline{ - { - {"$match", bson.D{ - {"$and", bson.A{ - bson.M{"$or": bson.A{ - bson.M{"created_at": bson.M{"$eq": zeroTime}}, - bson.M{"created_at": bson.M{"$eq": nil}}, - }}, - bson.M{"last_login": bson.M{"$ne": zeroTime}}, - }}, - }}, - }, - { - {"$replaceRoot", bson.D{{"newRoot", bson.M{"$mergeObjects": bson.A{"$$ROOT", bson.M{"created_at": "$last_login"}}}}}}, - }, - { - {"$merge", bson.M{"into": "users"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 38, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_38_test.go b/api/store/mongo/migrations/migration_38_test.go deleted file mode 100644 index 28a16c5f798..00000000000 --- a/api/store/mongo/migrations/migration_38_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration38(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - type Expected struct { - CreatedAt string - LastLogin string - } - - migrations := GenerateMigrations()[37:38] - - timeZero := time.Time{} - timePast := time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC) - timeNow := time.Now().UTC() - convertDate := func(t time.Time) string { - return t.Format("2006-01-02 15:04:05.000") - } - - userNoCreatedAt := models.User{ - ID: "userNoCreatedID", - CreatedAt: timeZero, - LastLogin: timeNow, - UserData: models.UserData{ - Name: "userNoCreatedAt", - Email: "userNoCreatedAt@mail.com", - Username: "userNoCreatedAt", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - userWithCreatedAt := models.User{ - ID: "userWithCreatedID", - CreatedAt: timePast, - LastLogin: timeNow, - UserData: models.UserData{ - Name: "userWithCreatedAt", - Email: "userWithCreatedAt@mail.com", - Username: "userWithCreatedAt", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), userNoCreatedAt) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), userWithCreatedAt) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - description: "Executes migration when user's created_at property is empty", - Test: func(t *testing.T) { - t.Helper() - - var userMigrated *models.User - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.D{{"username", userNoCreatedAt.Username}}).Decode(&userMigrated) - assert.NoError(t, err) - assert.Equal(t, - Expected{CreatedAt: convertDate(userNoCreatedAt.LastLogin), LastLogin: convertDate(userNoCreatedAt.LastLogin)}, - Expected{CreatedAt: convertDate(userMigrated.CreatedAt), LastLogin: convertDate(userMigrated.LastLogin)}, - ) - }, - }, - { - description: "Does not execute migration when user's created_at is already set", - Test: func(t *testing.T) { - t.Helper() - - var userMigrated *models.User - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.D{{"username", userWithCreatedAt.Username}}).Decode(&userMigrated) - assert.NoError(t, err) - assert.Equal(t, - Expected{CreatedAt: convertDate(userWithCreatedAt.CreatedAt), LastLogin: convertDate(userWithCreatedAt.LastLogin)}, - Expected{CreatedAt: convertDate(userMigrated.CreatedAt), LastLogin: convertDate(userMigrated.LastLogin)}, - ) - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, tc.Test) - } -} diff --git a/api/store/mongo/migrations/migration_39.go b/api/store/mongo/migrations/migration_39.go deleted file mode 100644 index d3ceb48500b..00000000000 --- a/api/store/mongo/migrations/migration_39.go +++ /dev/null @@ -1,45 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration39 = migrate.Migration{ - Version: 39, - Description: "remove online index from devices collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 39, - "action": "Up", - }).Info("Applying migration") - - if _, err := db.Collection("devices").Indexes().DropOne(ctx, "online_1"); err != nil { - return err - } - - _, err := db.Collection("devices").UpdateMany(ctx, bson.M{}, bson.M{"$unset": bson.M{"online": nil}}) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 39, - "action": "Down", - }).Info("Applying migration") - - indexModel := mongo.IndexModel{ - Keys: bson.D{{"online", 1}}, - } - - _, err := db.Collection("devices").Indexes().CreateOne(ctx, indexModel) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_39_test.go b/api/store/mongo/migrations/migration_39_test.go deleted file mode 100644 index 62b7aaa9f23..00000000000 --- a/api/store/mongo/migrations/migration_39_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration39(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrations := GenerateMigrations()[:39] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(39), version) -} diff --git a/api/store/mongo/migrations/migration_3_test.go b/api/store/mongo/migrations/migration_3_test.go deleted file mode 100644 index 7e450256b4c..00000000000 --- a/api/store/mongo/migrations/migration_3_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration3(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - type Device struct { - Attributes *models.DeviceInfo `json:"attributes"` - } - - device := Device{ - Attributes: &models.DeviceInfo{ - ID: "1", - }, - } - - _, err := c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - var afterMigrateDevice *models.Session - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"attributes": &models.DeviceInfo{ID: "1"}}).Decode(&afterMigrateDevice) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:3]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - var migratedDevice *models.Device - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"info": &models.DeviceInfo{ID: "1"}}).Decode(&migratedDevice) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_4.go b/api/store/mongo/migrations/migration_4.go deleted file mode 100644 index a5ef1b220e0..00000000000 --- a/api/store/mongo/migrations/migration_4.go +++ /dev/null @@ -1,32 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration4 = migrate.Migration{ - Version: 4, - Description: "Rename the column version to info.version", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 4, - "action": "Up", - }).Info("Applying migration") - - return renameField(db, "devices", "version", "info.version") - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 4, - "action": "Down", - }).Info("Applying migration") - - return renameField(db, "devices", "info.version", "version") - }), -} diff --git a/api/store/mongo/migrations/migration_40.go b/api/store/mongo/migrations/migration_40.go deleted file mode 100644 index 149d2f1952c..00000000000 --- a/api/store/mongo/migrations/migration_40.go +++ /dev/null @@ -1,58 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration40 = migrate.Migration{ - Version: 40, - Description: "remove online index from devices collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 40, - "action": "Up", - }).Info("Applying migration") - - if _, err := db.Collection("connected_devices").Indexes().DropOne(ctx, "last_seen"); err != nil { - return err - } - - mod := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(60), - } - if _, err := db.Collection("connected_devices").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 40, - "action": "Down", - }).Info("Applying migration") - - if _, err := db.Collection("connected_devices").Indexes().DropOne(ctx, "last_seen"); err != nil { - return err - } - - mod := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(30), - } - if _, err := db.Collection("connected_devices").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_40_test.go b/api/store/mongo/migrations/migration_40_test.go deleted file mode 100644 index a249c469f3b..00000000000 --- a/api/store/mongo/migrations/migration_40_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -func TestMigration40(t *testing.T) { - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 40", - func(t *testing.T) { - t.Helper() - - oldIndex := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(30), - } - - newIndex := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(30), - } - - _, err := c.Database("test").Collection("connected_devices").Indexes().CreateOne(context.TODO(), oldIndex) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[39:40]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - _, err = c.Database("test").Collection("connected_devices").Indexes().DropOne(context.TODO(), "last_seen") - assert.NoError(t, err) - - _, err = c.Database("test").Collection("connected_devices").Indexes().CreateOne(context.TODO(), newIndex) - assert.NoError(t, err) - - const Expected = 1 - list, err := c.Database("test").Collection("connected_devices").Indexes().ListSpecifications(context.TODO()) - assert.NoError(t, err) - - assert.Equal(t, newIndex.Options.ExpireAfterSeconds, list[Expected].ExpireAfterSeconds) - }, - }, - { - "Success to apply down on migration 40", - func(t *testing.T) { - t.Helper() - - oldIndex := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(30), - } - - _, err := c.Database("test").Collection("connected_devices").Indexes().CreateOne(context.TODO(), oldIndex) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[39:40]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - assert.NoError(t, err) - _, err = c.Database("test").Collection("connected_devices").Indexes().DropOne(context.TODO(), "last_seen") - assert.NoError(t, err) - - _, err = c.Database("test").Collection("connected_devices").Indexes().CreateOne(context.TODO(), oldIndex) - assert.NoError(t, err) - - const Expected = 1 - list, err := c.Database("test").Collection("connected_devices").Indexes().ListSpecifications(context.TODO()) - assert.NoError(t, err) - - assert.Equal(t, oldIndex.Options.ExpireAfterSeconds, list[Expected].ExpireAfterSeconds) - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_41.go b/api/store/mongo/migrations/migration_41.go deleted file mode 100644 index 22d566b54bc..00000000000 --- a/api/store/mongo/migrations/migration_41.go +++ /dev/null @@ -1,58 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration41 = migrate.Migration{ - Version: 41, - Description: "update online index from devices collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 41, - "action": "Up", - }).Info("Applying migration") - - if _, err := db.Collection("connected_devices").Indexes().DropOne(ctx, "last_seen"); err != nil { - return err - } - - mod := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(120), - } - if _, err := db.Collection("connected_devices").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 41, - "action": "Down", - }).Info("Applying migration") - - if _, err := db.Collection("connected_devices").Indexes().DropOne(ctx, "last_seen"); err != nil { - return err - } - - mod := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(60), - } - if _, err := db.Collection("connected_devices").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_41_test.go b/api/store/mongo/migrations/migration_41_test.go deleted file mode 100644 index 37d25e1c6df..00000000000 --- a/api/store/mongo/migrations/migration_41_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -func TestMigration41(t *testing.T) { - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 41", - func(t *testing.T) { - t.Helper() - - oldIndex := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(60), - } - newIndex := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(60), - } - _, err := c.Database("test").Collection("connected_devices").Indexes().CreateOne(context.TODO(), oldIndex) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[40:41]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - _, err = c.Database("test").Collection("connected_devices").Indexes().DropOne(context.TODO(), "last_seen") - assert.NoError(t, err) - - _, err = c.Database("test").Collection("connected_devices").Indexes().CreateOne(context.TODO(), newIndex) - assert.NoError(t, err) - - const Expected = 1 - list, err := c.Database("test").Collection("connected_devices").Indexes().ListSpecifications(context.TODO()) - assert.NoError(t, err) - - assert.Equal(t, newIndex.Options.ExpireAfterSeconds, list[Expected].ExpireAfterSeconds) - }, - }, - { - "Success to apply down on migration 41", - func(t *testing.T) { - t.Helper() - - oldIndex := mongo.IndexModel{ - Keys: bson.D{{"last_seen", 1}}, - Options: options.Index().SetName("last_seen").SetExpireAfterSeconds(60), - } - _, err := c.Database("test").Collection("connected_devices").Indexes().CreateOne(context.TODO(), oldIndex) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[40:41]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - assert.NoError(t, err) - _, err = c.Database("test").Collection("connected_devices").Indexes().DropOne(context.TODO(), "last_seen") - assert.NoError(t, err) - - _, err = c.Database("test").Collection("connected_devices").Indexes().CreateOne(context.TODO(), oldIndex) - assert.NoError(t, err) - - const Expected = 1 - list, err := c.Database("test").Collection("connected_devices").Indexes().ListSpecifications(context.TODO()) - assert.NoError(t, err) - - assert.Equal(t, oldIndex.Options.ExpireAfterSeconds, list[Expected].ExpireAfterSeconds) - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_42.go b/api/store/mongo/migrations/migration_42.go deleted file mode 100644 index 31ac404144f..00000000000 --- a/api/store/mongo/migrations/migration_42.go +++ /dev/null @@ -1,73 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration42 = migrate.Migration{ - Version: 42, - Description: "change hostname to filter hostname", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 42, - "action": "Up", - }).Info("Applying migration") - - _, err := db.Collection("public_keys").Aggregate(ctx, - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$set", bson.M{"filter.hostname": "$hostname"}}, - }, - { - {"$unset", "hostname"}, - }, - { - {"$merge", bson.M{"into": "public_keys", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 42, - "action": "Down", - }).Info("Applying migration") - - _, err := db.Collection("public_keys").Aggregate(ctx, - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$set", bson.M{"hostname": "$filter.hostname"}}, - }, - { - {"$unset", "filter"}, - }, - { - {"$merge", bson.M{"into": "public_keys", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_42_test.go b/api/store/mongo/migrations/migration_42_test.go deleted file mode 100644 index b284aa0dbeb..00000000000 --- a/api/store/mongo/migrations/migration_42_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration42(t *testing.T) { - type PublicKeyFields struct { - Name string `json:"name"` - Username string `json:"username" bson:"username,omitempty" validate:"regexp"` - Hostname string `json:"hostname" bson:"hostname" validate:"regexp"` - } - - type PublicKey struct { - Data []byte `json:"data"` - Fingerprint string `json:"fingerprint"` - CreatedAt time.Time `json:"created_at" bson:"created_at"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - PublicKeyFields `bson:",inline"` - } - - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 42", - func(t *testing.T) { - t.Helper() - - keyOld := &PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: PublicKeyFields{ - Name: "key", - Username: ".*", - Hostname: ".*", - }, - } - - keyNew := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: ".*", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - }, - }, - } - - _, err := c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyOld) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[41:42]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - key := new(models.PublicKey) - result := c.Database("test").Collection("public_keys").FindOne(context.TODO(), bson.M{"tenant_id": keyOld.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - assert.Equal(t, keyNew, key) - }, - }, - { - "Success to apply down on migration 42", - func(t *testing.T) { - t.Helper() - - keyOld := &PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: PublicKeyFields{ - Name: "key", - Username: ".*", - Hostname: ".*", - }, - } - - keyNew := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: ".*", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - }, - }, - } - - _, err := c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyOld) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[41:42]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - key := new(PublicKey) - result := c.Database("test").Collection("public_keys").FindOne(context.TODO(), bson.M{"tenant_id": keyNew.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - assert.Equal(t, keyOld, key) - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_43.go b/api/store/mongo/migrations/migration_43.go deleted file mode 100644 index 1ccb3529a2e..00000000000 --- a/api/store/mongo/migrations/migration_43.go +++ /dev/null @@ -1,73 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration43 = migrate.Migration{ - Version: 43, - Description: "add tags field to firewall_rules collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 43, - "action": "Up", - }).Info("Applying migration") - - _, err := db.Collection("firewall_rules").Aggregate(ctx, - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$set", bson.M{"filter.hostname": "$hostname"}}, - }, - { - {"$unset", "hostname"}, - }, - { - {"$merge", bson.M{"into": "firewall_rules", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 43, - "action": "Down", - }).Info("Applying migration") - - _, err := db.Collection("firewall_rules").Aggregate(ctx, - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$set", bson.M{"hostname": "$filter.hostname"}}, - }, - { - {"$unset", "filter"}, - }, - { - {"$merge", bson.M{"into": "firewall_rules", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_43_test.go b/api/store/mongo/migrations/migration_43_test.go deleted file mode 100644 index 3b9d0dc9b67..00000000000 --- a/api/store/mongo/migrations/migration_43_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration43(t *testing.T) { - type FirewallRuleFields struct { - Priority int `json:"priority"` - Action string `json:"action" validate:"required,oneof=allow deny"` - Active bool `json:"active"` - SourceIP string `json:"source_ip" bson:"source_ip" validate:"required,regexp"` - Username string `json:"username" validate:"required,regexp"` - Hostname string `json:"hostname" validate:"required,regexp"` - } - - type FirewallRule struct { - ID string `json:"id,omitempty" bson:"_id,omitempty"` - TenantID string `json:"tenant_id" bson:"tenant_id"` - FirewallRuleFields `bson:",inline"` - } - - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 43", - func(t *testing.T) { - t.Helper() - - ruleOld := &FirewallRule{ - ID: "ruleID", - TenantID: "tenant", - FirewallRuleFields: FirewallRuleFields{ - Hostname: ".*", - }, - } - - ruleNew := &models.FirewallRule{ - ID: "ruleID", - TenantID: "tenant", - FirewallRuleFields: models.FirewallRuleFields{ - Filter: models.FirewallFilter{ - Hostname: ".*", - }, - }, - } - - _, err := c.Database("test").Collection("firewall_rules").InsertOne(context.TODO(), ruleOld) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[42:43]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - rule := new(models.FirewallRule) - result := c.Database("test").Collection("firewall_rules").FindOne(context.TODO(), bson.M{"tenant_id": ruleOld.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(rule) - assert.NoError(t, err) - - assert.Equal(t, ruleNew, rule) - }, - }, - { - "Success to apply down on migration 43", - func(t *testing.T) { - t.Helper() - - ruleOld := &FirewallRule{ - ID: "ruleID", - TenantID: "tenant", - FirewallRuleFields: FirewallRuleFields{ - Hostname: ".*", - }, - } - - ruleNew := &models.FirewallRule{ - ID: "ruleID", - TenantID: "tenant", - FirewallRuleFields: models.FirewallRuleFields{ - Filter: models.FirewallFilter{ - Hostname: ".*", - }, - }, - } - - _, err := c.Database("test").Collection("firewall_rules").InsertOne(context.TODO(), ruleOld) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[42:43]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - rule := new(FirewallRule) - result := c.Database("test").Collection("firewall_rules").FindOne(context.TODO(), bson.M{"tenant_id": ruleNew.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(rule) - assert.NoError(t, err) - - assert.Equal(t, ruleOld, rule) - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_44.go b/api/store/mongo/migrations/migration_44.go deleted file mode 100644 index 91909f8bf33..00000000000 --- a/api/store/mongo/migrations/migration_44.go +++ /dev/null @@ -1,65 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration44 = migrate.Migration{ - Version: 44, - Description: "remove duplicated tags on public keys", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 44, - "action": "Up", - }).Info("Applying migration") - - _, err := db.Collection("public_keys").Aggregate(ctx, - mongo.Pipeline{ - { - {"$match", bson.M{"filter.tags": bson.M{"$exists": true}}}, - }, - { - {"$unwind", "$filter.tags"}, - }, - { - {"$group", bson.M{ - "_id": "$_id", - "body": bson.M{"$push": "$$ROOT"}, - "tags": bson.M{ - "$addToSet": "$filter.tags", - }, - "count": bson.M{ - "$sum": 1, - }, - }}, - }, - { - {"$replaceRoot", bson.D{{"newRoot", bson.M{"$mergeObjects": bson.A{bson.M{"$arrayElemAt": bson.A{"$body", 0}}, bson.M{"filter": bson.M{"tags": "$tags"}}, bson.M{"_id": "$_id"}}}}}}, - }, - { - {"$merge", bson.M{"into": "public_keys", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 44, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_44_test.go b/api/store/mongo/migrations/migration_44_test.go deleted file mode 100644 index 0abbc3d1021..00000000000 --- a/api/store/mongo/migrations/migration_44_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package migrations - -import ( - "context" - "sort" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration44(t *testing.T) { - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 44 when public key tags are duplicated", - func(t *testing.T) { - t.Helper() - - keyTagDuplicated := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag2"}, - }, - }, - } - - keyTagWithoutDuplication := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2"}, - }, - }, - } - - keyTagNoDuplicated := &models.PublicKey{ - Fingerprint: "fingerprint1", - TenantID: "tenant1", - PublicKeyFields: models.PublicKeyFields{ - Name: "key1", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag3"}, - }, - }, - } - - keyHostname := &models.PublicKey{ - Fingerprint: "fingerprint2", - TenantID: "tenant2", - PublicKeyFields: models.PublicKeyFields{ - Name: "key2", - Username: ".*", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - }, - }, - } - - _, err := c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyTagDuplicated) - assert.NoError(t, err) - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyTagNoDuplicated) - assert.NoError(t, err) - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyHostname) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[43:44]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - key := new(models.PublicKey) - result := c.Database("test").Collection("public_keys").FindOne(context.TODO(), bson.M{"tenant_id": keyTagDuplicated.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - sort.Strings(key.Filter.Tags) - - assert.Equal(t, keyTagWithoutDuplication, key) - }, - }, - { - "Success to apply up on migration 44 when public key tags are not duplicated", - func(t *testing.T) { - t.Helper() - - keyTagDuplicated := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag2"}, - }, - }, - } - - keyTagNoDuplicated := &models.PublicKey{ - Fingerprint: "fingerprint1", - TenantID: "tenant1", - PublicKeyFields: models.PublicKeyFields{ - Name: "key1", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag3"}, - }, - }, - } - - keyHostname := &models.PublicKey{ - Fingerprint: "fingerprint2", - TenantID: "tenant2", - PublicKeyFields: models.PublicKeyFields{ - Name: "key2", - Username: ".*", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - }, - }, - } - - _, err := c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyTagDuplicated) - assert.NoError(t, err) - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyTagNoDuplicated) - assert.NoError(t, err) - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyHostname) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[43:44]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - key := new(models.PublicKey) - result := c.Database("test").Collection("public_keys").FindOne(context.TODO(), bson.M{"tenant_id": keyTagNoDuplicated.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - sort.Strings(key.Filter.Tags) - - assert.Equal(t, keyTagNoDuplicated, key) - }, - }, - { - "Success to apply up on migration 44 when public key has hostname", - func(t *testing.T) { - t.Helper() - - keyTagDuplicated := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag2"}, - }, - }, - } - - keyTagNoDuplicated := &models.PublicKey{ - Fingerprint: "fingerprint1", - TenantID: "tenant1", - PublicKeyFields: models.PublicKeyFields{ - Name: "key1", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag3"}, - }, - }, - } - - keyHostname := &models.PublicKey{ - Fingerprint: "fingerprint2", - TenantID: "tenant2", - PublicKeyFields: models.PublicKeyFields{ - Name: "key2", - Username: ".*", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - }, - }, - } - - _, err := c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyTagDuplicated) - assert.NoError(t, err) - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyTagNoDuplicated) - assert.NoError(t, err) - _, err = c.Database("test").Collection("public_keys").InsertOne(context.TODO(), keyHostname) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[43:44]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - key := new(models.PublicKey) - result := c.Database("test").Collection("public_keys").FindOne(context.TODO(), bson.M{"tenant_id": keyHostname.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - assert.Equal(t, keyHostname, key) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_45.go b/api/store/mongo/migrations/migration_45.go deleted file mode 100644 index 5b75e3ff2c6..00000000000 --- a/api/store/mongo/migrations/migration_45.go +++ /dev/null @@ -1,65 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration45 = migrate.Migration{ - Version: 45, - Description: "remove duplicated tags on firewall rules", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 45, - "action": "Up", - }).Info("Applying migration") - - _, err := db.Collection("firewall_rules").Aggregate(ctx, - mongo.Pipeline{ - { - {"$match", bson.M{"filter.tags": bson.M{"$exists": true}}}, - }, - { - {"$unwind", "$filter.tags"}, - }, - { - {"$group", bson.M{ - "_id": "$_id", - "body": bson.M{"$push": "$$ROOT"}, - "tags": bson.M{ - "$addToSet": "$filter.tags", - }, - "count": bson.M{ - "$sum": 1, - }, - }}, - }, - { - {"$replaceRoot", bson.D{{"newRoot", bson.M{"$mergeObjects": bson.A{bson.M{"$arrayElemAt": bson.A{"$body", 0}}, bson.M{"filter": bson.M{"tags": "$tags"}}, bson.M{"_id": "$_id"}}}}}}, - }, - { - {"$merge", bson.M{"into": "firewall_rules", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 45, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_45_test.go b/api/store/mongo/migrations/migration_45_test.go deleted file mode 100644 index 4e6af62bdd2..00000000000 --- a/api/store/mongo/migrations/migration_45_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package migrations - -import ( - "context" - "sort" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration45(t *testing.T) { - ruleTagDuplicated := &models.FirewallRule{ - ID: "id", - TenantID: "tenant", - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 1, - Action: "", - Active: true, - SourceIP: ".*", - Username: ".*", - Filter: models.FirewallFilter{ - Tags: []string{"tag1", "tag2", "tag2"}, - }, - }, - } - - ruleTagWithoutDuplication := &models.FirewallRule{ - ID: "id", - TenantID: "tenant", - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 1, - Action: "", - Active: true, - SourceIP: ".*", - Username: ".*", - Filter: models.FirewallFilter{ - Tags: []string{"tag1", "tag2"}, - }, - }, - } - - ruleTagNoDuplicated := &models.FirewallRule{ - ID: "id1", - TenantID: "tenant1", - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 1, - Action: "", - Active: true, - SourceIP: ".*", - Username: ".*", - Filter: models.FirewallFilter{ - Tags: []string{"tag1", "tag3"}, - }, - }, - } - - ruleHostname := &models.FirewallRule{ - ID: "id2", - TenantID: "tenant2", - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 1, - Action: "", - Active: true, - SourceIP: ".*", - Username: ".*", - Filter: models.FirewallFilter{ - Hostname: ".*", - }, - }, - } - - _, err := c.Database("test").Collection("firewall_rules").InsertOne(context.TODO(), ruleTagDuplicated) - assert.NoError(t, err) - _, err = c.Database("test").Collection("firewall_rules").InsertOne(context.TODO(), ruleTagNoDuplicated) - assert.NoError(t, err) - _, err = c.Database("test").Collection("firewall_rules").InsertOne(context.TODO(), ruleHostname) - assert.NoError(t, err) - - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 45 when firewall rule tags are duplicated", - func(t *testing.T) { - t.Helper() - - migrations := GenerateMigrations()[44:45] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - rule := new(models.FirewallRule) - result := c.Database("test").Collection("firewall_rules").FindOne(context.TODO(), bson.M{"tenant_id": ruleTagDuplicated.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(rule) - assert.NoError(t, err) - - sort.Strings(rule.Filter.Tags) - - assert.Equal(t, ruleTagWithoutDuplication, rule) - }, - }, - { - "Success to apply up on migration 45 when firewall rule tags are not duplicated", - func(t *testing.T) { - t.Helper() - - migrations := GenerateMigrations()[44:45] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - rule := new(models.FirewallRule) - result := c.Database("test").Collection("firewall_rules").FindOne(context.TODO(), bson.M{"tenant_id": ruleTagNoDuplicated.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(rule) - assert.NoError(t, err) - - sort.Strings(rule.Filter.Tags) - - assert.Equal(t, ruleTagNoDuplicated, rule) - }, - }, - { - "Success to apply up on migration 45 when firewall rule has hostname", - func(t *testing.T) { - t.Helper() - - migrations := GenerateMigrations()[44:45] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - rule := new(models.FirewallRule) - result := c.Database("test").Collection("firewall_rules").FindOne(context.TODO(), bson.M{"tenant_id": ruleHostname.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(rule) - assert.NoError(t, err) - - assert.Equal(t, ruleHostname, rule) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - }) - } -} diff --git a/api/store/mongo/migrations/migration_46.go b/api/store/mongo/migrations/migration_46.go deleted file mode 100644 index 80a063cd83f..00000000000 --- a/api/store/mongo/migrations/migration_46.go +++ /dev/null @@ -1,67 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration46 = migrate.Migration{ - Version: 46, - Description: "change public keys with empty username in favor of .* regexp", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 46, - "action": "Up", - }).Info("Applying migration") - - _, err := db.Collection("public_keys").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{"username": ""}}, - }, - { - {"$set", bson.M{"username": ".*"}}, - }, - { - {"$merge", bson.M{"into": "public_keys", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 46, - "action": "Down", - }).Info("Applying migration") - - _, err := db.Collection("public_keys").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{"username": ".*"}}, - }, - { - {"$set", bson.M{"username": ""}}, - }, - { - {"$merge", bson.M{"into": "public_keys", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_46_test.go b/api/store/mongo/migrations/migration_46_test.go deleted file mode 100644 index 6fc702b9d89..00000000000 --- a/api/store/mongo/migrations/migration_46_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package migrations - -import ( - "context" - "sort" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration46(t *testing.T) { - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 46", - func(t *testing.T) { - t.Helper() - - keyUsernameEmpty := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: "", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag3"}, - }, - }, - } - - keyUsernameRegexp := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag3"}, - }, - }, - } - - _, err := c.Database("test").Collection("public_keys").InsertOne(context.Background(), keyUsernameEmpty) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[45:46]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - key := new(models.PublicKey) - result := c.Database("test").Collection("public_keys").FindOne(context.Background(), bson.M{"tenant_id": keyUsernameEmpty.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - sort.Strings(key.Filter.Tags) - - assert.Equal(t, keyUsernameRegexp, key) - }, - }, - { - "Success to apply down on migration 46", - func(t *testing.T) { - t.Helper() - - keyUsernameEmpty := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: "", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag3"}, - }, - }, - } - - keyUsernameRegexp := &models.PublicKey{ - Fingerprint: "fingerprint", - TenantID: "tenant", - PublicKeyFields: models.PublicKeyFields{ - Name: "key", - Username: ".*", - Filter: models.PublicKeyFilter{ - Tags: []string{"tag1", "tag2", "tag3"}, - }, - }, - } - - _, err := c.Database("test").Collection("public_keys").InsertOne(context.Background(), keyUsernameEmpty) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[45:46]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - key := new(models.PublicKey) - result := c.Database("test").Collection("public_keys").FindOne(context.Background(), bson.M{"tenant_id": keyUsernameRegexp.TenantID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - assert.Equal(t, keyUsernameEmpty, key) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_47.go b/api/store/mongo/migrations/migration_47.go deleted file mode 100644 index 2432071f009..00000000000 --- a/api/store/mongo/migrations/migration_47.go +++ /dev/null @@ -1,84 +0,0 @@ -package migrations - -import ( - "context" - "net" - "os" - - "github.com/shellhub-io/shellhub/pkg/geoip" - "github.com/shellhub-io/shellhub/pkg/geoip/geolite2" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration47 = migrate.Migration{ - Version: 47, - Description: "", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 47, - "action": "Up", - }).Info("Applying migration up") - - var locator geoip.Locator - if os.Getenv("GEOIP") == "true" { - locator, _ = geolite2.NewLocator(ctx, geolite2.FetchFromLicenseKey(os.Getenv("MAXMIND_LICENSE"))) - } else { - locator = geoip.NewNullGeoLite() - } - - cursor, err := db.Collection("sessions").Find(ctx, bson.D{}) - if err != nil { - return err - } - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - session := new(models.Session) - if err := cursor.Decode(session); err != nil { - return err - } - - position, err := locator.GetPosition(net.ParseIP(session.IPAddress)) - if err != nil { - return err - } - - if _, err := db.Collection("sessions").UpdateOne(ctx, bson.M{"uid": session.UID}, bson.M{"$set": bson.M{"position": position}}); err != nil { - return err - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 47, - "action": "Down", - }).Info("Applying migration down") - - _, err := db.Collection("sessions").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$unset", "position"}, - }, - { - {"$merge", bson.M{"into": "sessions", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_47_test.go b/api/store/mongo/migrations/migration_47_test.go deleted file mode 100644 index e01592a01a1..00000000000 --- a/api/store/mongo/migrations/migration_47_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package migrations - -import ( - "context" - "os" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration47(t *testing.T) { - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 47", - func(t *testing.T) { - t.Helper() - - sessionWithoutPossition := &models.Session{ - UID: "test", - IPAddress: "201.182.197.68", - } - - _, err := c.Database("test").Collection("sessions").InsertOne(context.Background(), sessionWithoutPossition) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[46:47]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - key := new(models.Session) - result := c.Database("test").Collection("sessions").FindOne(context.Background(), bson.M{"uid": sessionWithoutPossition.UID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - if os.Getenv("GEOIP") == "true" { - assert.NotEqual(t, sessionWithoutPossition.Position, key.Position) - } else { - assert.Equal(t, sessionWithoutPossition.Position, key.Position) - } - }, - }, - { - "Success to apply down on migration 47", - func(t *testing.T) { - t.Helper() - - sessionWithoutPossition := &models.Session{ - UID: "test", - IPAddress: "201.182.197.68", - } - - _, err := c.Database("test").Collection("sessions").InsertOne(context.Background(), sessionWithoutPossition) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[46:47]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - key := new(models.Session) - result := c.Database("test").Collection("sessions").FindOne(context.Background(), bson.M{"uid": sessionWithoutPossition.UID}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - assert.Equal(t, sessionWithoutPossition.Position, key.Position) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_48.go b/api/store/mongo/migrations/migration_48.go deleted file mode 100644 index d9aa21daf94..00000000000 --- a/api/store/mongo/migrations/migration_48.go +++ /dev/null @@ -1,97 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// invertFirewallRulePriority inverts the priority of the firewall rules. -// -// The priority of the firewall rules is inverted to follow a common pattern in the industry. -// -// If any error occurs, the migration is aborted. -func invertFirewallRulePriority(db *mongo.Database) error { - ctx := context.Background() - - type Properties struct { - ID string - Priority int - } - - options := new(options.FindOptions) - options.SetSort(bson.D{{"priority", 1}}) // Sort by priority in ascending order. - - namespaces, err := db.Collection("namespaces").Find(ctx, bson.M{}) - if err != nil { - return err - } - defer namespaces.Close(ctx) - - for namespaces.Next(ctx) { - var properties []Properties - - var namespace models.Namespace - if err := namespaces.Decode(&namespace); err != nil { - return err - } - - rules, err := db.Collection("firewall_rules").Find(ctx, bson.M{"tenant_id": namespace.TenantID}, options) - if err != nil { - return err - } - defer rules.Close(ctx) - - for rules.Next(ctx) { - rule := new(models.FirewallRule) - if err := rules.Decode(rule); err != nil { - return err - } - - properties = append(properties, Properties{ - ID: rule.ID, - Priority: rule.Priority, - }) - } - - for index := 0; index <= len(properties)-1; index++ { - id, _ := primitive.ObjectIDFromHex(properties[index].ID) - - _, err := db.Collection("firewall_rules").UpdateMany(ctx, bson.M{"_id": id}, bson.M{"$set": bson.M{"priority": properties[len(properties)-1-index].Priority}}) - if err != nil { - return err - } - } - } - - return nil -} - -var migration48 = migrate.Migration{ - Version: 48, - Description: "invert Firewall priority", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 48, - "action": "Up", - }).Info("Applying migration up") - - return invertFirewallRulePriority(db) - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 48, - "action": "Down", - }).Info("Applying migration down") - - return invertFirewallRulePriority(db) - }), -} diff --git a/api/store/mongo/migrations/migration_48_test.go b/api/store/mongo/migrations/migration_48_test.go deleted file mode 100644 index f4b759c1cb2..00000000000 --- a/api/store/mongo/migrations/migration_48_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration48(t *testing.T) { - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 48", - func(t *testing.T) { - t.Helper() - - namespace := models.Namespace{ - TenantID: "tenant", - } - - rule0 := models.FirewallRule{ - TenantID: namespace.TenantID, - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 0, - }, - } - - rule1 := models.FirewallRule{ - TenantID: namespace.TenantID, - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 1, - }, - } - - rule2 := models.FirewallRule{ - TenantID: namespace.TenantID, - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 2, - }, - } - - ctx := context.Background() - - _, err := c.Database("test").Collection("namespaces").InsertOne(ctx, namespace) - assert.NoError(t, err) - _, err = c.Database("test").Collection("firewall_rules").InsertOne(ctx, rule0) - assert.NoError(t, err) - _, err = c.Database("test").Collection("firewall_rules").InsertOne(ctx, rule1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("firewall_rules").InsertOne(ctx, rule2) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[47:48]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - key := new(models.FirewallRule) - result := c.Database("test").Collection("firewall_rules").FindOne(ctx, bson.M{"tenant_id": namespace.TenantID}) - assert.NoError(t, result.Err()) - - assert.NoError(t, result.Decode(key)) - assert.Equal(t, 2, key.Priority) - }, - }, - { - "Success to apply down on migration 48", - func(t *testing.T) { - t.Helper() - - namespace := models.Namespace{ - TenantID: "tenant", - } - - rule0 := models.FirewallRule{ - TenantID: namespace.TenantID, - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 0, - }, - } - - rule1 := models.FirewallRule{ - TenantID: namespace.TenantID, - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 1, - }, - } - - rule2 := models.FirewallRule{ - TenantID: namespace.TenantID, - FirewallRuleFields: models.FirewallRuleFields{ - Priority: 2, - }, - } - - ctx := context.Background() - - _, err := c.Database("test").Collection("namespaces").InsertOne(ctx, namespace) - assert.NoError(t, err) - _, err = c.Database("test").Collection("firewall_rules").InsertOne(ctx, rule0) - assert.NoError(t, err) - _, err = c.Database("test").Collection("firewall_rules").InsertOne(ctx, rule1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("firewall_rules").InsertOne(ctx, rule2) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[47:48]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - key := new(models.FirewallRule) - result := c.Database("test").Collection("firewall_rules").FindOne(ctx, bson.M{"tenant_id": namespace.TenantID}) - assert.NoError(t, result.Err()) - - assert.NoError(t, result.Decode(key)) - assert.Equal(t, 0, key.Priority) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_49.go b/api/store/mongo/migrations/migration_49.go deleted file mode 100644 index 314c48dd903..00000000000 --- a/api/store/mongo/migrations/migration_49.go +++ /dev/null @@ -1,89 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration49 = migrate.Migration{ - Version: 49, - Description: "set the number of namespaces owned by each user", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 49, - "action": "Up", - }).Info("Applying migration up") - - _, err := db.Collection("users").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - { - "$set", bson.M{"tmp_id": bson.M{"$toString": "$_id"}}, // FIXME: I guess we could eliminate the "$_id" conversion from objectID to string. - }, - }, - { - { - "$lookup", bson.M{ - "from": "namespaces", - "foreignField": "owner", - "localField": "tmp_id", - "as": "tmp", - }, - }, - }, - { - { - "$set", bson.M{"namespaces": bson.M{"$size": "$tmp"}}, - }, - }, - { - { - "$unset", bson.A{"tmp_id", "tmp"}, - }, - }, - { - {"$merge", bson.M{"into": "users", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 49, - "action": "Down", - }).Info("Applying migration down") - - _, err := db.Collection("users").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$unset", "namespaces"}, - }, - { - {"$merge", bson.M{"into": "users", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_49_test.go b/api/store/mongo/migrations/migration_49_test.go deleted file mode 100644 index e60cebfd4d6..00000000000 --- a/api/store/mongo/migrations/migration_49_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration49(t *testing.T) { - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 49", - func(t *testing.T) { - t.Helper() - - user1ID, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439011") - assert.NoError(t, err) - user1 := &models.User{ - ID: user1ID.String(), - } - - user2ID, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439012") - assert.NoError(t, err) - user2 := &models.User{ - ID: user2ID.String(), - } - - namespace1 := &models.Namespace{ - Name: "namespace1", - Owner: user1ID.String(), - } - namespace2 := &models.Namespace{ - Name: "namespace2", - Owner: user1ID.String(), - } - namespace3 := &models.Namespace{ - Name: "namespace3", - Owner: user2ID.String(), - } - - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace3) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[48:49]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - user := new(models.User) - result := c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"_id": user1ID.String()}) - assert.NoError(t, result.Err()) - - err = result.Decode(user) - assert.NoError(t, err) - - // assert.Equal(t, 2, user.Namespaces) - }, - }, - { - "Success to apply down on migration 49", - func(t *testing.T) { - t.Helper() - - user1ID, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439011") - assert.NoError(t, err) - user1 := &models.User{ - ID: user1ID.String(), - } - - user2ID, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439012") - assert.NoError(t, err) - user2 := &models.User{ - ID: user2ID.String(), - } - - namespace1 := &models.Namespace{ - Name: "namespace1", - Owner: user1ID.String(), - } - namespace2 := &models.Namespace{ - Name: "namespace2", - Owner: user1ID.String(), - } - namespace3 := &models.Namespace{ - Name: "namespace3", - Owner: user2ID.String(), - } - - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace3) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[48:49]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - user := new(models.User) - result := c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"_id": user1ID.String()}) - assert.NoError(t, result.Err()) - - err = result.Decode(user) - assert.NoError(t, err) - - // assert.Equal(t, 0, user.Namespaces) - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_4_test.go b/api/store/mongo/migrations/migration_4_test.go deleted file mode 100644 index ff0a8306205..00000000000 --- a/api/store/mongo/migrations/migration_4_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration4(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - deviceInfo := models.DeviceInfo{ - ID: "1", - Version: "0.0.0", - } - - device := models.Device{ - Info: &deviceInfo, - } - - _, err := c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - var afterMigrateDevice *models.Device - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"info": &deviceInfo}).Decode(&afterMigrateDevice) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:4]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - type DeviceInfo struct { - ID string `json:"id"` - Version string `json:"info.version"` - } - - type Device struct { - Info *DeviceInfo `json:"info"` - } - - var migratedDevice *Device - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"info": &deviceInfo}).Decode(&migratedDevice) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_5.go b/api/store/mongo/migrations/migration_5.go deleted file mode 100644 index 5899b41e3e4..00000000000 --- a/api/store/mongo/migrations/migration_5.go +++ /dev/null @@ -1,40 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration5 = migrate.Migration{ - Version: 5, - Description: "Set the email as unique on users collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 5, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"email", 1}}, - Options: options.Index().SetName("email").SetUnique(true), - } - _, err := db.Collection("users").Indexes().CreateOne(ctx, mod) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 5, - "action": "Down", - }).Info("Applying migration") - _, err := db.Collection("users").Indexes().DropOne(ctx, "email") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_50.go b/api/store/mongo/migrations/migration_50.go deleted file mode 100644 index 644b068ad30..00000000000 --- a/api/store/mongo/migrations/migration_50.go +++ /dev/null @@ -1,112 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/envs" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration50 = migrate.Migration{ - Version: 50, - Description: "set max number of namespaces per user", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 50, - "action": "Up", - }).Info("Applying migration up") - - var err error - if envs.IsCloud() { - _, err = db.Collection("users").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$set", bson.M{"tmp": bson.M{"$toString": "$_id"}}}, - }, - { - { - "$lookup", bson.M{ - "from": "namespaces", - "let": bson.M{"owner": "$tmp"}, - "pipeline": mongo.Pipeline{ - { - {"$match", bson.M{ - "$expr": bson.M{ - "$and": bson.A{ - bson.M{"$eq": bson.A{"$owner", "$$owner"}}, - bson.M{"$eq": bson.A{"$billing.active", true}}, - }, - }, - }}, - }, - }, - "as": "list", - }, - }, - }, - { - {"$set", bson.M{"max_namespaces": bson.M{"$add": bson.A{bson.M{"$size": "$list"}, 1}}}}, - }, - { - {"$unset", bson.A{"tmp", "list"}}, - }, - { - {"$merge", bson.M{"into": "users", "whenMatched": "replace"}}, - }, - }, - ) - } else { - _, err = db.Collection("users").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$set", bson.M{"max_namespaces": -1}}, - }, - { - {"$merge", bson.M{"into": "users", "whenMatched": "replace"}}, - }, - }, - ) - } - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 50, - "action": "Down", - }).Info("Applying migration down") - - _, err := db.Collection("users").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$unset", "max_namespaces"}, - }, - { - {"$merge", bson.M{"into": "users", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_50_test.go b/api/store/mongo/migrations/migration_50_test.go deleted file mode 100644 index e4baa2cb78f..00000000000 --- a/api/store/mongo/migrations/migration_50_test.go +++ /dev/null @@ -1,212 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration50(t *testing.T) { - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - user1ID, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439011") - assert.NoError(t, err) - user1 := &models.User{ - ID: user1ID.String(), - } - - user2ID, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439012") - assert.NoError(t, err) - user2 := &models.User{ - ID: user2ID.String(), - } - - namespace1 := &models.Namespace{ - Name: "namespace1", - Owner: user1ID.String(), - Billing: &models.Billing{ - Active: true, - }, - } - namespace2 := &models.Namespace{ - Name: "namespace2", - Owner: user1ID.String(), - } - namespace3 := &models.Namespace{ - Name: "namespace3", - Owner: user2ID.String(), - } - - cases := []struct { - description string - before func() - test func() (int, error) - expected int - after func() - }{ - { - "Success to apply up on migration 50 when it is a ShellHub Cloud instance", - func() { - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace3) - assert.NoError(t, err) - }, - func() (int, error) { - mock.On("Get", "SHELLHUB_CLOUD").Return("true").Once() - - migrations := GenerateMigrations()[49:50] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return 0, err - } - - user := new(models.User) - result := c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"_id": user1ID.String()}) - if err != nil { - return 0, err - } - - err = result.Decode(user) - if err != nil { - return 0, err - } - - return user.MaxNamespaces, nil - }, - 2, - func() { - err = c.Database("test").Collection("users").Drop(context.TODO()) - assert.NoError(t, err) - err = c.Database("test").Collection("namespaces").Drop(context.TODO()) - assert.NoError(t, err) - err = c.Database("test").Collection("migrations").Drop(context.TODO()) - assert.NoError(t, err) - }, - }, - { - "Success to apply up on migration 50 when it is a ShellHub Community instance", - func() { - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace3) - assert.NoError(t, err) - }, - func() (int, error) { - mock.On("Get", "SHELLHUB_CLOUD").Return("false").Once() - - migrations := GenerateMigrations()[49:50] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return 0, err - } - - user := new(models.User) - result := c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"_id": user1ID.String()}) - if err != nil { - return 0, err - } - - err = result.Decode(user) - if err != nil { - return 0, err - } - - return user.MaxNamespaces, nil - }, - -1, - func() { - err = c.Database("test").Collection("users").Drop(context.TODO()) - assert.NoError(t, err) - err = c.Database("test").Collection("namespaces").Drop(context.TODO()) - assert.NoError(t, err) - err = c.Database("test").Collection("migrations").Drop(context.TODO()) - assert.NoError(t, err) - }, - }, - { - "Success to apply down on migration 50", - func() { - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace1) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace2) - assert.NoError(t, err) - _, err = c.Database("test").Collection("namespaces").InsertOne(context.TODO(), namespace3) - assert.NoError(t, err) - }, - func() (int, error) { - migrations := GenerateMigrations()[49:50] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return 0, err - } - - user := new(models.User) - result := c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"_id": user1ID.String()}) - if err != nil { - return 0, err - } - - err = result.Decode(user) - if err != nil { - return 0, err - } - - return user.MaxNamespaces, nil - }, - 0, - func() { - err = c.Database("test").Collection("users").Drop(context.TODO()) - assert.NoError(t, err) - err = c.Database("test").Collection("namespaces").Drop(context.TODO()) - assert.NoError(t, err) - err = c.Database("test").Collection("migrations").Drop(context.TODO()) - assert.NoError(t, err) - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - tc.before() - - actual, err := tc.test() - assert.NoError(t, err) - assert.Equal(t, tc.expected, actual) - - tc.after() - }) - } -} diff --git a/api/store/mongo/migrations/migration_51.go b/api/store/mongo/migrations/migration_51.go deleted file mode 100644 index f239830df20..00000000000 --- a/api/store/mongo/migrations/migration_51.go +++ /dev/null @@ -1,51 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration51 = migrate.Migration{ - Version: 51, - Description: "create index for name on devices", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 51, - "action": "Up", - }).Info("Applying migration up") - Name := "name" - - if _, err := db.Collection("devices").Indexes().CreateOne(context.Background(), mongo.IndexModel{ - Keys: bson.M{ - Name: 1, - }, - Options: &options.IndexOptions{ //nolint:exhaustruct - Name: &Name, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 51, - "action": "Down", - }).Info("Applying migration down") - Name := "name" - - if _, err := db.Collection("devices").Indexes().DropOne(context.Background(), Name); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_51_test.go b/api/store/mongo/migrations/migration_51_test.go deleted file mode 100644 index b3addea0721..00000000000 --- a/api/store/mongo/migrations/migration_51_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration51(t *testing.T) { - const Name string = "name" - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - test func() error - }{ - { - "Success to apply up on migration 51", - func() error { - mock.On("Get", "SHELLHUB_CLOUD").Return("true").Once() - - migrations := GenerateMigrations()[50:51] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("devices").Indexes().List(context.Background()) - if err != nil { - return err - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == Name { - found = true - } - } - - if !found { - return errors.New("index not created") - } - - return nil - }, - }, - { - "Success to apply down on migration 51", - func() error { - migrations := GenerateMigrations()[50:51] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("devices").Indexes().List(context.Background()) - if err != nil { - return errors.New("index not dropped") - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == Name { - found = true - } - } - - if found { - return errors.New("index not dropped") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := tc.test() - assert.NoError(t, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_52.go b/api/store/mongo/migrations/migration_52.go deleted file mode 100644 index ac1874168c2..00000000000 --- a/api/store/mongo/migrations/migration_52.go +++ /dev/null @@ -1,67 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration52 = migrate.Migration{ - Version: 52, - Description: "add marketing field to users", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 52, - "action": "Up", - }).Info("Applying migration") - - _, err := db.Collection("users").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$set", bson.M{"email_marketing": true}}, - }, - { - {"$merge", bson.M{"into": "users", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 52, - "action": "Down", - }).Info("Applying migration") - - _, err := db.Collection("users").Aggregate(context.Background(), - mongo.Pipeline{ - { - {"$match", bson.M{}}, - }, - { - {"$set", bson.M{"email_marketing": false}}, - }, - { - {"$merge", bson.M{"into": "users", "whenMatched": "replace"}}, - }, - }, - ) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_52_test.go b/api/store/mongo/migrations/migration_52_test.go deleted file mode 100644 index 81c4e683238..00000000000 --- a/api/store/mongo/migrations/migration_52_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration52(t *testing.T) { - user := models.User{} - - cases := []struct { - description string - Test func(t *testing.T) - }{ - { - "Success to apply up on migration 52", - func(t *testing.T) { - t.Helper() - - _, err := c.Database("test").Collection("users").InsertOne(context.Background(), user) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[51:52]...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - key := new(models.User) - result := c.Database("test").Collection("users").FindOne(context.Background(), bson.M{}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - assert.True(t, key.EmailMarketing) - }, - }, - { - "Success to apply down on migration 52", - func(t *testing.T) { - t.Helper() - - _, err := c.Database("test").Collection("users").InsertOne(context.Background(), user) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[51:52]...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - key := new(models.User) - result := c.Database("test").Collection("users").FindOne(context.Background(), bson.M{}) - assert.NoError(t, result.Err()) - - err = result.Decode(key) - assert.NoError(t, err) - - assert.False(t, key.EmailMarketing) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - tc.Test(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_53.go b/api/store/mongo/migrations/migration_53.go deleted file mode 100644 index 3a9f3b6736b..00000000000 --- a/api/store/mongo/migrations/migration_53.go +++ /dev/null @@ -1,55 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration53 = migrate.Migration{ - Version: 53, - Description: "create index to announcement ID", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 53, - "action": "Up", - }).Info("Applying migration") - field := "uuid" - collection := "announcements" - unique := true - - if _, err := db.Collection(collection).Indexes().CreateOne(context.Background(), mongo.IndexModel{ - Keys: bson.M{ - field: 1, - }, - Options: &options.IndexOptions{ //nolint:exhaustruct - Name: &field, - Unique: &unique, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 53, - "action": "Down", - }).Info("Applying migration") - index := "uuid" - collection := "announcements" - - if _, err := db.Collection(collection).Indexes().DropOne(context.Background(), index); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_54.go b/api/store/mongo/migrations/migration_54.go deleted file mode 100644 index 01716215fd8..00000000000 --- a/api/store/mongo/migrations/migration_54.go +++ /dev/null @@ -1,54 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration54 = migrate.Migration{ - Version: 54, - Description: "create index to devices' tenant_id and status", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 54, - "action": "Up", - }).Info("Applying migration") - fieldTenantID := "tenant_id" - fieldStatus := "status" - name := "tenant_id_1_status_1" - - if _, err := db.Collection("devices").Indexes().CreateOne(context.Background(), mongo.IndexModel{ - Keys: bson.D{ - bson.E{Key: fieldTenantID, Value: 1}, - bson.E{Key: fieldStatus, Value: 1}, - }, - Options: &options.IndexOptions{ //nolint:exhaustruct - Name: &name, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 54, - "action": "Down", - }).Info("Applying migration") - name := "tenant_id_1_status_1" - - if _, err := db.Collection("devices").Indexes().DropOne(context.Background(), name); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_54_test.go b/api/store/mongo/migrations/migration_54_test.go deleted file mode 100644 index 94e1cb79efe..00000000000 --- a/api/store/mongo/migrations/migration_54_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration54(t *testing.T) { - const Name string = "tenant_id_1_status_1" - - cases := []struct { - description string - test func() error - }{ - { - "Success to apply up on migration 54", - func() error { - migrations := GenerateMigrations()[53:54] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("devices").Indexes().List(context.Background()) - if err != nil { - return err - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == Name { - found = true - } - } - - if !found { - return errors.New("index not created") - } - - return nil - }, - }, - { - "Success to apply down on migration 54", - func() error { - migrations := GenerateMigrations()[53:54] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("devices").Indexes().List(context.Background()) - if err != nil { - return errors.New("index not dropped") - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == Name { - found = true - } - } - - if found { - return errors.New("index not dropped") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := tc.test() - assert.NoError(t, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_55.go b/api/store/mongo/migrations/migration_55.go deleted file mode 100644 index ae4dedb84ee..00000000000 --- a/api/store/mongo/migrations/migration_55.go +++ /dev/null @@ -1,93 +0,0 @@ -package migrations - -import ( - "context" - "time" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration55 = migrate.Migration{ - Version: 55, - Description: "create indexes on removed_devices for tenant_id, tenant_id and uid and timestamp", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 55, - "action": "Up", - }).Info("Applying migration") - fieldTenantID := "tenant_id" - fieldUID := "uid" - fieldTimestamp := "timestamp" - - expire, err := time.ParseDuration("720h") - if err != nil { - return err - } - expireSeconds := int32(expire.Seconds()) - - fieldNameTenantID := "tenant_id_1" - fieldNameTenantIDUID := "tenant_id_1_uid_1" - fieldNameTimestamp := "timestamp_1" - if _, err := db.Collection("removed_devices").Indexes().CreateMany(context.Background(), []mongo.IndexModel{ - { - Keys: bson.D{ - bson.E{Key: fieldTenantID, Value: 1}, - }, - Options: &options.IndexOptions{ - Name: &fieldNameTenantID, - }, - }, - { - Keys: bson.D{ - bson.E{Key: fieldTenantID, Value: 1}, - bson.E{Key: fieldUID, Value: 1}, - }, - Options: &options.IndexOptions{ - Name: &fieldNameTenantIDUID, - }, - }, - { - Keys: bson.D{ - bson.E{Key: fieldTimestamp, Value: 1}, - }, - Options: &options.IndexOptions{ - Name: &fieldNameTimestamp, - ExpireAfterSeconds: &expireSeconds, - }, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 55, - "action": "Down", - }).Info("Applying migration") - fieldNameTenantID := "tenant_id_1" - fieldNameTenantIDUID := "tenant_id_1_uid_1" - fieldNameTimestamp := "timestamp_1" - - if _, err := db.Collection("removed_devices").Indexes().DropOne(context.Background(), fieldNameTenantID); err != nil { - return err - } - - if _, err := db.Collection("removed_devices").Indexes().DropOne(context.Background(), fieldNameTenantIDUID); err != nil { - return err - } - - if _, err := db.Collection("removed_devices").Indexes().DropOne(context.Background(), fieldNameTimestamp); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_55_test.go b/api/store/mongo/migrations/migration_55_test.go deleted file mode 100644 index 7aa615dc5a4..00000000000 --- a/api/store/mongo/migrations/migration_55_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "go.mongodb.org/mongo-driver/bson" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration55(t *testing.T) { - fieldNameTenantID := "tenant_id_1" - fieldNameTenantIDUID := "tenant_id_1_uid_1" - fieldNameTimestamp := "timestamp_1" - - cases := []struct { - description string - test func() error - }{ - { - "Success to apply up on migration 55", - func() error { - migrations := GenerateMigrations()[54:55] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("removed_devices").Indexes().List(context.Background()) - if err != nil { - return err - } - - var foundNameTenantID bool - var foundNameTenantIDUID bool - var foundNameTimestamp bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - switch index["name"] { - case fieldNameTenantID: - foundNameTenantID = true - case fieldNameTenantIDUID: - foundNameTenantIDUID = true - case fieldNameTimestamp: - foundNameTimestamp = true - } - } - - if !foundNameTenantID || !foundNameTenantIDUID || !foundNameTimestamp { - return errors.New("one of the indexes was not created") - } - - return nil - }, - }, - { - "Success to apply down on migration 55", - func() error { - migrations := GenerateMigrations()[54:55] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("removed_devices").Indexes().List(context.Background()) - if err != nil { - return errors.New("index not dropped") - } - - var foundNameTenantID bool - var foundNameTenantIDUID bool - var foundNameTimestamp bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - switch index["name"] { - case fieldNameTenantID: - foundNameTenantID = true - case fieldNameTenantIDUID: - foundNameTenantIDUID = true - case fieldNameTimestamp: - foundNameTimestamp = true - } - } - - if foundNameTenantID || foundNameTenantIDUID || foundNameTimestamp { - return errors.New("one of the indexes was deleted") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := tc.test() - assert.NoError(t, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_56.go b/api/store/mongo/migrations/migration_56.go deleted file mode 100644 index 6bcc1ff672e..00000000000 --- a/api/store/mongo/migrations/migration_56.go +++ /dev/null @@ -1,55 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration56 = migrate.Migration{ - Version: 56, - Description: "create index for public url address on devices", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 56, - "action": "Up", - }).Info("Applying migration up") - field := "public_url_address" - unique := true - sparse := true - - if _, err := db.Collection("devices").Indexes().CreateOne(context.Background(), mongo.IndexModel{ - Keys: bson.M{ - field: 1, - }, - Options: &options.IndexOptions{ //nolint:exhaustruct - Unique: &unique, - Sparse: &sparse, - Name: &field, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 56, - "action": "Down", - }).Info("Applying migration down") - field := "public_url_address" - - if _, err := db.Collection("devices").Indexes().DropOne(context.Background(), field); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_56_test.go b/api/store/mongo/migrations/migration_56_test.go deleted file mode 100644 index 6dd65bed39d..00000000000 --- a/api/store/mongo/migrations/migration_56_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration56(t *testing.T) { - const field string = "public_url_address" - - cases := []struct { - description string - test func() error - }{ - { - "Success to apply up on migration 56", - func() error { - migrations := GenerateMigrations()[55:56] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("devices").Indexes().List(context.Background()) - if err != nil { - return err - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == field { - found = true - } - } - - if !found { - return errors.New("index not created") - } - - return nil - }, - }, - { - "Success to apply down on migration 56", - func() error { - migrations := GenerateMigrations()[55:56] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("devices").Indexes().List(context.Background()) - if err != nil { - return errors.New("index not dropped") - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == field { - found = true - } - } - - if found { - return errors.New("index not dropped") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := tc.test() - assert.NoError(t, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_57.go b/api/store/mongo/migrations/migration_57.go deleted file mode 100644 index 470f8d0e68f..00000000000 --- a/api/store/mongo/migrations/migration_57.go +++ /dev/null @@ -1,157 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration57 = migrate.Migration{ - Version: 57, - Description: "update billing state to status and its values", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 57, - "action": "Up", - }).Info("Applying migration up") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "billing": bson.M{ - "$ne": nil, - }, - }, - }, - { - "$addFields": bson.M{ - "billing.status": bson.M{ - "$switch": bson.M{ - "branches": []bson.M{ - { - "case": bson.M{ - "$eq": []string{"$billing.state", "processed"}, - }, - "then": "active", - }, - { - "case": bson.M{ - "$eq": []string{"$billing.state", "past_due"}, - }, - "then": "past_due", - }, - { - "case": bson.M{ - "$eq": []string{"$billing.state", "pending"}, - }, - "then": "canceled", - }, - { - "case": bson.M{ - "$eq": []string{"$billing.state", "inactive"}, - }, - "then": "inactive", - }, - { - "case": bson.M{ - "$eq": []string{"$billing.state", "canceled"}, - }, - "then": "canceled", - }, - }, - "default": "canceled", - }, - }, - }, - }, - { - "$unset": "billing.state", - }, - { - "$merge": bson.M{ - "into": "namespaces", - "whenMatched": "replace", - }, - }, - } - - _, err := db.Collection("namespaces").Aggregate(context.Background(), pipeline) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 57, - "action": "Down", - }).Info("Applying migration down") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "billing": bson.M{ - "$ne": nil, - }, - }, - }, - { - "$addFields": bson.M{ - "billing.state": bson.M{ - "$switch": bson.M{ - "branches": []bson.M{ - { - "case": bson.M{ - "$eq": []string{"$billing.status", "active"}, - }, - "then": "processed", - }, - { - "case": bson.M{ - "$eq": []string{"$billing.status", "past_due"}, - }, - "then": "past_due", - }, - { - "case": bson.M{ - "$eq": []string{"$billing.status", "inactive"}, - }, - "then": "inactive", - }, - { - "case": bson.M{ - "$eq": []string{"$billing.status", "canceled"}, - }, - "then": "canceled", - }, - }, - "default": "canceled", - }, - }, - }, - }, - { - "$unset": "billing.status", - }, - { - "$merge": bson.M{ - "into": "namespaces", - "whenMatched": "replace", - }, - }, - } - - _, err := db.Collection("namespaces").Aggregate(context.Background(), pipeline) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_57_test.go b/api/store/mongo/migrations/migration_57_test.go deleted file mode 100644 index 205cba5b5f6..00000000000 --- a/api/store/mongo/migrations/migration_57_test.go +++ /dev/null @@ -1,183 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration57(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - type PaymentFailed struct { - Status bool `json:"status" bson:"status,omitempty"` - Amount float64 `json:"amount" bson:"amount,omitempty"` - Date time.Time `json:"date" bson:"date,omitempty"` - Details string `json:"details" bson:"details,omitempty"` - } - - type Billing struct { - SubscriptionID string `json:"subscription_id" bson:"subscription_id,omitempty"` - CurrentPeriodEnd time.Time `json:"current_period_end" bson:"current_period_end,omitempty"` - PriceID string `json:"price_id" bson:"price_id,omitempty"` - CustomerID string `json:"customer_id" bson:"customer_id,omitempty"` - PaymentMethodID string `json:"payment_method_id" bson:"payment_method_id,omitempty"` - PaymentFailed *PaymentFailed `json:"payment_failed" bson:"payment_failed,omitempty"` - State string `json:"state" bson:"state,omitempty"` - Active bool `json:"active" bson:"active,omitempty"` - SubItem string `json:"sub_item_id" bson:"sub_item_id,omitempty"` - } - - type Namespace struct { - TenantID string `json:"tenant_id" bson:"tenant_id"` - Billing *Billing `json:"billing" bson:"billing,omitempty"` - } - - cases := []struct { - description string - setup func() error - run func() error - check func() (string, error) - expected string - }{ - { - description: "Success to apply up on migration 57 when namespace has billing", - setup: func() error { - _, err := c.Database("test").Collection("namespaces").InsertOne(context.TODO(), Namespace{ - TenantID: "00000000-0000-0000-0000-000000000001", - Billing: &Billing{ - State: "processed", - }, - }) - - return err - }, - run: func() error { - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[56:57]...) - - return migrates.Up(context.Background(), migrate.AllAvailable) - }, - check: func() (string, error) { - namespace := new(models.Namespace) - err := c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{ - "tenant_id": "00000000-0000-0000-0000-000000000001", - }).Decode(&namespace) - if err != nil { - return "", err - } - - return string(namespace.Billing.Status), nil - }, - expected: "active", - }, - { - description: "Success to apply up on migration 57 when namespace has no billing", - setup: func() error { - _, err := c.Database("test").Collection("namespaces").InsertOne(context.TODO(), Namespace{ - TenantID: "00000000-0000-0000-0000-000000000002", - }) - - return err - }, - run: func() error { - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[56:57]...) - - return migrates.Up(context.Background(), migrate.AllAvailable) - }, - check: func() (string, error) { - namespace := new(models.Namespace) - if err := c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-0000-0000-000000000002"}).Decode(&namespace); err != nil { - return "", err - } - - if namespace.Billing != nil { - return "", errors.New("billing should be nil") - } - - return "", nil - }, - expected: "", - }, - { - description: "Success to apply down on migration 57 when namespace has billing", - setup: func() error { - _, err := c.Database("test").Collection("namespaces").InsertOne(context.TODO(), &models.Namespace{ - TenantID: "00000000-0000-0000-0000-000000000003", - Billing: &models.Billing{ - Status: "active", - }, - }) - - return err - }, - run: func() error { - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[56:57]...) - - return migrates.Down(context.Background(), migrate.AllAvailable) - }, - check: func() (string, error) { - namespace := new(Namespace) - err := c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{ - "tenant_id": "00000000-0000-0000-0000-000000000003", - }).Decode(&namespace) - if err != nil { - return "", err - } - - return namespace.Billing.State, nil - }, - expected: "processed", - }, - { - description: "Success to apply down on migration 57 when namespace has no billing", - setup: func() error { - _, err := c.Database("test").Collection("namespaces").InsertOne(context.TODO(), &models.Namespace{ - TenantID: "00000000-0000-0000-0000-000000000004", - }) - - return err - }, - run: func() error { - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[56:57]...) - - return migrates.Down(context.Background(), migrate.AllAvailable) - }, - check: func() (string, error) { - namespace := new(Namespace) - err := c.Database("test").Collection("namespaces").FindOne(context.TODO(), bson.M{ - "tenant_id": "00000000-0000-0000-0000-000000000004", - }).Decode(&namespace) - if err != nil { - return "", err - } - - if namespace.Billing != nil { - return "", errors.New("billing should be nil") - } - - return "", nil - }, - expected: "", - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - assert.NoError(t, tc.setup()) - assert.NoError(t, tc.run()) - - result, err := tc.check() - assert.Equal(t, tc.expected, result) - assert.NoError(t, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_58.go b/api/store/mongo/migrations/migration_58.go deleted file mode 100644 index 9732ac824b5..00000000000 --- a/api/store/mongo/migrations/migration_58.go +++ /dev/null @@ -1,95 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration58 = migrate.Migration{ - Version: 58, - Description: "", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 58, - "action": "Up", - }).Info("Applying migration up") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "billing": bson.M{ - "$ne": nil, - }, - }, - }, - { - "$set": bson.M{ - "billing.current_period_end": bson.M{ - "$convert": bson.M{ - "input": "$billing.current_period_end", - "to": "long", - }, - }, - }, - }, - { - "$merge": bson.M{ - "into": "namespaces", - "whenMatched": "replace", - }, - }, - } - - _, err := db.Collection("namespaces").Aggregate(context.Background(), pipeline) - if err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 58, - "action": "Down", - }).Info("Applying migration down") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "billing": bson.M{ - "$ne": nil, - }, - }, - }, - { - "$set": bson.M{ - "billing.current_period_end": bson.M{ - "$convert": bson.M{ - "input": "$billing.current_period_end", - "to": "date", - }, - }, - }, - }, - { - "$merge": bson.M{ - "into": "namespaces", - "whenMatched": "replace", - }, - }, - } - - _, err := db.Collection("namespaces").Aggregate(context.Background(), pipeline) - if err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_59.go b/api/store/mongo/migrations/migration_59.go deleted file mode 100644 index 554c6c1c6e5..00000000000 --- a/api/store/mongo/migrations/migration_59.go +++ /dev/null @@ -1,72 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration59 = migrate.Migration{ - Version: 59, - Description: "Converts all 'name' field values in the 'users' collection to lowercase.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 59, - "action": "Up", - }).Info("Starting migration Up action.") - - _, err := db.Collection("users").UpdateMany( - ctx, - bson.M{}, - []bson.M{ - { - "$set": bson.M{ - "username": bson.M{ - "$toLower": "$username", - }, - "email": bson.M{ - "$toLower": "$email", - }, - }, - }, - }, - ) - if err != nil { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 59, - "action": "Up", - "error": err.Error(), - }).Error("Failed to execute Up migration.") - - return err - } - - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 59, - "action": "Up", - }).Info("Completed migration Up action successfully.") - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 59, - "action": "Down", - }).Info("Starting migration Down action.") - - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 59, - "action": "Down", - }).Info("Completed migration Down action successfully.") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_59_test.go b/api/store/mongo/migrations/migration_59_test.go deleted file mode 100644 index e3b65ef77c4..00000000000 --- a/api/store/mongo/migrations/migration_59_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration59(t *testing.T) { - ctx := context.TODO() - - type Expected struct { - user *models.User - err error - } - - cases := []struct { - description string - setup func() (func() error, error) - check func() (*models.User, error) - expected Expected - }{ - { - description: "Success to apply up on migration 59", - setup: func() (func() error, error) { - if _, err := c.Database("test").Collection("users").InsertOne(ctx, models.User{ - ID: "652594bcc7b001c6f298df48", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UserData: models.UserData{ - Name: "John Doe", - Email: "JohnDoe@test.com", - Username: "John Doe", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - }); err != nil { - return nil, err - } - - user := new(models.User) - if err := c.Database("test").Collection("users").FindOne(ctx, bson.M{"name": "John Doe"}).Decode(&user); err != nil { - return nil, err - } - - return func() error { - d, err := c.Database("test").Collection("users").DeleteOne(ctx, bson.M{"username": "john doe"}) - if err != nil { - return err - } - - if d.DeletedCount < 1 { - return errors.New("No users deleted") - } - - return nil - }, nil - }, - check: func() (*models.User, error) { - user := new(models.User) - - if err := c.Database("test").Collection("users").FindOne(ctx, bson.M{"username": "john doe"}).Decode(&user); err != nil { - return nil, err - } - - return user, nil - }, - expected: Expected{ - user: &models.User{ - ID: "652594bcc7b001c6f298df48", - MaxNamespaces: 0, - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - EmailMarketing: false, - UserData: models.UserData{ - Name: "John Doe", - Email: "johndoe@test.com", - Username: "john doe", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - teardown, err := tc.setup() - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), migration59) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - user, err := tc.check() - assert.Equal(t, tc.expected, Expected{user, err}) - - assert.NoError(t, teardown()) - }) - } -} diff --git a/api/store/mongo/migrations/migration_5_test.go b/api/store/mongo/migrations/migration_5_test.go deleted file mode 100644 index 029125c4021..00000000000 --- a/api/store/mongo/migrations/migration_5_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration5(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user1 := models.User{ - UserData: models.UserData{ - Name: "name1", - Username: "username1", - Email: "email", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - user2 := models.User{ - UserData: models.UserData{ - Name: "name2", - Username: "username2", - Email: "email", - }, - Password: models.UserPassword{ - Hash: "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b", - }, - } - - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("users").InsertOne(context.TODO(), user2) - assert.NoError(t, err) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:4]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:5]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.Error(t, err) -} diff --git a/api/store/mongo/migrations/migration_6.go b/api/store/mongo/migrations/migration_6.go deleted file mode 100644 index ed01a0f2cde..00000000000 --- a/api/store/mongo/migrations/migration_6.go +++ /dev/null @@ -1,46 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration6 = migrate.Migration{ - Version: 6, - Description: "Unset unique on status in the devices collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 6, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"status", 1}}, - Options: options.Index().SetName("status").SetUnique(false), - } - if _, err := db.Collection("devices").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - _, err := db.Collection("devices").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"status": "accepted"}}) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 6, - "action": "Down", - }).Info("Applying migration") - if _, err := db.Collection("devices").UpdateMany(ctx, bson.M{}, bson.M{"$unset": bson.M{"status": ""}}); err != nil { - return err - } - _, err := db.Collection("status").Indexes().DropOne(ctx, "status") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_60.go b/api/store/mongo/migrations/migration_60.go deleted file mode 100644 index 881f4b1f94e..00000000000 --- a/api/store/mongo/migrations/migration_60.go +++ /dev/null @@ -1,48 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration60 = migrate.Migration{ - Version: 60, - Description: "create index for tenant_id on active_sessions", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 60, - "action": "Up", - }).Info("Applying migration up") - indexName := "tenant_id" - if _, err := db.Collection("active_sessions").Indexes().CreateOne(context.Background(), mongo.IndexModel{ - Keys: bson.M{ - "tenant_id": 1, - }, - Options: &options.IndexOptions{ //nolint:exhaustruct - Name: &indexName, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 60, - "action": "Down", - }).Info("Applying migration down") - if _, err := db.Collection("active_sessions").Indexes().DropOne(context.Background(), "tenant_id"); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_60_test.go b/api/store/mongo/migrations/migration_60_test.go deleted file mode 100644 index 4d40a92b8a0..00000000000 --- a/api/store/mongo/migrations/migration_60_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration60(t *testing.T) { - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - test func() error - }{ - { - "Success to apply up on migration 60", - func() error { - mock.On("Get", "SHELLHUB_CLOUD").Return("true").Once() - - migrations := GenerateMigrations()[59:60] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("active_sessions").Indexes().List(context.Background()) - if err != nil { - return err - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == "tenant_id" { - found = true - } - } - - if !found { - return errors.New("index not created") - } - - return nil - }, - }, - { - "Success to apply down on migration 60", - func() error { - migrations := GenerateMigrations()[59:60] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("active_sessions").Indexes().List(context.Background()) - if err != nil { - return errors.New("index not dropped") - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == "tenant_id" { - found = true - } - } - - if found { - return errors.New("index not dropped") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := tc.test() - assert.NoError(t, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_61.go b/api/store/mongo/migrations/migration_61.go deleted file mode 100644 index 9e208531569..00000000000 --- a/api/store/mongo/migrations/migration_61.go +++ /dev/null @@ -1,34 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration61 = migrate.Migration{ - Version: 61, - Description: "delete devices with empty name", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 61, - "action": "Up", - }).Info("Applying migration up") - if _, err := db.Collection("devices").DeleteMany(context.Background(), bson.M{"$or": bson.A{ - bson.M{"name": ""}, - bson.M{"name": bson.M{"$exists": false}}, - }}); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - // This migration is not reversible. - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_61_test.go b/api/store/mongo/migrations/migration_61_test.go deleted file mode 100644 index 999c5e4b242..00000000000 --- a/api/store/mongo/migrations/migration_61_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration61(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() (func() error, error) - test func() error - }{ - { - "Success to apply up on migration 61", - func() (func() error, error) { - if _, err := c.Database("test").Collection("devices").InsertOne(ctx, models.Device{ - Name: "", - }); err != nil { - return nil, err - } - - if _, err := c.Database("test").Collection("devices").InsertOne(ctx, models.Device{ - Name: "test", - }); err != nil { - return nil, err - } - - return func() error { - _, err := c.Database("test").Collection("devices").DeleteOne(ctx, bson.M{ - "name": "test", - }) - if err != nil { - return err - } - - return nil - }, nil - }, - func() error { - migrations := GenerateMigrations()[60:61] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - count, err := c.Database("test").Collection("devices").CountDocuments(ctx, bson.M{"name": ""}) - if err != nil { - return err - } - - if count != 0 { - return errors.New("failed because don't deleted the expected") - } - - count, err = c.Database("test").Collection("devices").CountDocuments(ctx, bson.M{"name": "test"}) - if err != nil { - return err - } - - if count != 1 { - return errors.New("failed because deleted more than the expected") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - teardown, err := tc.setup() - assert.NoError(t, err) - - err = tc.test() - assert.NoError(t, err) - - if teardown != nil { - assert.NoError(t, teardown()) - } - }) - } -} diff --git a/api/store/mongo/migrations/migration_62.go b/api/store/mongo/migrations/migration_62.go deleted file mode 100644 index 00399fc054e..00000000000 --- a/api/store/mongo/migrations/migration_62.go +++ /dev/null @@ -1,62 +0,0 @@ -package migrations - -import ( - "context" - - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration62 = migrate.Migration{ - Version: 62, - Description: "create index for tenant_id on recorded_sessions", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 62, - "action": "Up", - }).Info("Applying migration up") - - indexName := "tenant_id" - _, err := db.Collection("recorded_sessions").Indexes().CreateOne(context.Background(), mongo.IndexModel{ - Keys: bson.M{ - "tenant_id": 1, - }, - Options: &options.IndexOptions{ //nolint:exhaustruct - Name: &indexName, - }, - }) - if err != nil { - log.WithFields(log.Fields{ - "component": "migration", - "version": 62, - "action": "Up", - }).WithError(err).Info("Error while trying to apply migration 62") - - return err - } - - log.WithFields(log.Fields{ - "component": "migration", - "version": 62, - "action": "Up", - }).Info("Succeeds to to apply migration 62") - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 62, - "action": "Down", - }).Info("Applying migration down") - if _, err := db.Collection("recorded_sessions").Indexes().DropOne(context.Background(), "tenant_id"); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_62_test.go b/api/store/mongo/migrations/migration_62_test.go deleted file mode 100644 index f58cdc8fab8..00000000000 --- a/api/store/mongo/migrations/migration_62_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration62Up(t *testing.T) { - cases := []struct { - description string - mocks func() - expected func() error - }{ - { - description: "Success to apply up on migration 62", - mocks: func() { - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - mock.On("Get", "SHELLHUB_CLOUD").Return("true").Once() - }, - expected: func() error { - cursor, err := c.Database("test").Collection("recorded_sessions").Indexes().List(context.Background()) - if err != nil { - return err - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == "tenant_id" { - found = true - } - } - - if !found { - return errors.New("index not created") - } - - return nil - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - tc.mocks() - - migrations := GenerateMigrations()[61:62] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - assert.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - assert.NoError(t, tc.expected()) - }) - } -} - -func TestMigration62Down(t *testing.T) { - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - mocks func() - expected func() error - }{ - { - description: "Success to apply down on migration 62", - mocks: func() {}, - expected: func() error { - cursor, err := c.Database("test").Collection("recorded_sessions").Indexes().List(context.Background()) - if err != nil { - return errors.New("index not dropped") - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == "tenant_id" { - found = true - } - } - - if found { - return errors.New("index not dropped") - } - - return nil - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - tc.mocks() - - migrations := GenerateMigrations()[61:62] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - assert.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - assert.NoError(t, tc.expected()) - }) - } -} diff --git a/api/store/mongo/migrations/migration_63.go b/api/store/mongo/migrations/migration_63.go deleted file mode 100644 index 2159e0fce1c..00000000000 --- a/api/store/mongo/migrations/migration_63.go +++ /dev/null @@ -1,57 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration63 = migrate.Migration{ - Version: 63, - Description: "add MFA fields to collection users", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 63, - "action": "Up", - }).Info("Applying migration") - - update := bson.M{ - "$set": bson.M{ - "status_mfa": false, - "secret": "", - "codes": []string{}, - }, - } - - if _, err := db.Collection("users").UpdateMany(ctx, bson.M{}, update); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 63, - "action": "Down", - }).Info("Reverting migration") - - update := bson.M{ - "$unset": bson.M{ - "status_mfa": "", - "secret": "", - "codes": "", - }, - } - - if _, err := db.Collection("users").UpdateMany(ctx, bson.M{}, update); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_63_test.go b/api/store/mongo/migrations/migration_63_test.go deleted file mode 100644 index 9f60eedcbd6..00000000000 --- a/api/store/mongo/migrations/migration_63_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration63(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user := models.User{ - UserData: models.UserData{ - Name: "Test", - }, - } - - _, err := c.Database("test").Collection("users").InsertOne(context.TODO(), user) - assert.NoError(t, err) - - migrations := GenerateMigrations()[62:63] - - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - version, _, err := migrates.Version(context.Background()) - assert.NoError(t, err) - assert.Equal(t, uint64(63), version) - - var migratedUser *models.User - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"name": user.Name}).Decode(&migratedUser) - assert.NoError(t, err) - assert.False(t, migratedUser.MFA.Enabled) - assert.Equal(t, "", migratedUser.MFA.Secret) - assert.Empty(t, migratedUser.MFA.RecoveryCodes) - - err = migrates.Down(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - err = c.Database("test").Collection("users").FindOne(context.TODO(), bson.M{"name": user.Name}).Decode(&migratedUser) - assert.NoError(t, err) - assert.False(t, migratedUser.MFA.Enabled) - assert.Equal(t, "", migratedUser.MFA.Secret) - assert.Empty(t, migratedUser.MFA.RecoveryCodes) -} diff --git a/api/store/mongo/migrations/migration_64.go b/api/store/mongo/migrations/migration_64.go deleted file mode 100644 index ae1d6c05214..00000000000 --- a/api/store/mongo/migrations/migration_64.go +++ /dev/null @@ -1,61 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration64 = migrate.Migration{ - Version: 64, - Description: "Adding the 'settings.connection_announcement' attribute to the namespace if it does not already exist.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 64, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "settings.connection_announcement": bson.M{"$in": []interface{}{nil, ""}}, - } - - update := bson.M{ - "$set": bson.M{ - "settings.connection_announcement": "", - }, - } - - _, err := db. - Collection("namespaces"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 64, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "settings.connection_announcement": bson.M{"$in": []interface{}{nil, ""}}, - } - - update := bson.M{ - "$unset": bson.M{ - "settings.connection_announcement": "", - }, - } - - _, err := db. - Collection("namespaces"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_64_test.go b/api/store/mongo/migrations/migration_64_test.go deleted file mode 100644 index 74ceb470696..00000000000 --- a/api/store/mongo/migrations/migration_64_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration64(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 64", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, models.Namespace{ - TenantID: "00000000-0000-4000-0000-000000000000", - Settings: &models.NamespaceSettings{}, - }) - - return err - }, - test: func() error { - migrations := GenerateMigrations()[63:64] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - ns := new(models.Namespace) - if err := query.Decode(ns); err != nil { - return errors.New("unable to find the namespace") - } - - if ns.Settings.ConnectionAnnouncement != "" { - return errors.New("unable to apply the migration") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - assert.NoError(t, tc.test()) - }) - } -} diff --git a/api/store/mongo/migrations/migration_65.go b/api/store/mongo/migrations/migration_65.go deleted file mode 100644 index 9a7f9371294..00000000000 --- a/api/store/mongo/migrations/migration_65.go +++ /dev/null @@ -1,61 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration65 = migrate.Migration{ - Version: 65, - Description: "Adding the 'recovery_email' attribute to the user if it does not already exist.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 65, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "recovery_email": bson.M{"$in": []interface{}{nil, ""}}, - } - - update := bson.M{ - "$set": bson.M{ - "recovery_email": "", - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 65, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "_id": bson.M{"$ne": nil}, - } - - update := bson.M{ - "$unset": bson.M{ - "recovery_email": "", - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_65_test.go b/api/store/mongo/migrations/migration_65_test.go deleted file mode 100644 index dc0fc726c13..00000000000 --- a/api/store/mongo/migrations/migration_65_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration65(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 65", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, models.User{ - UserData: models.UserData{ - Username: "john_doe", - }, - }) - - return err - }, - test: func() error { - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[64]) - if err := migrates.Up(context.Background(), migrate.AllAvailable); err != nil { - return err - } - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"username": "john_doe"}) - - user := new(models.User) - if err := query.Decode(user); err != nil { - return errors.New("unable to find the user") - } - - if user.RecoveryEmail != "" { - return errors.New("unable to apply the migration") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - assert.NoError(t, tc.test()) - }) - } -} diff --git a/api/store/mongo/migrations/migration_66.go b/api/store/mongo/migrations/migration_66.go deleted file mode 100644 index 525362be39f..00000000000 --- a/api/store/mongo/migrations/migration_66.go +++ /dev/null @@ -1,67 +0,0 @@ -package migrations - -import ( - "context" - - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration66 = migrate.Migration{ - Version: 66, - Description: "Replace the user's MFA attributes.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log. - WithFields(log.Fields{ - "component": "migration", - "version": 66, - "action": "Up", - }). - Info("Applying migration") - - filter := bson.M{ - "_id": bson.M{ - "$ne": nil, - }, - } - - rename := bson.M{ - "$rename": bson.M{ - "status_mfa": "mfa.enabled", - "secret": "mfa.secret", - "codes": "mfa.recovery_codes", - }, - } - - if _, err := db.Collection("users").UpdateMany(ctx, filter, rename); err != nil { - return err - } - - unset := bson.M{ - "$unset": bson.M{ - "status_mfa": "", - "secret": "", - "codes": "", - }, - } - - _, err := db.Collection("users").UpdateMany(ctx, filter, unset) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - log. - WithFields(log.Fields{ - "component": "migration", - "version": 66, - "action": "Up", - }). - Info("Applying migration") - - log.Info("Unable to undo the MFA object") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_66_test.go b/api/store/mongo/migrations/migration_66_test.go deleted file mode 100644 index 3fbc6686f0f..00000000000 --- a/api/store/mongo/migrations/migration_66_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration66Up(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - expected map[string]interface{} - }{ - { - description: "Success to apply up on migration 66", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "username": "john_doe", - "status_mfa": true, - "secret": "secret", - "codes": []string{"code-1", "code-2"}, - }) - - return err - }, - expected: map[string]interface{}{ - "mfa": map[string]interface{}{ - "enabled": true, - "secret": "secret", - "recovery_codes": primitive.A{"code-1", "code-2"}, - }, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[65]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"username": "john_doe"}) - - user := make(map[string]interface{}) - require.NoError(t, query.Decode(&user)) - - attr, ok := user["mfa"] - require.Equal(t, true, ok) - require.Equal(t, tc.expected["mfa"], attr) - - _, ok = user["status_mfa"] - require.Equal(t, false, ok) - _, ok = user["secret"] - require.Equal(t, false, ok) - _, ok = user["codes"] - require.Equal(t, false, ok) - }) - } -} diff --git a/api/store/mongo/migrations/migration_67.go b/api/store/mongo/migrations/migration_67.go deleted file mode 100644 index 2127021c9fb..00000000000 --- a/api/store/mongo/migrations/migration_67.go +++ /dev/null @@ -1,91 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/hash" - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration67 = migrate.Migration{ - Version: 67, - Description: "Hash the user's MFA recovery code before storing it as a plain string.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log. - WithFields(log.Fields{ - "component": "migration", - "version": 67, - "action": "Up", - }). - Info("Applying migration") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "mfa.enabled": true, - "mfa.recovery_codes.0": bson.M{ - "$not": bson.M{ - "$regex": "^\\$", - }, - }, - }, - }, - } - - cursor, err := db.Collection("users").Aggregate(ctx, pipeline) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - - for cursor.Next(ctx) { - user := new(models.User) - if err := cursor.Decode(user); err != nil { - return err - } - - recoveryCodes := make([]string, 0) - for _, c := range user.MFA.RecoveryCodes { - hash, err := hash.Do(c) - if err != nil { - return err - } - - recoveryCodes = append(recoveryCodes, hash) - - } - - filter := bson.M{"username": user.Username} - update := bson.M{"$set": bson.M{"mfa.recovery_codes": recoveryCodes}} - - updateModels = append(updateModels, mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(false)) - } - - if len(updateModels) > 0 { - if _, err := db.Collection("users").BulkWrite(ctx, updateModels); err != nil { - return err - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - log. - WithFields(log.Fields{ - "component": "migration", - "version": 67, - "action": "Down", - }). - Info("Applying migration") - - log.Info("Unable to undo the recovery code hash") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_67_test.go b/api/store/mongo/migrations/migration_67_test.go deleted file mode 100644 index fe728ca5e74..00000000000 --- a/api/store/mongo/migrations/migration_67_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package migrations - -import ( - "context" - "strings" - "testing" - - "github.com/labstack/gommon/log" - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/hash" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration67Up(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - recoveryCodes []string - test func() error - }{ - { - description: "Success to apply up on migration 67", - recoveryCodes: []string{"secret-1", "secret-2"}, - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, models.User{ - UserData: models.UserData{ - Username: "john_doe", - }, - MFA: models.UserMFA{ - Enabled: true, - RecoveryCodes: []string{"secret-1", "secret-2"}, - }, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[66]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"username": "john_doe"}) - - user := new(models.User) - require.NoError(t, query.Decode(user)) - - log.Infof("user: %+v", user) - - require.Equal(t, len(tc.recoveryCodes), len(user.MFA.RecoveryCodes)) - for i, c := range tc.recoveryCodes { - require.NotEqual(t, c, user.MFA.RecoveryCodes[i]) - require.Equal(t, true, strings.HasPrefix(user.MFA.RecoveryCodes[i], "$")) - require.Equal(t, true, hash.CompareWith(c, user.MFA.RecoveryCodes[i])) - } - }) - } -} diff --git a/api/store/mongo/migrations/migration_68.go b/api/store/mongo/migrations/migration_68.go deleted file mode 100644 index d06123d32ad..00000000000 --- a/api/store/mongo/migrations/migration_68.go +++ /dev/null @@ -1,81 +0,0 @@ -package migrations - -import ( - "context" - - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration68 = migrate.Migration{ - Version: 68, - Description: "Rename `api_keys.user_id` to `api_keys.created_by`.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log. - WithFields(log.Fields{ - "component": "migration", - "version": 68, - "action": "Up", - }). - Info("Applying migration") - - filter := bson.M{ - "user_id": bson.M{"$nin": []interface{}{nil, ""}}, - } - - rename := bson.M{ - "$rename": bson.M{ - "user_id": "created_by", - }, - } - - if _, err := db.Collection("api_keys").UpdateMany(ctx, filter, rename); err != nil { - return err - } - - unset := bson.M{ - "$unset": bson.M{ - "user_id": "", - }, - } - - _, err := db.Collection("api_keys").UpdateMany(ctx, filter, unset) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log. - WithFields(log.Fields{ - "component": "migration", - "version": 68, - "action": "Down", - }). - Info("Applying migration") - - filter := bson.M{ - "created_by": bson.M{"$nin": []interface{}{nil, ""}}, - } - - rename := bson.M{ - "$rename": bson.M{ - "created_by": "user_id", - }, - } - - if _, err := db.Collection("api_keys").UpdateMany(ctx, filter, rename); err != nil { - return err - } - - unset := bson.M{ - "$unset": bson.M{ - "created_by": "", - }, - } - - _, err := db.Collection("api_keys").UpdateMany(ctx, filter, unset) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_68_test.go b/api/store/mongo/migrations/migration_68_test.go deleted file mode 100644 index 8f9e17b54e8..00000000000 --- a/api/store/mongo/migrations/migration_68_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration68Up(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - expected map[string]interface{} - }{ - { - description: "Success to apply up on migration 68", - setup: func() error { - _, err := c. - Database("test"). - Collection("api_keys"). - InsertOne(ctx, map[string]interface{}{ - "name": "dev", - "user_id": "000000000000000000000000", - }) - - return err - }, - expected: map[string]interface{}{ - "name": "dev", - "created_by": "000000000000000000000000", - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[67]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("api_keys"). - FindOne(context.TODO(), bson.M{"name": "dev"}) - - apiKey := make(map[string]interface{}) - require.NoError(t, query.Decode(&apiKey)) - - _, ok := apiKey["user_id"] - require.Equal(t, false, ok) - - attr, ok := apiKey["created_by"] - require.Equal(t, true, ok) - require.Equal(t, tc.expected["created_by"], attr) - }) - } -} - -func TestMigration68Down(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - expected map[string]interface{} - }{ - { - description: "Success to apply down on migration 68", - setup: func() error { - _, err := c. - Database("test"). - Collection("api_keys"). - InsertOne(ctx, map[string]interface{}{ - "name": "dev", - "created_by": "000000000000000000000000", - }) - - return err - }, - expected: map[string]interface{}{ - "name": "dev", - "user_id": "000000000000000000000000", - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[67]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("api_keys"). - FindOne(context.TODO(), bson.M{"name": "dev"}) - - apiKey := make(map[string]interface{}) - require.NoError(t, query.Decode(&apiKey)) - - _, ok := apiKey["created_by"] - require.Equal(t, false, ok) - - attr, ok := apiKey["user_id"] - require.Equal(t, true, ok) - require.Equal(t, tc.expected["user_id"], attr) - }) - } -} diff --git a/api/store/mongo/migrations/migration_69.go b/api/store/mongo/migrations/migration_69.go deleted file mode 100644 index 9d2c069a5f5..00000000000 --- a/api/store/mongo/migrations/migration_69.go +++ /dev/null @@ -1,106 +0,0 @@ -package migrations - -import ( - "context" - "crypto/sha256" - "encoding/hex" - - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration69 = migrate.Migration{ - Version: 69, - Description: "Hash API key ID. It will delete the old document and create a new one with the hashed ID.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log. - WithFields(log.Fields{ - "component": "migration", - "version": 69, - "action": "Up", - }). - Info("Applying migration") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "_id": bson.M{ - "$regex": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$", - }, - }, - }, - } - - cursor, err := db.Collection("api_keys").Aggregate(ctx, pipeline) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - deleteModels := make([]mongo.WriteModel, 0) - - for cursor.Next(ctx) { - apiKey := new(models.APIKey) - if err := cursor.Decode(apiKey); err != nil { - return err - } - - idSum := sha256.Sum256([]byte(apiKey.ID)) - hashedID := hex.EncodeToString(idSum[:]) - - doc := &models.APIKey{ - ID: hashedID, - Name: apiKey.Name, - CreatedBy: apiKey.CreatedBy, - TenantID: apiKey.TenantID, - Role: apiKey.Role, - CreatedAt: apiKey.CreatedAt, - UpdatedAt: apiKey.UpdatedAt, - ExpiresIn: apiKey.ExpiresIn, - } - - deleteModels = append(deleteModels, mongo.NewDeleteOneModel().SetFilter(bson.M{"_id": apiKey.ID})) - updateModels = append(updateModels, mongo.NewInsertOneModel().SetDocument(doc)) - } - - if len(updateModels) > 0 || len(deleteModels) > 0 { - mongoSession, err := db.Client().StartSession() - if err != nil { - return err - } - defer mongoSession.EndSession(ctx) - - _, err = mongoSession.WithTransaction(ctx, func(_ mongo.SessionContext) (interface{}, error) { - if _, err := db.Collection("api_keys").BulkWrite(ctx, updateModels); err != nil { - return nil, err - } - - _, err := db.Collection("api_keys").BulkWrite(ctx, deleteModels) - - return nil, err - }) - if err != nil { - return err - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - log. - WithFields(log.Fields{ - "component": "migration", - "version": 69, - "action": "Down", - }). - Info("Applying migration") - - log.Info("Unable to undo the api key hash") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_69_test.go b/api/store/mongo/migrations/migration_69_test.go deleted file mode 100644 index 8f6f0a8b760..00000000000 --- a/api/store/mongo/migrations/migration_69_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package migrations - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - mongodb "go.mongodb.org/mongo-driver/mongo" -) - -func TestMigration69Up(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - plainID string - test func() error - }{ - { - description: "Success to apply up on migration 69", - plainID: "343d67d3-5084-4845-ab10-59891c88ec76", - setup: func() error { - _, err := c. - Database("test"). - Collection("api_keys"). - InsertOne(ctx, models.APIKey{ID: "343d67d3-5084-4845-ab10-59891c88ec76"}) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[68]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - idSum := sha256.Sum256([]byte(tc.plainID)) - hashedID := hex.EncodeToString(idSum[:]) - - old := c. - Database("test"). - Collection("api_keys"). - FindOne(context.TODO(), bson.M{"_id": tc.plainID}). - Decode(&models.APIKey{}) - require.Equal(t, mongodb.ErrNoDocuments, old) - - query := c. - Database("test"). - Collection("api_keys"). - FindOne(context.TODO(), bson.M{"_id": hashedID}) - - apiKey := new(models.APIKey) - require.NoError(t, query.Decode(apiKey)) - require.Equal(t, hashedID, apiKey.ID) - }) - } -} diff --git a/api/store/mongo/migrations/migration_6_test.go b/api/store/mongo/migrations/migration_6_test.go deleted file mode 100644 index 31bbf332987..00000000000 --- a/api/store/mongo/migrations/migration_6_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration6(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:5]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - device1 := models.Device{ - Status: "accepted", - } - - device2 := models.Device{ - Status: "accepted", - } - - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device2) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:6]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_7.go b/api/store/mongo/migrations/migration_7.go deleted file mode 100644 index 11a0d44dc2e..00000000000 --- a/api/store/mongo/migrations/migration_7.go +++ /dev/null @@ -1,57 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration7 = migrate.Migration{ - Version: 7, - Description: "Unset unique on uid and message in the recoded_sessions collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 7, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"uid", 1}}, - Options: options.Index().SetName("uid").SetUnique(false), - } - if _, err := db.Collection("recorded_sessions").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - - mod = mongo.IndexModel{ - Keys: bson.D{{"message", 1}}, - Options: options.Index().SetName("message").SetUnique(false), - } - _, err := db.Collection("recorded_sessions").Indexes().CreateOne(ctx, mod) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 7, - "action": "Down", - }).Info("Applying migration") - if _, err := db.Collection("recorded_sessions").UpdateMany(ctx, bson.M{}, bson.M{"$unset": bson.M{"uid": ""}}); err != nil { - return err - } - if _, err := db.Collection("recorded_sessions").UpdateMany(ctx, bson.M{}, bson.M{"$unset": bson.M{"message": ""}}); err != nil { - return err - } - if _, err := db.Collection("recorded_sessions").Indexes().DropOne(ctx, "uid"); err != nil { - return err - } - _, err := db.Collection("recorded_sessions").Indexes().DropOne(ctx, "message") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_70.go b/api/store/mongo/migrations/migration_70.go deleted file mode 100644 index ee453654522..00000000000 --- a/api/store/mongo/migrations/migration_70.go +++ /dev/null @@ -1,61 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration70 = migrate.Migration{ - Version: 70, - Description: "Adding the 'preferences' attribute to the user if it does not already exist.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 70, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "preferences": bson.M{"$exists": false}, - } - - update := bson.M{ - "$set": bson.M{ - "preferences": bson.M{}, - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 70, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "preferences": bson.M{"$exists": true}, - } - - update := bson.M{ - "$unset": bson.M{ - "preferences": "", - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_70_test.go b/api/store/mongo/migrations/migration_70_test.go deleted file mode 100644 index 7b14993a9fb..00000000000 --- a/api/store/mongo/migrations/migration_70_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration70Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 70", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[69]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(t, query.Decode(&user)) - - _, ok := user["preferences"] - require.Equal(t, true, ok) - }) - } -} - -func TestMigration70Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 70", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - "preferences": map[string]interface{}{}, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[69]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(t, query.Decode(&user)) - - _, ok := user["preferences"] - require.Equal(t, false, ok) - }) - } -} diff --git a/api/store/mongo/migrations/migration_71.go b/api/store/mongo/migrations/migration_71.go deleted file mode 100644 index aa83563bd77..00000000000 --- a/api/store/mongo/migrations/migration_71.go +++ /dev/null @@ -1,63 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration71 = migrate.Migration{ - Version: 71, - Description: "Adding the 'preferences.preferred_namespace' attribute to the user if it does not already exist.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 71, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "preferences": bson.M{"$exists": true}, - "preferences.preferred_namespace": bson.M{"$exists": false}, - } - - update := bson.M{ - "$set": bson.M{ - "preferences.preferred_namespace": "", - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 71, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "preferences": bson.M{"$exists": true}, - "preferences.preferred_namespace": bson.M{"$exists": true}, - } - - update := bson.M{ - "$unset": bson.M{ - "preferences.preferred_namespace": "", - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_71_test.go b/api/store/mongo/migrations/migration_71_test.go deleted file mode 100644 index 6696ad1f980..00000000000 --- a/api/store/mongo/migrations/migration_71_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration71Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 71", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - "preferences": map[string]interface{}{}, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[70]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(t, query.Decode(&user)) - - preferences := user["preferences"] - _, ok := preferences.(map[string]interface{})["preferred_namespace"] - require.Equal(t, true, ok) - }) - } -} - -func TestMigration71Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 71", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - "preferences": map[string]interface{}{ - "preferred_namespace": "00000000-0000-4000-0000-000000000000", - }, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[70]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(t, query.Decode(&user)) - - preferences := user["preferences"] - _, ok := preferences.(map[string]interface{})["preferred_namespace"] - require.Equal(t, false, ok) - }) - } -} diff --git a/api/store/mongo/migrations/migration_72.go b/api/store/mongo/migrations/migration_72.go deleted file mode 100644 index e99e3f6a7a7..00000000000 --- a/api/store/mongo/migrations/migration_72.go +++ /dev/null @@ -1,118 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration72 = migrate.Migration{ - Version: 72, - Description: "Adding the 'members.$.status' attribute to the namespace if it does not already exist.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 72, - "action": "Up", - }).Info("Applying migration") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "tenant_id": bson.M{ - "$exists": true, - }, - }, - }, - } - - cursor, err := db.Collection("namespaces").Aggregate(ctx, pipeline) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - - for cursor.Next(ctx) { - namespace := new(models.Namespace) - if err := cursor.Decode(namespace); err != nil { - return err - } - - for _, m := range namespace.Members { - if m.Status == "" { - updateModel := mongo. - NewUpdateOneModel(). - SetFilter(bson.M{"tenant_id": namespace.TenantID, "members": bson.M{"$elemMatch": bson.M{"id": m.ID}}}). - SetUpdate(bson.M{"$set": bson.M{"members.$.status": models.DeviceStatusAccepted}}) - - updateModels = append(updateModels, updateModel) - } - } - } - - if len(updateModels) == 0 { - return nil - } - - _, err = db.Collection("namespaces").BulkWrite(ctx, updateModels) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 72, - "action": "Down", - }).Info("Reverting migration") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "tenant_id": bson.M{ - "$exists": true, - }, - }, - }, - } - - cursor, err := db.Collection("namespaces").Aggregate(ctx, pipeline) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - - for cursor.Next(ctx) { - namespace := new(models.Namespace) - if err := cursor.Decode(namespace); err != nil { - return err - } - - for _, m := range namespace.Members { - if m.Status != "" { - updateModel := mongo. - NewUpdateOneModel(). - SetFilter(bson.M{"tenant_id": namespace.TenantID, "members": bson.M{"$elemMatch": bson.M{"id": m.ID}}}). - SetUpdate(bson.M{"$unset": bson.M{"members.$.status": ""}}) - - updateModels = append(updateModels, updateModel) - } - } - } - - if len(updateModels) == 0 { - return nil - } - - _, err = db.Collection("namespaces").BulkWrite(ctx, updateModels) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_72_test.go b/api/store/mongo/migrations/migration_72_test.go deleted file mode 100644 index dd7c32648f4..00000000000 --- a/api/store/mongo/migrations/migration_72_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration72Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 72", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, map[string]interface{}{ - "tenant_id": "00000000-0000-4000-0000-000000000000", - "members": []map[string]interface{}{ - { - "id": "000000000000000000000000", - }, - }, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[71]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - namespace := make(map[string]interface{}) - require.NoError(t, query.Decode(&namespace)) - - members := namespace["members"].(primitive.A) - for _, m := range members { - val, ok := m.(map[string]interface{})["status"] - require.Equal(t, true, ok) - require.Equal(t, "accepted", val) - } - }) - } -} - -func TestMigration72Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 72", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, map[string]interface{}{ - "tenant_id": "00000000-0000-4000-0000-000000000000", - "members": []map[string]interface{}{ - { - "id": "000000000000000000000000", - "status": "accepted", - }, - }, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[71]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - namespace := make(map[string]interface{}) - require.NoError(t, query.Decode(&namespace)) - - members := namespace["members"].(primitive.A) - for _, m := range members { - _, ok := m.(map[string]interface{})["status"] - require.Equal(t, false, ok) - } - }) - } -} diff --git a/api/store/mongo/migrations/migration_73.go b/api/store/mongo/migrations/migration_73.go deleted file mode 100644 index 3a6f8aac75f..00000000000 --- a/api/store/mongo/migrations/migration_73.go +++ /dev/null @@ -1,120 +0,0 @@ -package migrations - -import ( - "context" - "time" - - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration73 = migrate.Migration{ - Version: 73, - Description: "Adding the 'members.$.added_at' attribute to the namespace if it does not already exist. The value is the Go time.Time zeroer", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 73, - "action": "Up", - }).Info("Applying migration") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "tenant_id": bson.M{ - "$exists": true, - }, - }, - }, - } - - cursor, err := db.Collection("namespaces").Aggregate(ctx, pipeline) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - - for cursor.Next(ctx) { - namespace := new(models.Namespace) - if err := cursor.Decode(namespace); err != nil { - return err - } - - for _, m := range namespace.Members { - if m.AddedAt.Equal((time.Time{})) { - updateModel := mongo. - NewUpdateOneModel(). - SetFilter(bson.M{"tenant_id": namespace.TenantID, "members": bson.M{"$elemMatch": bson.M{"id": m.ID}}}). - // We update the added_at field to the same value as in the if statement - // because when the attribute is null in MongoDB, it will be converted - // to the zero value of time.Time. - SetUpdate(bson.M{"$set": bson.M{"members.$.added_at": time.Time{}}}) - - updateModels = append(updateModels, updateModel) - } - } - } - - if len(updateModels) == 0 { - return nil - } - - _, err = db.Collection("namespaces").BulkWrite(ctx, updateModels) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 73, - "action": "Down", - }).Info("Reverting migration") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "tenant_id": bson.M{ - "$exists": true, - }, - }, - }, - } - - cursor, err := db.Collection("namespaces").Aggregate(ctx, pipeline) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - - for cursor.Next(ctx) { - namespace := new(models.Namespace) - if err := cursor.Decode(namespace); err != nil { - return err - } - - for _, m := range namespace.Members { - updateModel := mongo. - NewUpdateOneModel(). - SetFilter(bson.M{"tenant_id": namespace.TenantID, "members": bson.M{"$elemMatch": bson.M{"id": m.ID}}}). - SetUpdate(bson.M{"$unset": bson.M{"members.$.added_at": ""}}) - - updateModels = append(updateModels, updateModel) - } - } - - if len(updateModels) == 0 { - return nil - } - - _, err = db.Collection("namespaces").BulkWrite(ctx, updateModels) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_73_test.go b/api/store/mongo/migrations/migration_73_test.go deleted file mode 100644 index d1ad8aee8b8..00000000000 --- a/api/store/mongo/migrations/migration_73_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package migrations - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration73Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 73", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, map[string]interface{}{ - "tenant_id": "00000000-0000-4000-0000-000000000000", - "members": []map[string]interface{}{ - { - "id": "000000000000000000000000", - }, - }, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[72]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - namespace := make(map[string]interface{}) - require.NoError(t, query.Decode(&namespace)) - - members := namespace["members"].(primitive.A) - for _, m := range members { - _, ok := m.(map[string]interface{})["added_at"] - require.Equal(t, true, ok) - } - }) - } -} - -func TestMigration73Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 73", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, map[string]interface{}{ - "tenant_id": "00000000-0000-4000-0000-000000000000", - "members": []map[string]interface{}{ - { - "id": "000000000000000000000000", - "added_at": time.Now(), - }, - }, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[72]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - namespace := make(map[string]interface{}) - require.NoError(t, query.Decode(&namespace)) - - members := namespace["members"].(primitive.A) - for _, m := range members { - _, ok := m.(map[string]interface{})["added_at"] - require.Equal(t, false, ok) - } - }) - } -} diff --git a/api/store/mongo/migrations/migration_74.go b/api/store/mongo/migrations/migration_74.go deleted file mode 100644 index b195729cee8..00000000000 --- a/api/store/mongo/migrations/migration_74.go +++ /dev/null @@ -1,68 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/envs" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration74 = migrate.Migration{ - Version: 74, - Description: "Adding default message on announcement if is not set.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 74, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "settings.connection_announcement": "", - } - - annoucementMsg := "" - if envs.IsCommunity() { - annoucementMsg = models.DefaultAnnouncementMessage - } - - update := bson.M{ - "$set": bson.M{ - "settings.connection_announcement": annoucementMsg, - }, - } - - _, err := db. - Collection("namespaces"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 74, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "settings.connection_announcement": models.DefaultAnnouncementMessage, - } - - update := bson.M{ - "$set": bson.M{ - "settings.connection_announcement": "", - }, - } - - _, err := db. - Collection("namespaces"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_74_test.go b/api/store/mongo/migrations/migration_74_test.go deleted file mode 100644 index 34a6be6cfa6..00000000000 --- a/api/store/mongo/migrations/migration_74_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - env_mocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -var envMock *env_mocks.Backend - -func TestMigration74(t *testing.T) { - ctx := context.Background() - - envMock = &env_mocks.Backend{} - envs.DefaultBackend = envMock - - cases := []struct { - description string - setup func() error - requireMocks func() - test func() error - }{ - { - description: "Success to apply up on migration 74, without message on cloud", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, models.Namespace{ - TenantID: "00000000-0000-4000-0000-000000000000", - Settings: &models.NamespaceSettings{}, - }) - - return err - }, - requireMocks: func() { - envMock.On("Get", "SHELLHUB_CLOUD").Return("true").Once() - }, - test: func() error { - migrations := GenerateMigrations()[73:74] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - ns := new(models.Namespace) - if err := query.Decode(ns); err != nil { - return errors.New("unable to find the namespace") - } - - if ns.Settings.ConnectionAnnouncement != "" { - return errors.New("unable to apply the migration") - } - - return nil - }, - }, { - description: "Success to apply up on migration 74, with message on community", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, models.Namespace{ - TenantID: "00000000-0000-4000-0000-000000000000", - Settings: &models.NamespaceSettings{}, - }) - - return err - }, - requireMocks: func() { - envMock.On("Get", "SHELLHUB_CLOUD").Return("false").Once() - envMock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Once() - }, - test: func() error { - migrations := GenerateMigrations()[73:74] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - ns := new(models.Namespace) - if err := query.Decode(ns); err != nil { - return errors.New("unable to find the namespace") - } - - if ns.Settings.ConnectionAnnouncement != models.DefaultAnnouncementMessage { - return errors.New("unable to apply the migration") - } - - return nil - }, - }, { - description: "Success to unapply the migration 74", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, models.Namespace{ - TenantID: "00000000-0000-4000-0000-000000000000", - Settings: &models.NamespaceSettings{}, - }) - - return err - }, - requireMocks: func() {}, - test: func() error { - migrations := GenerateMigrations()[73:74] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - ns := new(models.Namespace) - if err := query.Decode(ns); err != nil { - return errors.New("unable to find the namespace") - } - - if ns.Settings.ConnectionAnnouncement != "" { - return errors.New("unable to unapply the migration") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - tc.requireMocks() - - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - assert.NoError(t, tc.test()) - - envMock.AssertExpectations(t) - }) - } -} diff --git a/api/store/mongo/migrations/migration_75.go b/api/store/mongo/migrations/migration_75.go deleted file mode 100644 index 9e4aec7032c..00000000000 --- a/api/store/mongo/migrations/migration_75.go +++ /dev/null @@ -1,126 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration75 = migrate.Migration{ - Version: 75, - Description: "Convert user.confirmed to user.status", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 75, - "action": "Up", - }).Info("Applying migration") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "confirmed": bson.M{ - "$exists": true, - }, - "status": bson.M{ - "$exists": false, - }, - }, - }, - } - - cursor, err := db.Collection("users").Aggregate(ctx, pipeline) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - - for cursor.Next(ctx) { - user := make(map[string]interface{}) - if err := cursor.Decode(&user); err != nil { - return err - } - - updateModel := mongo. - NewUpdateOneModel(). - SetFilter(bson.M{"_id": user["_id"]}) - - if confirmed := user["confirmed"]; confirmed == true { - updateModel.SetUpdate(bson.M{"$set": bson.M{"status": models.UserStatusConfirmed.String()}, "$unset": bson.M{"confirmed": ""}}) - } else { - updateModel.SetUpdate(bson.M{"$set": bson.M{"status": models.UserStatusNotConfirmed.String()}, "$unset": bson.M{"confirmed": ""}}) - } - - updateModels = append(updateModels, updateModel) - } - - if len(updateModels) == 0 { - return nil - } - - _, err = db.Collection("users").BulkWrite(ctx, updateModels) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 75, - "action": "Down", - }).Info("Reverting migration") - - pipeline := []bson.M{ - { - "$match": bson.M{ - "confirmed": bson.M{ - "$exists": false, - }, - "status": bson.M{ - "$exists": true, - }, - }, - }, - } - - cursor, err := db.Collection("users").Aggregate(ctx, pipeline) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - - for cursor.Next(ctx) { - user := make(map[string]interface{}) - if err := cursor.Decode(&user); err != nil { - return err - } - - updateModel := mongo. - NewUpdateOneModel(). - SetFilter(bson.M{"_id": user["_id"]}) - - if status := user["status"].(string); status == models.UserStatusConfirmed.String() { - updateModel.SetUpdate(bson.M{"$set": bson.M{"confirmed": true}, "$unset": bson.M{"status": ""}}) - } else { - updateModel.SetUpdate(bson.M{"$set": bson.M{"confirmed": false}, "$unset": bson.M{"status": ""}}) - } - - updateModels = append(updateModels, updateModel) - } - - if len(updateModels) == 0 { - return nil - } - - _, err = db.Collection("users").BulkWrite(ctx, updateModels) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_75_test.go b/api/store/mongo/migrations/migration_75_test.go deleted file mode 100644 index f35747b7616..00000000000 --- a/api/store/mongo/migrations/migration_75_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration75Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func(primitive.ObjectID) error - expected string - }{ - { - description: "Success to apply up on migration 75 with confirmed == true", - setup: func(objID primitive.ObjectID) error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "_id": objID, - "confirmed": true, - }) - - return err - }, - expected: models.UserStatusConfirmed.String(), - }, - { - description: "Success to apply up on migration 75 with confirmed == false", - setup: func(objID primitive.ObjectID) error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "_id": objID, - "confirmed": false, - }) - - return err - }, - expected: models.UserStatusNotConfirmed.String(), - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - objID := primitive.NewObjectID() - assert.NoError(tt, tc.setup(objID)) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[74]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"_id": objID}) - - user := make(map[string]interface{}) - require.NoError(tt, query.Decode(&user)) - - _, ok := user["confirmed"] - require.Equal(tt, false, ok) - - status, ok := user["status"].(string) - require.Equal(tt, true, ok) - require.Equal(tt, tc.expected, status) - }) - } -} - -func TestMigration75Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func(primitive.ObjectID) error - expected bool - }{ - { - description: "Success to apply up on migration 75 with status confirmed", - setup: func(objID primitive.ObjectID) error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "_id": objID, - "status": models.UserStatusConfirmed.String(), - }) - - return err - }, - expected: true, - }, - { - description: "Success to apply up on migration 75 with status unconfirmed", - setup: func(objID primitive.ObjectID) error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "_id": objID, - "status": models.UserStatusNotConfirmed.String(), - }) - - return err - }, - expected: false, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - objID := primitive.NewObjectID() - assert.NoError(tt, tc.setup(objID)) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[74]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(tt, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"_id": objID}) - - user := make(map[string]interface{}) - require.NoError(tt, query.Decode(&user)) - - _, ok := user["status"] - require.Equal(tt, false, ok) - - confirmed, ok := user["confirmed"].(bool) - require.Equal(tt, true, ok) - require.Equal(tt, tc.expected, confirmed) - }) - } -} diff --git a/api/store/mongo/migrations/migration_76.go b/api/store/mongo/migrations/migration_76.go deleted file mode 100644 index 6254185676f..00000000000 --- a/api/store/mongo/migrations/migration_76.go +++ /dev/null @@ -1,87 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration76 = migrate.Migration{ - Version: 76, - Description: "Remove user.namespace from users collection.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 76). - WithField("action", " Up"). - Info("Applying migration") - - filter := bson.M{"namespaces": bson.M{"$exists": true}} - update := bson.M{"$unset": bson.M{"namespaces": ""}} - - _, err := db.Collection("users").UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 76). - WithField("action", "Down"). - Info("Applying migration") - - filter := []bson.M{ - { - "$match": bson.M{ - "namespaces": bson.M{ - "$exists": false, - }, - }, - }, - } - - cursor, err := db.Collection("users").Aggregate(ctx, filter) - if err != nil { - return err - } - defer cursor.Close(ctx) - - updateModels := make([]mongo.WriteModel, 0) - for cursor.Next(ctx) { - user := new(models.User) - if err := cursor.Decode(user); err != nil { - return err - } - - cursor, err := db.Collection("namespaces").Find(ctx, bson.M{"members": bson.M{"$elemMatch": bson.M{"id": user.ID, "role": "owner"}}}) - if err != nil { - return err - } - defer cursor.Close(ctx) - - namespaces := make([]models.Namespace, 0) - if err := cursor.All(ctx, &namespaces); err != nil { - continue - } - - userID, _ := primitive.ObjectIDFromHex(user.ID) - updateModel := mongo. - NewUpdateOneModel(). - SetFilter(bson.M{"_id": userID}). - SetUpdate(bson.M{"$set": bson.M{"namespaces": len(namespaces)}}) - - updateModels = append(updateModels, updateModel) - } - - if len(updateModels) == 0 { - return nil - } - - _, err = db.Collection("users").BulkWrite(ctx, updateModels) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_76_test.go b/api/store/mongo/migrations/migration_76_test.go deleted file mode 100644 index b8c7232e09b..00000000000 --- a/api/store/mongo/migrations/migration_76_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration76Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func(primitive.ObjectID) error - }{ - { - description: "Success to apply up on migration 76", - setup: func(objID primitive.ObjectID) error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "_id": objID, - "namespaces": 1, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - objID := primitive.NewObjectID() - assert.NoError(tt, tc.setup(objID)) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[75]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"_id": objID}) - - user := make(map[string]interface{}) - require.NoError(tt, query.Decode(&user)) - - _, ok := user["namespaces"] - require.Equal(tt, false, ok) - }) - } -} - -func TestMigration76Down(t *testing.T) { - db := c.Database("test") - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func(primitive.ObjectID) error - expected int32 - }{ - { - description: "Success to apply up on migration 76", - setup: func(objID primitive.ObjectID) error { - _, err := db.Collection("users").InsertOne(ctx, map[string]interface{}{"_id": objID}) - if err != nil { - return err - } - - namespaces := []map[string]interface{}{ - { - "_id": primitive.NewObjectID(), - "members": []bson.M{ - {"id": objID.Hex(), "role": "owner"}, - {"id": "000000000000000000000000", "role": "observer"}, - }, - }, - { - "_id": primitive.NewObjectID(), - "members": []bson.M{ - {"id": objID.Hex(), "role": "owner"}, - {"id": "000000000000000000000000", "role": "observer"}, - }, - }, - { - "_id": primitive.NewObjectID(), - "members": []bson.M{ - {"id": objID.Hex(), "role": "observer"}, - {"id": "000000000000000000000000", "role": "owner"}, - }, - }, - } - - for _, n := range namespaces { - _, err = db.Collection("namespaces").InsertOne(ctx, n) - if err != nil { - return err - } - } - - return nil - }, - expected: 2, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - objID := primitive.NewObjectID() - assert.NoError(tt, tc.setup(objID)) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[75]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(tt, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"_id": objID}) - - user := make(map[string]interface{}) - require.NoError(tt, query.Decode(&user)) - - count, ok := user["namespaces"] - require.Equal(tt, true, ok) - require.Equal(tt, tc.expected, count.(int32)) - }) - } -} diff --git a/api/store/mongo/migrations/migration_77.go b/api/store/mongo/migrations/migration_77.go deleted file mode 100644 index 88c88216a49..00000000000 --- a/api/store/mongo/migrations/migration_77.go +++ /dev/null @@ -1,43 +0,0 @@ -package migrations - -import ( - "context" - - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration77 = migrate.Migration{ - Version: 77, - Description: "Recreate the unique index on the 'username' field in the 'users' collection with a partial filter for documents where the 'username' field is a string.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 77). - WithField("action", " Up"). - Info("Applying migration") - - _, _ = db.Collection("users").Indexes().DropOne(ctx, "username") - - indexModel := mongo.IndexModel{ - Keys: bson.M{"username": 1}, - Options: options.Index().SetName("username").SetUnique(true).SetPartialFilterExpression(bson.M{"username": bson.M{"$type": "string"}}), - } - - _, err := db.Collection("users").Indexes().CreateOne(ctx, indexModel) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 77). - WithField("action", "Down"). - Info("Reverting migration") - - _, err := db.Collection("users").Indexes().DropOne(ctx, "username") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_77_test.go b/api/store/mongo/migrations/migration_77_test.go deleted file mode 100644 index 1c77b9456c5..00000000000 --- a/api/store/mongo/migrations/migration_77_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration77Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - }{ - { - description: "Success to apply up on migration 77", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "username": nil, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[76]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "username": nil, - }) - require.NoError(tt, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_78.go b/api/store/mongo/migrations/migration_78.go deleted file mode 100644 index 5a4c848272b..00000000000 --- a/api/store/mongo/migrations/migration_78.go +++ /dev/null @@ -1,43 +0,0 @@ -package migrations - -import ( - "context" - - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration78 = migrate.Migration{ - Version: 78, - Description: "Recreate the unique index on the 'email' field in the 'users' collection with a partial filter for documents where the 'email' field is a string.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 78). - WithField("action", " Up"). - Info("Applying migration") - - _, _ = db.Collection("users").Indexes().DropOne(ctx, "email") - - indexModel := mongo.IndexModel{ - Keys: bson.M{"email": 1}, - Options: options.Index().SetName("email").SetUnique(true).SetPartialFilterExpression(bson.M{"email": bson.M{"$type": "string"}}), - } - - _, err := db.Collection("users").Indexes().CreateOne(ctx, indexModel) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 78). - WithField("action", "Down"). - Info("Reverting migration") - - _, err := db.Collection("users").Indexes().DropOne(ctx, "email") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_78_test.go b/api/store/mongo/migrations/migration_78_test.go deleted file mode 100644 index 84e6ca8edba..00000000000 --- a/api/store/mongo/migrations/migration_78_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration78Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - }{ - { - description: "Success to apply up on migration 78", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "email": nil, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[77]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "email": nil, - }) - require.NoError(tt, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_79.go b/api/store/mongo/migrations/migration_79.go deleted file mode 100644 index 9f88e04738e..00000000000 --- a/api/store/mongo/migrations/migration_79.go +++ /dev/null @@ -1,49 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/envs" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration79 = migrate.Migration{ - Version: 79, - Description: "create and populate the system collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 79). - WithField("action", " Up"). - Info("Applying migration") - - if err := db.CreateCollection(ctx, "system"); err != nil { - return err - } - - if envs.IsCommunity() { - users, err := db.Collection("users").CountDocuments(ctx, bson.M{}) - if err != nil { - return err - } - - if _, err := db.Collection("system").InsertOne(ctx, bson.M{ - "setup": users > 0, - }); err != nil { - return err - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 79). - WithField("action", "Down"). - Info("Reverting migration") - - return db.Collection("system").Drop(ctx) - }), -} diff --git a/api/store/mongo/migrations/migration_79_test.go b/api/store/mongo/migrations/migration_79_test.go deleted file mode 100644 index 6d541e7ff23..00000000000 --- a/api/store/mongo/migrations/migration_79_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration79(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - mock.On("Get", "SHELLHUB_CLOUD").Return("false") - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false") - - tests := []struct { - description string - setup func(t *testing.T) - run func(t *testing.T) - }{ - { - description: "Apply up on migration 79 when there is no user", - setup: func(_ *testing.T) {}, - run: func(t *testing.T) { - result := c.Database("test").Collection("system").FindOne(ctx, bson.M{}) - require.NoError(t, result.Err()) - - var system models.System - - err := result.Decode(&system) - require.NoError(t, err) - - assert.Equal(t, false, system.Setup) - }, - }, - { - description: "Apply up on migration 79 when there is at least one user", - setup: func(t *testing.T) { - _, err := c.Database("test").Collection("users").InsertOne(ctx, models.User{}) - require.NoError(t, err) - }, - run: func(t *testing.T) { - result := c.Database("test").Collection("system").FindOne(ctx, bson.M{}) - require.NoError(t, result.Err()) - - var system models.System - - err := result.Decode(&system) - require.NoError(t, err) - - assert.Equal(t, true, system.Setup) - }, - }, - } - - for _, test := range tests { - t.Run(test.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - test.setup(tt) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[78]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - test.run(tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_7_test.go b/api/store/mongo/migrations/migration_7_test.go deleted file mode 100644 index 6a837cd6e9b..00000000000 --- a/api/store/mongo/migrations/migration_7_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration7(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:6]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - recordedSession1 := models.RecordedSession{ - UID: "uid", - Message: "message", - } - - recordedSession2 := models.RecordedSession{ - UID: "uid", - Message: "message", - } - - _, err = c.Database("test").Collection("recorded_sessions").InsertOne(context.TODO(), recordedSession1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("recorded_sessions").InsertOne(context.TODO(), recordedSession2) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:7]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_8.go b/api/store/mongo/migrations/migration_8.go deleted file mode 100644 index 2d9b7bdb96b..00000000000 --- a/api/store/mongo/migrations/migration_8.go +++ /dev/null @@ -1,46 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration8 = migrate.Migration{ - Version: 8, - Description: "Unset unique on recorded in the sessions collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 8, - "action": "Up", - }).Info("Applying migration") - mod := mongo.IndexModel{ - Keys: bson.D{{"recorded", 1}}, - Options: options.Index().SetName("recorded").SetUnique(false), - } - if _, err := db.Collection("sessions").Indexes().CreateOne(ctx, mod); err != nil { - return err - } - _, err := db.Collection("sessions").UpdateMany(ctx, bson.M{}, bson.M{"$set": bson.M{"recorded": false}}) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 8, - "action": "Down", - }).Info("Applying migration") - if _, err := db.Collection("sessions").UpdateMany(ctx, bson.M{}, bson.M{"$unset": bson.M{"recorded": ""}}); err != nil { - return err - } - _, err := db.Collection("sessions").Indexes().DropOne(ctx, "recorded") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_80.go b/api/store/mongo/migrations/migration_80.go deleted file mode 100644 index 40b8d5bbb2f..00000000000 --- a/api/store/mongo/migrations/migration_80.go +++ /dev/null @@ -1,41 +0,0 @@ -package migrations - -import ( - "context" - - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration80 = migrate.Migration{ - Version: 80, - Description: "Remove the 'message' index from the 'recorded_sessions' collection.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 80). - WithField("action", " Up"). - Info("Applying migration") - - _, err := db.Collection("recorded_sessions").Indexes().DropOne(ctx, "message") - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 80). - WithField("action", "Down"). - Info("Applying migration") - - index := mongo.IndexModel{ - Keys: bson.D{{Key: "message", Value: 1}}, - Options: options.Index().SetName("message").SetUnique(false), - } - - _, err := db.Collection("recorded_sessions").Indexes().CreateOne(ctx, index) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_81.go b/api/store/mongo/migrations/migration_81.go deleted file mode 100644 index 0444a6e748b..00000000000 --- a/api/store/mongo/migrations/migration_81.go +++ /dev/null @@ -1,41 +0,0 @@ -package migrations - -import ( - "context" - - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration81 = migrate.Migration{ - Version: 81, - Description: "Create a 'time' index in the 'recorded_sessions' collection.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 81). - WithField("action", " Up"). - Info("Applying migration") - - index := mongo.IndexModel{ - Keys: bson.D{{Key: "time", Value: 1}}, - Options: options.Index().SetName("time").SetUnique(false), - } - - _, err := db.Collection("recorded_sessions").Indexes().CreateOne(ctx, index) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithField("component", "migration"). - WithField("version", 81). - WithField("action", "Down"). - Info("Applying migration") - - _, err := db.Collection("recorded_sessions").Indexes().DropOne(ctx, "time") - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_82.go b/api/store/mongo/migrations/migration_82.go deleted file mode 100644 index d841e06af9f..00000000000 --- a/api/store/mongo/migrations/migration_82.go +++ /dev/null @@ -1,68 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - "go.mongodb.org/mongo-driver/mongo/writeconcern" -) - -var migration82 = migrate.Migration{ - Version: 82, - Description: "Adding the 'namespaces.type' attribute to the namespaces if it does not already exist.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 82, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "type": bson.M{"$in": []interface{}{nil, ""}}, - } - - update := bson.M{ - "$set": bson.M{ - "type": models.TypeTeam, - }, - } - - _, err := db. - Collection("namespaces", - options.Collection().SetWriteConcern(writeconcern.Majority()), - ). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 82, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "type": bson.M{"$in": []interface{}{nil, ""}}, - } - - update := bson.M{ - "$unset": bson.M{ - "type": models.TypeTeam, - }, - } - - _, err := db. - Collection("namespaces", - options.Collection().SetWriteConcern(writeconcern.Majority()), - ). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_82_test.go b/api/store/mongo/migrations/migration_82_test.go deleted file mode 100644 index e0b1dbed382..00000000000 --- a/api/store/mongo/migrations/migration_82_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envMocks "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration82(t *testing.T) { - ctx := context.Background() - - mock := &envMocks.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 82", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, models.Namespace{ - TenantID: "00000000-0000-4000-0000-000000000000", - Settings: &models.NamespaceSettings{}, - }) - - return err - }, - test: func() error { - migrations := GenerateMigrations()[81:82] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - query := c. - Database("test"). - Collection("namespaces"). - FindOne(context.TODO(), bson.M{"tenant_id": "00000000-0000-4000-0000-000000000000"}) - - ns := new(models.Namespace) - if err := query.Decode(ns); err != nil { - return errors.New("unable to find the namespace") - } - - if ns.Type != models.TypeTeam { - return errors.New("unable to apply the migration") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - assert.NoError(t, tc.test()) - }) - } -} diff --git a/api/store/mongo/migrations/migration_83.go b/api/store/mongo/migrations/migration_83.go deleted file mode 100644 index 40ffe1f6e83..00000000000 --- a/api/store/mongo/migrations/migration_83.go +++ /dev/null @@ -1,58 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration83 = migrate.Migration{ - Version: 83, - Description: "Set the user's 'origin' attribute to 'manual' if it does not already exist.", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 83, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "origin": bson.M{"$exists": false}, - } - - update := bson.M{ - "$set": bson.M{ - "origin": models.UserOriginLocal.String(), - }, - } - - _, err := db.Collection("users").UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 83, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "origin": bson.M{"$exists": true}, - } - - update := bson.M{ - "$unset": bson.M{ - "origin": "", - }, - } - - _, err := db.Collection("users").UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_83_test.go b/api/store/mongo/migrations/migration_83_test.go deleted file mode 100644 index 528efd2b83b..00000000000 --- a/api/store/mongo/migrations/migration_83_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration83Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 83", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[82]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(tt, query.Decode(&user)) - - v, ok := user["origin"] - require.Equal(tt, true, ok) - require.Equal(tt, v, models.UserOriginLocal.String()) - }) - } -} - -func TestMigration83Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 83", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - "origin": models.UserOriginLocal.String(), - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[82]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(t, query.Decode(&user)) - - _, ok := user["origin"] - require.Equal(t, false, ok) - }) - } -} diff --git a/api/store/mongo/migrations/migration_84.go b/api/store/mongo/migrations/migration_84.go deleted file mode 100644 index b9d11306663..00000000000 --- a/api/store/mongo/migrations/migration_84.go +++ /dev/null @@ -1,48 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration84 = migrate.Migration{ - Version: 84, - Description: "create index for sessions' type", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 84, - "action": "Up", - }).Info("Applying migration up") - name := "events.types" - if _, err := db.Collection("sessions").Indexes().CreateOne(context.Background(), mongo.IndexModel{ - Keys: bson.M{ - "events.types": 1, - }, - Options: &options.IndexOptions{ //nolint:exhaustruct - Name: &name, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 84, - "action": "Down", - }).Info("Applying migration down") - if _, err := db.Collection("sessions").Indexes().DropOne(context.Background(), "events.types"); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_84_test.go b/api/store/mongo/migrations/migration_84_test.go deleted file mode 100644 index 76e84b5fb32..00000000000 --- a/api/store/mongo/migrations/migration_84_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration84(t *testing.T) { - cases := []struct { - description string - test func() error - }{ - { - "Success to apply up on migration 84", - func() error { - migrations := GenerateMigrations()[83:84] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("sessions").Indexes().List(context.Background()) - if err != nil { - return err - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == "events.types" { - found = true - } - } - - if !found { - return errors.New("index not created") - } - - return nil - }, - }, - { - "Success to apply down on migration 84", - func() error { - migrations := GenerateMigrations()[83:84] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("sessions").Indexes().List(context.Background()) - if err != nil { - return errors.New("index not dropped") - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == "events.types" { - found = true - } - } - - if found { - return errors.New("index not dropped") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := tc.test() - assert.NoError(t, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_85.go b/api/store/mongo/migrations/migration_85.go deleted file mode 100644 index c3a7609e283..00000000000 --- a/api/store/mongo/migrations/migration_85.go +++ /dev/null @@ -1,48 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var migration85 = migrate.Migration{ - Version: 85, - Description: "create index for tunnels address", - Up: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 85, - "action": "Up", - }).Info("Applying migration up") - name := "address" - if _, err := db.Collection("tunnels").Indexes().CreateOne(context.Background(), mongo.IndexModel{ - Keys: bson.M{ - "address": 1, - }, - Options: &options.IndexOptions{ //nolint:exhaustruct - Name: &name, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 85, - "action": "Down", - }).Info("Applying migration down") - if _, err := db.Collection("tunnels").Indexes().DropOne(context.Background(), "address"); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_85_test.go b/api/store/mongo/migrations/migration_85_test.go deleted file mode 100644 index b9e3790d326..00000000000 --- a/api/store/mongo/migrations/migration_85_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration85(t *testing.T) { - cases := []struct { - description string - test func() error - }{ - { - "Success to apply up on migration 85", - func() error { - migrations := GenerateMigrations()[84:85] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("tunnels").Indexes().List(context.Background()) - if err != nil { - return err - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == "address" { - found = true - } - } - - if !found { - return errors.New("index not created") - } - - return nil - }, - }, - { - "Success to apply down on migration 85", - func() error { - migrations := GenerateMigrations()[83:85] - migrates := migrate.NewMigrate(c.Database("test"), migrations...) - err := migrates.Down(context.Background(), migrate.AllAvailable) - if err != nil { - return err - } - - cursor, err := c.Database("test").Collection("tunnels").Indexes().List(context.Background()) - if err != nil { - return errors.New("index not dropped") - } - - var found bool - for cursor.Next(context.Background()) { - var index bson.M - if err := cursor.Decode(&index); err != nil { - return err - } - - if index["name"] == "address" { - found = true - } - } - - if found { - return errors.New("index not dropped") - } - - return nil - }, - }, - } - - for _, test := range cases { - tc := test - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := tc.test() - assert.NoError(t, err) - }) - } -} diff --git a/api/store/mongo/migrations/migration_86.go b/api/store/mongo/migrations/migration_86.go deleted file mode 100644 index 5eb3764215b..00000000000 --- a/api/store/mongo/migrations/migration_86.go +++ /dev/null @@ -1,62 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration86 = migrate.Migration{ - Version: 86, - Description: "Adding an 'auth_methods' attributes to user collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 86, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "preferences.auth_methods": bson.M{"$exists": false}, - } - - update := bson.M{ - "$set": bson.M{ - "preferences.auth_methods": []string{models.UserAuthMethodLocal.String()}, - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 86, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "preferences.auth_methods": bson.M{"$exists": true}, - } - - update := bson.M{ - "$unset": bson.M{ - "preferences.auth_methods": "", - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_86_test.go b/api/store/mongo/migrations/migration_86_test.go deleted file mode 100644 index b332186d607..00000000000 --- a/api/store/mongo/migrations/migration_86_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration86Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 86", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - "preferences": map[string]string{}, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[85]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(tt, query.Decode(&user)) - - v, ok := user["preferences"].(map[string]interface{})["auth_methods"] - require.Equal(tt, true, ok) - require.Equal(tt, primitive.A{"local"}, v) - }) - } -} - -func TestMigration86Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply down on migration 86", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - "preferences": map[string]interface{}{ - "auth_methods": []string{"some_method"}, - }, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[85]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(t, query.Decode(&user)) - - _, ok := user["preferences"].(map[string]interface{})["auth_methods"] - require.Equal(t, false, ok) - }) - } -} diff --git a/api/store/mongo/migrations/migration_87.go b/api/store/mongo/migrations/migration_87.go deleted file mode 100644 index e499b8207ee..00000000000 --- a/api/store/mongo/migrations/migration_87.go +++ /dev/null @@ -1,71 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration87 = migrate.Migration{ - Version: 87, - Description: "Adding an 'authentication' attributes to system collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 87, - "action": "Up", - }).Info("Applying migration") - - if count, _ := db.Collection("system").CountDocuments(ctx, bson.M{}); count == 0 { - if _, err := db.Collection("system").InsertOne(ctx, bson.M{"setup": true}); err != nil { - return err - } - } - - filter := bson.M{ - "authentication": bson.M{"$exists": false}, - } - - update := bson.M{ - "$set": bson.M{ - "authentication": bson.M{ - "local": bson.M{ - "enabled": true, - }, - }, - }, - } - - _, err := db. - Collection("system"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 87, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "authentication": bson.M{"$exists": true}, - } - - update := bson.M{ - "$unset": bson.M{ - "authentication": "", - }, - } - - _, err := db. - Collection("system"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_87_test.go b/api/store/mongo/migrations/migration_87_test.go deleted file mode 100644 index baa31524081..00000000000 --- a/api/store/mongo/migrations/migration_87_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration87Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - mock.On("Get", "SHELLHUB_CLOUD").Return("false") - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false") - - tests := []struct { - description string - setup func() error - }{ - { - description: "Apply up on migration 87", - setup: func() error { - _, err := c. - Database("test"). - Collection("system"). - InsertOne(ctx, map[string]interface{}{}) - - return err - }, - }, - } - - for _, tc := range tests { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[86]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("system"). - FindOne(context.TODO(), bson.M{}) - - system := make(map[string]interface{}) - require.NoError(tt, query.Decode(&system)) - - authentication, ok := system["authentication"].(map[string]interface{}) - require.Equal(tt, true, ok) - require.Equal(tt, map[string]interface{}{"local": map[string]interface{}{"enabled": true}}, authentication) - }) - } -} - -func TestMigration87Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - mock.On("Get", "SHELLHUB_CLOUD").Return("false") - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false") - - tests := []struct { - description string - setup func() error - }{ - { - description: "Apply up on migration 87", - setup: func() error { - _, err := c. - Database("test"). - Collection("system"). - InsertOne(ctx, map[string]interface{}{ - "authentication": "some_value", - }) - - return err - }, - }, - } - - for _, tc := range tests { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[86]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(tt, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("system"). - FindOne(context.TODO(), bson.M{}) - - system := make(map[string]interface{}) - require.NoError(tt, query.Decode(&system)) - - _, ok := system["authentication"] - require.Equal(tt, false, ok) - }) - } -} diff --git a/api/store/mongo/migrations/migration_88.go b/api/store/mongo/migrations/migration_88.go deleted file mode 100644 index 055cf181098..00000000000 --- a/api/store/mongo/migrations/migration_88.go +++ /dev/null @@ -1,73 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration88 = migrate.Migration{ - Version: 88, - Description: "Adding an 'authentication.saml' attributes to system collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 88, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "authentication.saml": bson.M{"$exists": false}, - } - - update := bson.M{ - "$set": bson.M{ - "authentication.saml": bson.M{ - "enabled": false, - "idp": bson.M{ - "entity_id": "", - "signon_url": "", - "certificates": []string{}, - }, - "sp": bson.M{ - "sign_auth_requests": false, - "certificate": "", - "private_key": "", - }, - }, - }, - } - - _, err := db. - Collection("system"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 88, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "authentication.saml": bson.M{"$exists": true}, - } - - update := bson.M{ - "$unset": bson.M{ - "authentication.saml": "", - }, - } - - _, err := db. - Collection("system"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_88_test.go b/api/store/mongo/migrations/migration_88_test.go deleted file mode 100644 index 76ac5bd5e27..00000000000 --- a/api/store/mongo/migrations/migration_88_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration88Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - tests := []struct { - description string - setup func() error - }{ - { - description: "Apply up on migration 88", - setup: func() error { - _, err := c. - Database("test"). - Collection("system"). - InsertOne(ctx, map[string]interface{}{ - "authentication": map[string]interface{}{ - "local": map[string]interface{}{ - "enabled": true, - }, - }, - }) - - return err - }, - }, - } - - for _, tc := range tests { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[87]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("system"). - FindOne(context.TODO(), bson.M{}) - - system := make(map[string]interface{}) - require.NoError(tt, query.Decode(&system)) - - saml, ok := system["authentication"].(map[string]interface{})["saml"].(map[string]interface{}) - require.Equal(tt, true, ok) - - enabled, ok := saml["enabled"] - require.Equal(tt, true, ok) - require.Equal(tt, false, enabled) - - idp, ok := saml["idp"].(map[string]interface{}) - require.Equal(tt, true, ok) - require.Equal(tt, map[string]interface{}{"entity_id": "", "signon_url": "", "certificates": primitive.A{}}, idp) - - sp, ok := saml["sp"].(map[string]interface{}) - require.Equal(tt, true, ok) - require.Equal(tt, map[string]interface{}{"sign_auth_requests": false, "certificate": "", "private_key": ""}, sp) - }) - } -} - -func TestMigration88Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - mock.On("Get", "SHELLHUB_CLOUD").Return("false") - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false") - - tests := []struct { - description string - setup func() error - }{ - { - description: "Apply up on migration 88", - setup: func() error { - _, err := c. - Database("test"). - Collection("system"). - InsertOne(ctx, map[string]interface{}{ - "authentication": map[string]interface{}{ - "local": true, - }, - }) - - return err - }, - }, - } - - for _, tc := range tests { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[87]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(tt, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("system"). - FindOne(context.TODO(), bson.M{}) - - system := make(map[string]interface{}) - require.NoError(tt, query.Decode(&system)) - - _, ok := system["authentication"].(map[string]interface{})["saml"] - require.Equal(tt, false, ok) - }) - } -} diff --git a/api/store/mongo/migrations/migration_89.go b/api/store/mongo/migrations/migration_89.go deleted file mode 100644 index 93ec772a153..00000000000 --- a/api/store/mongo/migrations/migration_89.go +++ /dev/null @@ -1,61 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration89 = migrate.Migration{ - Version: 89, - Description: "Adding an external ID attribute to users collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 89, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "external_id": bson.M{"$exists": false}, - } - - update := bson.M{ - "$set": bson.M{ - "external_id": "", - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 89, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{ - "external_id": bson.M{"$exists": true}, - } - - update := bson.M{ - "$unset": bson.M{ - "external_id": "", - }, - } - - _, err := db. - Collection("users"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_89_test.go b/api/store/mongo/migrations/migration_89_test.go deleted file mode 100644 index 5ccc5ca0fd3..00000000000 --- a/api/store/mongo/migrations/migration_89_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration89Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 89", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[88]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(tt, query.Decode(&user)) - - _, ok := user["external_id"] - require.Equal(tt, true, ok) - }) - } -} - -func TestMigration89Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - test func() error - }{ - { - description: "Success to apply up on migration 89", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, map[string]interface{}{ - "name": "john doe", - "external_id": "unique_string", - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - assert.NoError(t, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[88]) - require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("users"). - FindOne(context.TODO(), bson.M{"name": "john doe"}) - - user := make(map[string]interface{}) - require.NoError(t, query.Decode(&user)) - - _, ok := user["external_id"] - require.Equal(t, false, ok) - }) - } -} diff --git a/api/store/mongo/migrations/migration_8_test.go b/api/store/mongo/migrations/migration_8_test.go deleted file mode 100644 index d35ae5a5811..00000000000 --- a/api/store/mongo/migrations/migration_8_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" -) - -func TestMigration8(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:7]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - session1 := models.Session{ - Recorded: true, - } - - session2 := models.Session{ - Recorded: true, - } - - _, err = c.Database("test").Collection("sessions").InsertOne(context.TODO(), session1) - assert.NoError(t, err) - - _, err = c.Database("test").Collection("sessions").InsertOne(context.TODO(), session2) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:8]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) -} diff --git a/api/store/mongo/migrations/migration_9.go b/api/store/mongo/migrations/migration_9.go deleted file mode 100644 index 714003fff9c..00000000000 --- a/api/store/mongo/migrations/migration_9.go +++ /dev/null @@ -1,52 +0,0 @@ -package migrations - -import ( - "context" - "strings" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration9 = migrate.Migration{ - Version: 9, - Description: "Set all devices names to lowercase in the devices collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 9, - "action": "Up", - }).Info("Applying migration") - cursor, err := db.Collection("devices").Find(ctx, bson.D{}) - if err != nil { - return err - } - defer cursor.Close(ctx) - for cursor.Next(ctx) { - device := new(models.Device) - err := cursor.Decode(&device) - if err != nil { - return err - } - - device.Name = strings.ToLower(device.Name) - if _, err = db.Collection("devices").UpdateOne(ctx, bson.M{"uid": device.UID}, bson.M{"$set": bson.M{"name": strings.ToLower(device.Name)}}); err != nil { - return err - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 9, - "action": "Down", - }).Info("Applying migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_90.go b/api/store/mongo/migrations/migration_90.go deleted file mode 100644 index a7227ad0a6b..00000000000 --- a/api/store/mongo/migrations/migration_90.go +++ /dev/null @@ -1,65 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration90 = migrate.Migration{ - Version: 90, - Description: "Add events field on sessions", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 90, - "action": "Up", - }).Info("Applying migration") - - filter := bson.M{ - "$or": []bson.M{ - {"events": bson.M{"$exists": false}}, - {"events": bson.M{"$eq": bson.M{}}}, - }, - } - - update := bson.M{ - "$set": bson.M{ - "events": bson.M{ - "types": bson.A{}, - "items": bson.A{}, - }, - }, - } - - _, err := db. - Collection("sessions"). - UpdateMany(ctx, filter, update) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 90, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{} - - update := bson.M{ - "$unset": bson.M{ - "events": "", - }, - } - - _, err := db. - Collection("sessions"). - UpdateMany(ctx, filter, update) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_90_test.go b/api/store/mongo/migrations/migration_90_test.go deleted file mode 100644 index 65fab2cbfaf..00000000000 --- a/api/store/mongo/migrations/migration_90_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration90Up(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - }{ - { - description: "Success to apply up on migration 90", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, map[string]interface{}{ - "uid": "test", - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[89]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{ - "uid": "test", - }) - - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - - require.Contains(tt, session, "events") - require.Contains(tt, session["events"], "types") - require.Contains(tt, session["events"], "items") - }) - } -} - -func TestMigration90Down(t *testing.T) { - ctx := context.Background() - - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - }{ - { - description: "Success to revert migration 90", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, models.Session{ - Events: models.SessionEvents{ - Types: []string{}, - Seats: []int{0}, - }, - }) - - return err - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - assert.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[89]) - require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) - require.NoError(tt, migrates.Down(context.Background(), migrate.AllAvailable)) - - query := c. - Database("test"). - Collection("sessions"). - FindOne(context.TODO(), bson.M{}) - - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - - require.NotContains(tt, session, "events") - }) - } -} diff --git a/api/store/mongo/migrations/migration_91.go b/api/store/mongo/migrations/migration_91.go deleted file mode 100644 index 7249d1da299..00000000000 --- a/api/store/mongo/migrations/migration_91.go +++ /dev/null @@ -1,119 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration91 = migrate.Migration{ - Version: 91, - Description: "Add sessions_events collections", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 91, - "action": "Up", - }).Info("Applying migration") - - if err := db.CreateCollection(ctx, "sessions_events"); err != nil { - return err - } - - sessionIndex := mongo.IndexModel{ - Keys: bson.M{ - "session": 1, - }, - } - - if _, err := db.Collection("sessions_events").Indexes().CreateOne(ctx, sessionIndex); err != nil { - return err - } - - typeIndex := mongo.IndexModel{ - Keys: bson.M{ - "type": 1, - }, - } - - if _, err := db.Collection("sessions_events").Indexes().CreateOne(ctx, typeIndex); err != nil { - return err - } - - cursor, err := db.Collection("sessions").Find(ctx, bson.M{"events.items": bson.M{"$exists": true}}) - if err != nil { - return err - } - - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - var session struct { - UID string `bson:"uid"` - Events struct { - Items []models.SessionEvent `bson:"items"` - } `bson:"events"` - } - - if err := cursor.Decode(&session); err != nil { - return err - } - - for _, event := range session.Events.Items { - event.Session = session.UID - if _, err := db.Collection("sessions_events").InsertOne(ctx, event); err != nil { - return err - } - } - - if _, err := db.Collection("sessions").UpdateOne(ctx, bson.M{ - "uid": session.UID, - }, bson.M{ - "$unset": bson.M{ - "events.items": "", - }, - }); err != nil { - return err - } - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 91, - "action": "Down", - }).Info("Reverting migration") - cursor, err := db.Collection("sessions_events").Find(ctx, bson.M{}) - if err != nil { - return err - } - - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - var event models.SessionEvent - if err := cursor.Decode(&event); err != nil { - return err - } - sessionID := event.Session - - event.Session = "" - update := bson.M{"$push": bson.M{"events.items": event}} - if _, err := db.Collection("sessions").UpdateOne(ctx, bson.M{"uid": sessionID}, update); err != nil { - return err - } - } - - if err := db.Collection("sessions_events").Drop(ctx); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_91_test.go b/api/store/mongo/migrations/migration_91_test.go deleted file mode 100644 index 1a42b93cebe..00000000000 --- a/api/store/mongo/migrations/migration_91_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration91Up(t *testing.T) { - ctx := context.Background() - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "Session with single event", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "test", - "events": bson.M{ - "types": bson.A{"test"}, - "items": []bson.M{ - {"type": "test", "data": "some data"}, - }, - }, - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("sessions_events"). - FindOne(ctx, bson.M{"session": "test"}) - sessionEvent := make(map[string]interface{}) - require.NoError(tt, query.Decode(&sessionEvent)) - require.Contains(tt, sessionEvent, "type") - require.Contains(tt, sessionEvent, "data") - - query = c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "test"}) - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - require.NotContains(tt, session, "events.items") - }, - }, - { - description: "Session with empty events.items array", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "empty-items", - "events": bson.M{ - "types": bson.A{"test"}, - "items": []bson.M{}, - }, - }) - - return err - }, - verify: func(tt *testing.T) { - count, err := c. - Database("test"). - Collection("sessions_events"). - CountDocuments(ctx, bson.M{"session": "empty-items"}) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "empty-items"}) - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - require.NotContains(tt, session, "events.items") - }, - }, - { - description: "Session with multiple events", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "multi-event", - "events": bson.M{ - "types": bson.A{"test"}, - "items": []bson.M{ - {"type": "event1", "data": "data1"}, - {"type": "event2", "data": "data2"}, - }, - }, - }) - - return err - }, - verify: func(tt *testing.T) { - cursor, err := c. - Database("test"). - Collection("sessions_events"). - Find(ctx, bson.M{"session": "multi-event"}) - require.NoError(tt, err) - var events []bson.M - require.NoError(tt, cursor.All(ctx, &events)) - assert.Equal(tt, 2, len(events)) - for _, event := range events { - assert.Equal(tt, "multi-event", event["session"]) - } - - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "multi-event"}) - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - require.NotContains(tt, session, "events.items") - }, - }, - { - description: "Session with no events field", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "no-events", - }) - - return err - }, - verify: func(tt *testing.T) { - count, err := c. - Database("test"). - Collection("sessions_events"). - CountDocuments(ctx, bson.M{"session": "no-events"}) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "no-events"}) - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - _, exists := session["events"] - assert.False(tt, exists) - }, - }, - { - description: "Verify indexes created", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "index-test", - "events": bson.M{ - "types": bson.A{"test"}, - "items": []bson.M{ - {"type": "test", "data": "index data"}, - }, - }, - }) - - return err - }, - verify: func(tt *testing.T) { - indexCursor, err := c. - Database("test"). - Collection("sessions_events"). - Indexes().List(ctx) - require.NoError(tt, err) - var indexes []bson.M - require.NoError(tt, indexCursor.All(ctx, &indexes)) - var sessionIndexFound, typeIndexFound bool - for _, index := range indexes { - if key, ok := index["key"].(bson.M); ok { - if _, ok := key["session"]; ok { - sessionIndexFound = true - } - if _, ok := key["type"]; ok { - typeIndexFound = true - } - } - } - assert.True(tt, sessionIndexFound) - assert.True(tt, typeIndexFound) - }, - }, - { - description: "Multiple sessions processed", - setup: func() error { - docs := []interface{}{ - bson.M{ - "uid": "session1", - "events": bson.M{ - "types": bson.A{"test"}, - "items": []bson.M{ - {"type": "event1", "data": "data1"}, - }, - }, - }, - bson.M{ - "uid": "session2", - "events": bson.M{ - "types": bson.A{"test"}, - "items": []bson.M{ - {"type": "event2", "data": "data2"}, - {"type": "event3", "data": "data3"}, - }, - }, - }, - } - _, err := c. - Database("test"). - Collection("sessions"). - InsertMany(ctx, docs) - - return err - }, - verify: func(tt *testing.T) { - count1, err := c. - Database("test"). - Collection("sessions_events"). - CountDocuments(ctx, bson.M{"session": "session1"}) - require.NoError(tt, err) - assert.Equal(tt, int64(1), count1) - - count2, err := c. - Database("test"). - Collection("sessions_events"). - CountDocuments(ctx, bson.M{"session": "session2"}) - require.NoError(tt, err) - assert.Equal(tt, int64(2), count2) - - for _, uid := range []string{"session1", "session2"} { - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": uid}) - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - require.NotContains(tt, session, "events.items") - } - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - require.NoError(tt, tc.setup()) - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[90]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - tc.verify(tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_92.go b/api/store/mongo/migrations/migration_92.go deleted file mode 100644 index c4bd4f91ed9..00000000000 --- a/api/store/mongo/migrations/migration_92.go +++ /dev/null @@ -1,89 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration92 = migrate.Migration{ - Version: 92, - Description: "Adding seat and seats to sessions and sessions events", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 92, - "action": "Up", - }).Info("Applying migration") - - if _, err := db. - Collection("sessions"). - UpdateMany(ctx, bson.M{ - "authenticated": true, - }, bson.M{ - "$set": bson.M{ - "events.seats": bson.A{0}, - }, - }); err != nil { - return err - } - - if _, err := db. - Collection("sessions"). - UpdateMany(ctx, bson.M{ - "authenticated": false, - }, bson.M{ - "$set": bson.M{ - "events.seats": bson.A{}, - }, - }); err != nil { - return err - } - - if _, err := db. - Collection("sessions_events"). - UpdateMany(ctx, bson.M{}, bson.M{ - "$set": bson.M{ - "seat": 0, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 92, - "action": "Down", - }).Info("Reverting migration") - - filter := bson.M{} - - if _, err := db. - Collection("sessions"). - UpdateMany(ctx, filter, bson.M{ - "$unset": bson.M{ - "events.seats": "", - }, - }); err != nil { - return err - } - - if _, err := db. - Collection("sessions_events"). - UpdateMany(ctx, filter, bson.M{ - "$unset": bson.M{ - "seat": "", - }, - }); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_92_test.go b/api/store/mongo/migrations/migration_92_test.go deleted file mode 100644 index 3ecd5f403b0..00000000000 --- a/api/store/mongo/migrations/migration_92_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration92Up(t *testing.T) { - ctx := context.Background() - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "Authenticated session should get events.seats set to [int32(0)]", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "auth-test", - "authenticated": true, - "events": bson.M{ - "types": bson.A{"test"}, - }, - }) - if err != nil { - return err - } - _, err = c. - Database("test"). - Collection("sessions_events"). - InsertOne(ctx, bson.M{ - "session": "auth-test", - "type": "test", - "data": "some data", - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "auth-test"}) - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - events, ok := session["events"].(map[string]interface{}) - require.True(tt, ok) - seats, exists := events["seats"] - require.True(tt, exists) - assert.Equal(tt, bson.A{int32(0)}, seats) - - query = c. - Database("test"). - Collection("sessions_events"). - FindOne(ctx, bson.M{"session": "auth-test"}) - sessionEvent := make(map[string]interface{}) - require.NoError(tt, query.Decode(&sessionEvent)) - seat, exists := sessionEvent["seat"] - require.True(tt, exists) - assert.Equal(tt, int32(0), seat) - }, - }, - { - description: "Unauthenticated session should get events.seats set to []", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "unauth-test", - "authenticated": false, - "events": bson.M{ - "types": bson.A{"test"}, - }, - }) - if err != nil { - return err - } - _, err = c. - Database("test"). - Collection("sessions_events"). - InsertOne(ctx, bson.M{ - "session": "unauth-test", - "type": "test", - "data": "some data", - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "unauth-test"}) - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - events, ok := session["events"].(map[string]interface{}) - require.True(tt, ok) - seats, exists := events["seats"] - require.True(tt, exists) - assert.Equal(tt, bson.A{}, seats) - - query = c. - Database("test"). - Collection("sessions_events"). - FindOne(ctx, bson.M{"session": "unauth-test"}) - sessionEvent := make(map[string]interface{}) - require.NoError(tt, query.Decode(&sessionEvent)) - seat, exists := sessionEvent["seat"] - require.True(tt, exists) - assert.Equal(tt, int32(0), seat) - }, - }, - { - description: "Session without events field should be updated if authenticated", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "no-events-test", - "authenticated": true, - }) - if err != nil { - return err - } - _, err = c. - Database("test"). - Collection("sessions_events"). - InsertOne(ctx, bson.M{ - "session": "no-events-test", - "type": "test", - "data": "some data", - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "no-events-test"}) - session := make(map[string]interface{}) - require.NoError(tt, query.Decode(&session)) - events, ok := session["events"].(map[string]interface{}) - require.True(tt, ok) - seats, exists := events["seats"] - require.True(tt, exists) - assert.Equal(tt, bson.A{int32(0)}, seats) - - query = c. - Database("test"). - Collection("sessions_events"). - FindOne(ctx, bson.M{"session": "no-events-test"}) - sessionEvent := make(map[string]interface{}) - require.NoError(tt, query.Decode(&sessionEvent)) - seat, exists := sessionEvent["seat"] - require.True(tt, exists) - assert.Equal(tt, int32(0), seat) - }, - }, - { - description: "Multiple sessions documents update correctly", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertMany(ctx, []interface{}{ - bson.M{ - "uid": "multi-test-auth", - "authenticated": true, - "events": bson.M{"types": bson.A{"test"}}, - }, - bson.M{ - "uid": "multi-test-unauth", - "authenticated": false, - "events": bson.M{"types": bson.A{"test"}}, - }, - }) - if err != nil { - return err - } - _, err = c. - Database("test"). - Collection("sessions_events"). - InsertMany(ctx, []interface{}{ - bson.M{ - "session": "multi-test-auth", - "type": "test", - "data": "data1", - }, - bson.M{ - "session": "multi-test-unauth", - "type": "test", - "data": "data2", - }, - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "multi-test-auth"}) - sessionAuth := make(map[string]interface{}) - require.NoError(tt, query.Decode(&sessionAuth)) - eventsAuth, ok := sessionAuth["events"].(map[string]interface{}) - require.True(tt, ok) - seatsAuth, exists := eventsAuth["seats"] - require.True(tt, exists) - assert.Equal(tt, bson.A{int32(0)}, seatsAuth) - - query = c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "multi-test-unauth"}) - sessionUnauth := make(map[string]interface{}) - require.NoError(tt, query.Decode(&sessionUnauth)) - eventsUnauth, ok := sessionUnauth["events"].(map[string]interface{}) - require.True(tt, ok) - seatsUnauth, exists := eventsUnauth["seats"] - require.True(tt, exists) - assert.Equal(tt, bson.A{}, seatsUnauth) - - query = c. - Database("test"). - Collection("sessions_events"). - FindOne(ctx, bson.M{"session": "multi-test-auth"}) - sessionEventAuth := make(map[string]interface{}) - require.NoError(tt, query.Decode(&sessionEventAuth)) - seatAuth, exists := sessionEventAuth["seat"] - require.True(tt, exists) - assert.Equal(tt, int32(0), seatAuth) - - query = c. - Database("test"). - Collection("sessions_events"). - FindOne(ctx, bson.M{"session": "multi-test-unauth"}) - sessionEventUnauth := make(map[string]interface{}) - require.NoError(tt, query.Decode(&sessionEventUnauth)) - seatUnauth, exists := sessionEventUnauth["seat"] - require.True(tt, exists) - assert.Equal(tt, int32(0), seatUnauth) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[91]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_93.go b/api/store/mongo/migrations/migration_93.go deleted file mode 100644 index 809cbffa153..00000000000 --- a/api/store/mongo/migrations/migration_93.go +++ /dev/null @@ -1,35 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration93 = migrate.Migration{ - Version: 93, - Description: "remove public_url and public_url_address from device collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 93, - "action": "Up", - }).Info("Applying migration") - - _, err := db.Collection("devices").UpdateMany(ctx, bson.M{}, bson.M{"$unset": bson.M{"public_url": "", "public_url_address": ""}}) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 93, - "action": "Down", - }).Info("Cannot undo migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_93_test.go b/api/store/mongo/migrations/migration_93_test.go deleted file mode 100644 index 58b6e4257ac..00000000000 --- a/api/store/mongo/migrations/migration_93_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration93Up(t *testing.T) { - ctx := context.Background() - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "removes the public_url attribute from devices", - setup: func() error { - _, err := c.Database("test").Collection("devices").InsertOne(ctx, bson.M{"uid": "uid", "public_url": true}) - - return err - }, - verify: func(tt *testing.T) { - device := make(map[string]any) - require.NoError(tt, c.Database("test").Collection("devices").FindOne(ctx, bson.M{"uid": "uid"}).Decode(&device)) - - _, ok := device["public_url"] - require.Equal(tt, false, ok) - }, - }, - { - description: "removes the public_url_address attribute from devices", - setup: func() error { - _, err := c.Database("test").Collection("devices").InsertOne(ctx, bson.M{"uid": "uid", "public_url_address": "address"}) - - return err - }, - verify: func(tt *testing.T) { - device := make(map[string]any) - require.NoError(tt, c.Database("test").Collection("devices").FindOne(ctx, bson.M{"uid": "uid"}).Decode(&device)) - - _, ok := device["public_url_address"] - require.Equal(tt, false, ok) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[92]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_94.go b/api/store/mongo/migrations/migration_94.go deleted file mode 100644 index 035d0629cbd..00000000000 --- a/api/store/mongo/migrations/migration_94.go +++ /dev/null @@ -1,89 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration94 = migrate.Migration{ - Version: 94, - Description: "Adding 'disconnected_at' attribute to 'devices'", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 94, - "action": "Up", - }).Info("Applying migration") - - session, err := db.Client().StartSession() - if err != nil { - return err - } - defer session.EndSession(ctx) - - fn := func(_ mongo.SessionContext) (any, error) { - pipeline := []bson.M{ - { - "$match": bson.M{ - "uid": bson.M{ - "$exists": true, - }, - }, - }, - { - "$lookup": bson.M{ - "from": "connected_devices", - "localField": "uid", - "foreignField": "uid", - "as": "connected_devices_data", - }, - }, - } - - cursor, err := db.Collection("devices").Aggregate(ctx, pipeline) - if err != nil { - return nil, err - } - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - device := make(map[string]any) - if err := cursor.Decode(&device); err != nil { - return nil, err - } - - update := bson.M{"$set": bson.M{"disconnected_at": device["last_seen"]}} - if connectedDevicesData, ok := device["connected_devices_data"].(bson.A); ok && len(connectedDevicesData) > 0 { - update = bson.M{"$set": bson.M{"disconnected_at": nil}} - } - - if _, err := db.Collection("devices").UpdateOne(ctx, bson.M{"_id": device["_id"]}, update); err != nil { - return nil, err - } - } - - if err := db.Collection("connected_devices").Drop(ctx); err != nil { - return nil, err - } - - return nil, nil - } - - _, err = session.WithTransaction(ctx, fn) - - return err - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 94, - "action": "Down", - }).Info("Cannot down migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_94_test.go b/api/store/mongo/migrations/migration_94_test.go deleted file mode 100644 index b81965e0439..00000000000 --- a/api/store/mongo/migrations/migration_94_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package migrations - -import ( - "context" - "slices" - "testing" - "time" - - "github.com/shellhub-io/shellhub/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestMigration94Up(t *testing.T) { - cases := []struct { - description string - setup func(ctx context.Context) error - verify func(ctx context.Context, tt *testing.T) - }{ - { - description: "drops the 'connected_devices' collection", - setup: func(ctx context.Context) error { - _, err := c.Database("test").Collection("connected_devices").InsertOne(ctx, bson.M{"uid": "auth-test"}) - - return err - }, - verify: func(ctx context.Context, tt *testing.T) { - res, err := c.Database("test").ListCollectionNames(ctx, bson.M{}) - require.NoError(tt, err) - require.Equal(tt, false, slices.Contains(res, "connected_devices")) - }, - }, - { - description: "sets the value to nil when the device have a related connected_device", - setup: func(ctx context.Context) error { - randomUIDs := []string{uuid.Generate(), uuid.Generate()} - for _, uid := range randomUIDs { - if _, err := c.Database("test").Collection("devices").InsertOne(ctx, bson.M{"uid": uid, "last_seen": time.Now()}); err != nil { - return err - } - - if _, err := c.Database("test").Collection("connected_devices").InsertOne(ctx, bson.M{"uid": uid}); err != nil { - return err - } - } - - return nil - }, - verify: func(ctx context.Context, tt *testing.T) { - cursor, err := c.Database("test").Collection("devices").Find(ctx, bson.M{}) - require.NoError(tt, err) - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - device := make(map[string]any) - require.NoError(tt, cursor.Decode(&device)) - - disconnectedAt, ok := device["disconnected_at"] - require.Equal(tt, true, ok) - require.Equal(tt, nil, disconnectedAt) - } - }, - }, - { - description: "sets the value to last_seen when the device does not have a related connected_device", - setup: func(ctx context.Context) error { - randomUIDs := []string{uuid.Generate(), uuid.Generate()} - for _, uid := range randomUIDs { - _, err := c.Database("test").Collection("devices").InsertOne(ctx, bson.M{"uid": uid, "last_seen": time.Now()}) - - return err - } - - return nil - }, - verify: func(ctx context.Context, tt *testing.T) { - cursor, err := c.Database("test").Collection("devices").Find(ctx, bson.M{}) - require.NoError(tt, err) - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - device := make(map[string]any) - require.NoError(tt, cursor.Decode(&device)) - - disconnectedAt, ok := device["disconnected_at"] - require.Equal(tt, true, ok) - require.WithinDuration(tt, device["last_seen"].(primitive.DateTime).Time(), disconnectedAt.(primitive.DateTime).Time(), 1*time.Second) - } - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - ctx := context.Background() - - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup(ctx)) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[93]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - - tc.verify(ctx, tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_95.go b/api/store/mongo/migrations/migration_95.go deleted file mode 100644 index 5d946321dd8..00000000000 --- a/api/store/mongo/migrations/migration_95.go +++ /dev/null @@ -1,468 +0,0 @@ -package migrations - -import ( - "context" - "fmt" - "runtime" - "slices" - - "github.com/shellhub-io/shellhub/pkg/envs" - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - "golang.org/x/sync/semaphore" -) - -var migration95 = migrate.Migration{ - Version: 95, - Description: "Convert recorded sessions into session's events", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 95, - "action": "Up", - }).Info("Applying migration") - - if !envs.IsEnterprise() { - return nil - } - - cursor, err := db.Collection("recorded_sessions").Aggregate(ctx, []bson.M{ - { - "$match": bson.M{}, - }, - { - "$group": bson.M{ - "_id": "$uid", - }, - }, - }) - if err != nil { - return fmt.Errorf("failed to query session UIDs: %w", err) - } - - defer cursor.Close(ctx) - - var ( - maxWorkers = runtime.GOMAXPROCS(0) - sem = semaphore.NewWeighted(int64(maxWorkers)) - ) - - for cursor.Next(ctx) { - if err := sem.Acquire(ctx, 1); err != nil { - log.Printf("Failed to acquire semaphore: %v", err) - - break - } - - var result struct { - UID string `bson:"_id"` - } - - if err := cursor.Decode(&result); err != nil { - log.WithError(err).Error("Failed to decode UID result") - sem.Release(1) - - return err - } - - go func(uid string) { - defer sem.Release(1) - - log.WithField("uid", uid).Debug("Processing session") - - logger := log.WithFields(log.Fields{ - "uid": uid, - }) - - query := []bson.M{ - { - "$match": bson.M{ - "uid": uid, - }, - }, - { - "$sort": bson.M{ - "time": 1, - }, - }, - } - - cursor, err := db.Collection("recorded_sessions").Aggregate(ctx, query) - if err != nil { - logger.WithError(err).Error("Failed to query session records") - - return - } - - defer cursor.Close(ctx) - - s := db.Collection("sessions").FindOne(ctx, bson.M{ - "uid": uid, - }) - if err != nil { - logger.WithError(err).Error("Failed to query session records") - - return - } - - if s.Err() != nil { - if _, err := db.Collection("recorded_sessions").DeleteMany(ctx, bson.M{ - "uid": uid, - }); err != nil { - logger.WithError(err).Error("failed to delete the recorded session when session isn't found") - - return - } - - return - } - - session := &models.Session{} - if err := s.Decode(session); err != nil { - logger.WithError(err).Error("failed to decode the session") - - return - } - - record := &models.RecordedSession{} - - if cursor.Next(ctx) { - if err := cursor.Decode(record); err != nil { - logger.WithError(err).Error("Failed to decode session record") - - return - } - } - - if !slices.Contains(session.Events.Types, string(models.SessionEventTypePtyRequest)) { - if _, err := db.Collection("sessions").UpdateOne(ctx, - bson.M{"uid": uid}, - bson.M{ - "$addToSet": bson.M{ - "events.types": models.SessionEventTypePtyRequest, - "events.seats": 0, - }, - }, - ); err != nil { - logger.WithError(err).Error("Failed to update session events types to pty-req") - - return - } - - if _, err := db.Collection("sessions_events").InsertOne(ctx, &models.SessionEvent{ - Session: uid, - Type: models.SessionEventTypePtyRequest, - Timestamp: record.Time, - Data: &models.SSHPty{ - Term: "", - Columns: uint32(record.Width), - Rows: uint32(record.Height), - Width: 0, - Height: 0, - Modelist: []byte{}, - }, - Seat: 0, - }); err != nil { - logger.WithError(err).Error("Failed to insert session event pty-req") - - return - } - } - - lastWidth, lastHeight := record.Width, record.Height - - if _, err := db.Collection("sessions").UpdateOne(ctx, - bson.M{"uid": uid}, - bson.M{ - "$addToSet": bson.M{ - "events.types": models.SessionEventTypePtyOutput, - "events.seats": 0, - }, - }, - ); err != nil { - logger.WithError(err).Error("Failed to update session events types to pty-output") - - return - } - - if _, err := db.Collection("sessions_events").InsertOne(ctx, &models.SessionEvent{ - Session: uid, - Type: models.SessionEventTypePtyOutput, - Timestamp: record.Time, - Data: &models.SSHPtyOutput{ - Output: record.Message, - }, - Seat: 0, - }); err != nil { - logger.WithError(err).Error("Failed to insert session event pty-output") - - return - } - - for cursor.Next(ctx) { - if err := cursor.Decode(record); err != nil { - logger.WithError(err).Error("Failed to decode session record") - - return - } - - if record.Width != lastWidth || record.Height != lastHeight { - if !slices.Contains(session.Events.Types, string(models.SessionEventTypeWindowChange)) { - if _, err := db.Collection("sessions").UpdateOne(ctx, - bson.M{"uid": uid}, - bson.M{ - "$addToSet": bson.M{ - "events.types": models.SessionEventTypeWindowChange, - "events.seats": 0, - }, - }, - ); err != nil { - logger.WithError(err).Error("Failed to update session events types to window-change") - - return - } - - if _, err := db.Collection("sessions_events").InsertOne(ctx, &models.SessionEvent{ - Session: uid, - Type: models.SessionEventTypeWindowChange, - Timestamp: record.Time, - Data: &models.SSHWindowChange{ - Columns: uint32(record.Width), - Rows: uint32(record.Height), - Width: 0, - Height: 0, - }, - Seat: 0, - }); err != nil { - logger.WithError(err).Error("Failed to insert session event window-change") - - return - } - } - - lastWidth, lastHeight = record.Width, record.Height - } - - if _, err := db.Collection("sessions").UpdateOne(ctx, - bson.M{"uid": uid}, - bson.M{ - "$addToSet": bson.M{ - "events.types": models.SessionEventTypePtyOutput, - "events.seats": 0, - }, - }, - ); err != nil { - logger.WithError(err).Error("Failed to update session events types to pty-output") - - return - } - - if _, err := db.Collection("sessions_events").InsertOne(ctx, &models.SessionEvent{ - Session: uid, - Type: models.SessionEventTypePtyOutput, - Timestamp: record.Time, - Data: &models.SSHPtyOutput{ - Output: record.Message, - }, - Seat: 0, - }); err != nil { - logger.WithError(err).Error("Failed to insert session event pty-output") - - return - } - - } - - if _, err := db.Collection("recorded_sessions").DeleteMany(ctx, bson.M{ - "uid": uid, - }); err != nil { - logger.WithError(err).Error("failed to delete the recorded session") - - return - } - - logger.Debug("Successfully processed session") - }(result.UID) - } - - if err := sem.Acquire(ctx, int64(maxWorkers)); err != nil { - log.Printf("Failed to acquire semaphore: %v", err) - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 95, - "action": "Down", - }).Info("Reverting migration") - - if !envs.IsEnterprise() { - return nil - } - - cursor, err := db.Collection("sessions").Find(ctx, bson.M{ - "events.types": bson.M{ - "$in": []models.SessionEventType{ - models.SessionEventTypePtyRequest, - models.SessionEventTypePtyOutput, - models.SessionEventTypeWindowChange, - }, - }, - }) - if err != nil { - return fmt.Errorf("failed to query sessions: %w", err) - } - - defer cursor.Close(ctx) - - var ( - maxWorkers = runtime.GOMAXPROCS(0) - sem = semaphore.NewWeighted(int64(maxWorkers)) - ) - - for cursor.Next(ctx) { - if err := sem.Acquire(ctx, 1); err != nil { - log.Printf("Failed to acquire semaphore: %v", err) - - break - } - - go func() { - defer sem.Release(1) - - var session struct { - UID string `bson:"uid"` - } - - if err := cursor.Decode(&session); err != nil { - log.WithError(err).Error("Failed to decode session") - - return - } - - uid := session.UID - log.WithField("uid", uid).Debug("Reverting session") - - eventsCursor, err := db.Collection("sessions_events").Find(ctx, bson.M{ - "session": uid, - "type": bson.M{ - "$in": []models.SessionEventType{ - models.SessionEventTypePtyRequest, - models.SessionEventTypePtyOutput, - models.SessionEventTypeWindowChange, - }, - }, - }, options.Find().SetSort(bson.D{{Key: "timestamp", Value: 1}})) - if err != nil { - log.WithError(err).WithField("uid", uid).Error("Failed to query session events") - - return - } - - defer eventsCursor.Close(ctx) - - var lastWidth, lastHeight uint32 - for eventsCursor.Next(ctx) { - var event models.SessionEvent - if err := eventsCursor.Decode(&event); err != nil { - log.WithError(err).WithField("uid", uid).Error("Failed to decode event") - - continue - } - - switch event.Type { - case models.SessionEventTypePtyRequest: - d := &models.SSHPty{} - - data, _ := bson.Marshal(event.Data.(primitive.D)) - if err := bson.Unmarshal(data, &d); err != nil { - return - } - - ptyReq := d - - lastWidth, lastHeight = ptyReq.Columns, ptyReq.Rows - case models.SessionEventTypeWindowChange: - d := &models.SSHWindowChange{} - - data, _ := bson.Marshal(event.Data.(primitive.D)) - if err := bson.Unmarshal(data, &d); err != nil { - return - } - - winChange := d - - lastWidth, lastHeight = winChange.Columns, winChange.Rows - case models.SessionEventTypePtyOutput: - d := &models.SSHPtyOutput{} - - data, _ := bson.Marshal(event.Data.(primitive.D)) - if err := bson.Unmarshal(data, &d); err != nil { - return - } - - ptyOutput := d - - _, err := db.Collection("recorded_sessions").InsertOne(ctx, bson.M{ - "uid": uid, - "message": ptyOutput.Output, - "time": event.Timestamp, - "width": lastWidth, - "height": lastHeight, - }) - if err != nil { - log.WithError(err).WithField("uid", uid).Error("Failed to insert recorded session") - } - } - } - - _, err = db.Collection("sessions_events").DeleteMany(ctx, bson.M{ - "session": uid, - "type": bson.M{ - "$in": []models.SessionEventType{ - models.SessionEventTypePtyRequest, - models.SessionEventTypePtyOutput, - models.SessionEventTypeWindowChange, - }, - }, - }) - if err != nil { - log.WithError(err).WithField("uid", uid).Error("Failed to delete session events") - } - - _, err = db.Collection("sessions").UpdateOne(ctx, - bson.M{"uid": uid}, - bson.M{ - "$pull": bson.M{ - "events.types": bson.M{ - "$in": []models.SessionEventType{ - models.SessionEventTypePtyRequest, - models.SessionEventTypePtyOutput, - models.SessionEventTypeWindowChange, - }, - }, - }, - }, - ) - if err != nil { - log.WithError(err).WithField("uid", uid).Error("Failed to update session") - } - - log.WithField("uid", uid).Debug("Successfully reverted session") - }() - } - - if err := sem.Acquire(ctx, int64(maxWorkers)); err != nil { - log.WithError(err).Printf("Failed to acquire semaphore") - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_95_test.go b/api/store/mongo/migrations/migration_95_test.go deleted file mode 100644 index b4663d3ea91..00000000000 --- a/api/store/mongo/migrations/migration_95_test.go +++ /dev/null @@ -1,435 +0,0 @@ -package migrations - -import ( - "context" - "fmt" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration95Up(t *testing.T) { - ctx := context.Background() - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("true").Once() - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "Single recorded session should be converted to events", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-1", - "authenticated": true, - "events": bson.M{ - "types": bson.A{}, - "seats": bson.A{}, - }, - }) - if err != nil { - return err - } - - _, err = c. - Database("test"). - Collection("recorded_sessions"). - InsertMany(ctx, []any{ - bson.M{ - "uid": "session-1", - "message": "initial output", - "time": "2023-01-01T10:00:00Z", - "width": 80, - "height": 24, - }, - bson.M{ - "uid": "session-1", - "message": "resized terminal output", - "time": "2023-01-01T10:01:00Z", - "width": 100, - "height": 30, - }, - bson.M{ - "uid": "session-1", - "message": "final output", - "time": "2023-01-01T10:02:00Z", - "width": 100, - "height": 30, - }, - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "session-1"}) - - session := make(map[string]any) - require.NoError(tt, query.Decode(&session)) - - events, ok := session["events"].(map[string]any) - require.True(tt, ok) - - types, ok := events["types"].(bson.A) - require.True(tt, ok) - assert.Contains(tt, types, "pty-req") - assert.Contains(tt, types, "window-change") - assert.Contains(tt, types, "pty-output") - - seats, ok := events["seats"].(bson.A) - require.True(tt, ok) - assert.Contains(tt, seats, int32(0)) - - cursor, err := c. - Database("test"). - Collection("sessions_events"). - Find(ctx, bson.M{"session": "session-1"}) - require.NoError(tt, err) - defer cursor.Close(ctx) - - var sessionEvents []map[string]any - require.NoError(tt, cursor.All(ctx, &sessionEvents)) - - assert.Equal(tt, 5, len(sessionEvents)) - - eventTypeCounts := make(map[string]int) - for _, event := range sessionEvents { - eventType := event["type"].(string) - eventTypeCounts[eventType]++ - assert.Equal(tt, int32(0), event["seat"]) - } - - assert.Equal(tt, 1, eventTypeCounts["pty-req"]) - assert.Equal(tt, 1, eventTypeCounts["window-change"]) - assert.Equal(tt, 3, eventTypeCounts["pty-output"]) - - count, err := c. - Database("test"). - Collection("recorded_sessions"). - CountDocuments(ctx, bson.M{"uid": "session-1"}) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - }, - }, - { - description: "When not in enterprise mode, nothing should be migrated", - setup: func() error { - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Once() - - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-4", - "authenticated": true, - "events": bson.M{ - "types": bson.A{}, - "seats": bson.A{}, - }, - }) - if err != nil { - return err - } - - _, err = c. - Database("test"). - Collection("recorded_sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-4", - "message": "test output", - "time": "2023-01-01T10:00:00Z", - "width": 80, - "height": 24, - }) - - return err - }, - verify: func(tt *testing.T) { - count, err := c. - Database("test"). - Collection("recorded_sessions"). - CountDocuments(ctx, bson.M{"uid": "session-4"}) - require.NoError(tt, err) - assert.Equal(tt, int64(1), count) - - count, err = c. - Database("test"). - Collection("sessions_events"). - CountDocuments(ctx, bson.M{"session": "session-4"}) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[94]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} - -func TestMigration95Down(t *testing.T) { - ctx := context.Background() - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("true").Twice() - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "Success to revert migration 95 in cloud mode", - setup: func() error { - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-1", - "authenticated": true, - "events": bson.M{ - "types": bson.A{"pty-req", "window-change", "pty-output"}, - "seats": bson.A{int32(0)}, - }, - }) - if err != nil { - return err - } - - timestamp1 := "2023-01-01T10:00:00Z" - timestamp2 := "2023-01-01T10:01:00Z" - timestamp3 := "2023-01-01T10:02:00Z" - - _, err = c. - Database("test"). - Collection("sessions_events"). - InsertMany(ctx, []any{ - bson.M{ - "session": "session-1", - "type": "pty-req", - "timestamp": timestamp1, - "data": bson.M{ - "term": "", - "columns": uint32(80), - "rows": uint32(24), - "width": uint32(0), - "height": uint32(0), - "modelist": []byte{}, - }, - "seat": int32(0), - }, - bson.M{ - "session": "session-1", - "type": "window-change", - "timestamp": timestamp2, - "data": bson.M{ - "columns": uint32(100), - "rows": uint32(30), - "width": uint32(0), - "height": uint32(0), - }, - "seat": int32(0), - }, - bson.M{ - "session": "session-1", - "type": "pty-output", - "timestamp": timestamp1, - "data": bson.M{ - "output": "initial output", - }, - "seat": int32(0), - }, - bson.M{ - "session": "session-1", - "type": "pty-output", - "timestamp": timestamp2, - "data": bson.M{ - "output": "resized terminal output", - }, - "seat": int32(0), - }, - bson.M{ - "session": "session-1", - "type": "pty-output", - "timestamp": timestamp3, - "data": bson.M{ - "output": "final output", - }, - "seat": int32(0), - }, - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "session-1"}) - - session := make(map[string]any) - require.NoError(tt, query.Decode(&session)) - - events, ok := session["events"].(map[string]any) - require.True(tt, ok) - - types, ok := events["types"].(bson.A) - require.True(tt, ok) - assert.NotContains(tt, types, "pty-req") - assert.NotContains(tt, types, "window-change") - assert.NotContains(tt, types, "pty-output") - - count, err := c. - Database("test"). - Collection("sessions_events"). - CountDocuments(ctx, bson.M{ - "session": "session-1", - "type": bson.M{ - "$in": []string{"pty-req", "window-change", "pty-output"}, - }, - }) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - - cursor, err := c. - Database("test"). - Collection("recorded_sessions"). - Find(ctx, bson.M{"uid": "session-1"}) - require.NoError(tt, err) - defer cursor.Close(ctx) - - var recordedSessions []map[string]any - require.NoError(tt, cursor.All(ctx, &recordedSessions)) - - assert.Equal(tt, 3, len(recordedSessions)) - - widthHeightCount := make(map[string]int) - for _, record := range recordedSessions { - key := fmt.Sprintf("%v-%v", record["width"], record["height"]) - widthHeightCount[key]++ - assert.Contains(tt, []string{ - "initial output", - "resized terminal output", - "final output", - }, record["message"]) - } - - assert.Equal(tt, 1, widthHeightCount["80-24"]) - assert.Equal(tt, 2, widthHeightCount["100-30"]) - }, - }, - { - description: "Skip migration revert when not in enterprise mode", - setup: func() error { - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Twice() - - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-3", - "authenticated": true, - "events": bson.M{ - "types": bson.A{"pty-req", "pty-output"}, - "seats": bson.A{int32(0)}, - }, - }) - if err != nil { - return err - } - - _, err = c. - Database("test"). - Collection("sessions_events"). - InsertOne(ctx, bson.M{ - "session": "session-3", - "type": "pty-output", - "timestamp": "2023-01-01T10:00:00Z", - "data": bson.M{ - "output": "test output", - }, - "seat": int32(0), - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("sessions"). - FindOne(ctx, bson.M{"uid": "session-3"}) - - session := make(map[string]any) - require.NoError(tt, query.Decode(&session)) - - events, ok := session["events"].(map[string]any) - require.True(tt, ok) - - types, ok := events["types"].(bson.A) - require.True(tt, ok) - assert.Contains(tt, types, "pty-req") - assert.Contains(tt, types, "pty-output") - - count, err := c. - Database("test"). - Collection("sessions_events"). - CountDocuments(ctx, bson.M{ - "session": "session-3", - "type": "pty-output", - }) - require.NoError(tt, err) - assert.Equal(tt, int64(1), count) - - count, err = c. - Database("test"). - Collection("recorded_sessions"). - CountDocuments(ctx, bson.M{"uid": "session-3"}) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[94]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - - require.NoError(tt, migrates.Down(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_96.go b/api/store/mongo/migrations/migration_96.go deleted file mode 100644 index e809d9ca3f6..00000000000 --- a/api/store/mongo/migrations/migration_96.go +++ /dev/null @@ -1,49 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/envs" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration96 = migrate.Migration{ - Version: 96, - Description: "Drops the recorded_session collection", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 96, - "action": "Up", - }).Info("Applying migration") - - if !envs.IsEnterprise() { - return nil - } - - if err := db.Collection("recorded_sessions").Drop(ctx); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 96, - "action": "Down", - }).Info("Reverting migration") - - if !envs.IsEnterprise() { - return nil - } - - if err := db.CreateCollection(ctx, "recorded_sessions"); err != nil { - return err - } - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_96_test.go b/api/store/mongo/migrations/migration_96_test.go deleted file mode 100644 index 7557e9165e2..00000000000 --- a/api/store/mongo/migrations/migration_96_test.go +++ /dev/null @@ -1,261 +0,0 @@ -package migrations - -import ( - "context" - "slices" - "testing" - - "github.com/shellhub-io/shellhub/pkg/envs" - envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration96Up(t *testing.T) { - ctx := context.Background() - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "Drop recorded_sessions collection in enterprise mode", - setup: func() error { - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("true").Once() - - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-1", - "authenticated": true, - }) - if err != nil { - return err - } - - _, err = c. - Database("test"). - Collection("recorded_sessions"). - InsertMany(ctx, []any{ - bson.M{ - "uid": "session-1", - "message": "test output 1", - "time": "2023-01-01T10:00:00Z", - "width": 80, - "height": 24, - }, - bson.M{ - "uid": "session-1", - "message": "test output 2", - "time": "2023-01-01T10:01:00Z", - "width": 80, - "height": 24, - }, - }) - - return err - }, - verify: func(tt *testing.T) { - collections, err := c.Database("test").ListCollectionNames(ctx, bson.M{}) - require.NoError(tt, err) - - hasRecordedSessions := slices.Contains(collections, "recorded_sessions") - - if hasRecordedSessions { - count, err := c. - Database("test"). - Collection("recorded_sessions"). - CountDocuments(ctx, bson.M{}) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - } - - count, err := c. - Database("test"). - Collection("sessions"). - CountDocuments(ctx, bson.M{}) - require.NoError(tt, err) - assert.Equal(tt, int64(1), count) - }, - }, - { - description: "Nothing happens when not in enterprise mode", - setup: func() error { - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Once() - - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-2", - "authenticated": true, - }) - if err != nil { - return err - } - - _, err = c. - Database("test"). - Collection("recorded_sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-2", - "message": "test output", - "time": "2023-01-01T10:00:00Z", - "width": 80, - "height": 24, - }) - - return err - }, - verify: func(tt *testing.T) { - count, err := c. - Database("test"). - Collection("recorded_sessions"). - CountDocuments(ctx, bson.M{"uid": "session-2"}) - require.NoError(tt, err) - assert.Equal(tt, int64(1), count) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[94]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} - -func TestMigration96Down(t *testing.T) { - ctx := context.Background() - mock := &envmock.Backend{} - envs.DefaultBackend = mock - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "Migration down has no effect", - setup: func() error { - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("true").Once() - - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-3", - "authenticated": true, - }) - if err != nil { - return err - } - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[94]) - if err := migrates.Up(ctx, migrate.AllAvailable); err != nil { - return err - } - - return nil - }, - verify: func(tt *testing.T) { - collections, err := c.Database("test").ListCollectionNames(ctx, bson.M{}) - require.NoError(tt, err) - - hasRecordedSessions := slices.Contains(collections, "recorded_sessions") - - if hasRecordedSessions { - count, err := c. - Database("test"). - Collection("recorded_sessions"). - CountDocuments(ctx, bson.M{}) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - } - - count, err := c. - Database("test"). - Collection("sessions"). - CountDocuments(ctx, bson.M{}) - require.NoError(tt, err) - assert.Equal(tt, int64(1), count) - }, - }, - { - description: "Nothing happens when not in enterprise mode", - setup: func() error { - mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false").Twice() - - _, err := c. - Database("test"). - Collection("sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-4", - "authenticated": true, - }) - if err != nil { - return err - } - - _, err = c. - Database("test"). - Collection("recorded_sessions"). - InsertOne(ctx, bson.M{ - "uid": "session-4", - "message": "test output", - "time": "2023-01-01T10:00:00Z", - "width": 80, - "height": 24, - }) - if err != nil { - return err - } - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[95]) - if err := migrates.Up(ctx, migrate.AllAvailable); err != nil { - return err - } - - return nil - }, - verify: func(tt *testing.T) { - count, err := c. - Database("test"). - Collection("recorded_sessions"). - CountDocuments(ctx, bson.M{"uid": "session-4"}) - require.NoError(tt, err) - assert.Equal(tt, int64(1), count) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[95]) - require.NoError(tt, migrates.Down(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_97.go b/api/store/mongo/migrations/migration_97.go deleted file mode 100644 index a79ccdde2be..00000000000 --- a/api/store/mongo/migrations/migration_97.go +++ /dev/null @@ -1,123 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "runtime" - - "github.com/shellhub-io/shellhub/pkg/api/internalclient" - "github.com/shellhub-io/shellhub/pkg/envs" - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "golang.org/x/sync/semaphore" -) - -var ErrMigration97StatusNot200 = errors.New("failed to save the session as asciinema file") - -var migration97 = migrate.Migration{ - Version: 97, - Description: "Save session's events as Asciinema file on Object Storage", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 97, - "action": "Up", - }).Info("Applying migration") - - if !envs.IsEnterprise() { - return nil - } - - cursor, err := db.Collection("sessions").Aggregate(ctx, []bson.M{ - { - "$match": bson.M{ - "recorded": true, - "events.types": bson.M{ - "$all": []models.SessionEventType{ - models.SessionEventTypePtyRequest, - models.SessionEventTypePtyOutput, - }, - }, - }, - }, - }) - if err != nil { - log.WithError(err).Error("Failed to find recorded sessions") - - return err - } - - defer cursor.Close(ctx) - - cli, err := internalclient.NewClient() - if err != nil { - log.WithError(err).Error("Failed to find recorded sessions") - - return err - } - - var ( - maxWorkers = runtime.GOMAXPROCS(0) - sem = semaphore.NewWeighted(int64(maxWorkers)) - ) - - session := &models.Session{} - - for cursor.Next(ctx) { - if err := sem.Acquire(ctx, 1); err != nil { - log.Printf("Failed to acquire semaphore: %v", err) - - break - } - - if err := cursor.Decode(&session); err != nil { - log.WithError(err).Error("Failed to decode UID result") - sem.Release(1) - - return err - } - - go func(session models.Session) { - defer sem.Release(1) - - for s := range session.Events.Seats { - uid, seat := session.UID, s - - log.WithFields(log.Fields{ - "uid": uid, - "seat": seat, - }).Debug("Processing session as Asciinema file") - - if err := cli.SaveSession(uid, seat); err != nil { - log.WithError(err).Error("Error on saving session a session") - - return - } - - log.WithFields(log.Fields{ - "uid": uid, - "seat": seat, - }).Debug("Session saved as Asciinema file") - } - }(*session) - } - - if err := sem.Acquire(ctx, int64(maxWorkers)); err != nil { - log.Printf("Failed to acquire semaphore: %v", err) - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - log.WithFields(log.Fields{ - "component": "migration", - "version": 93, - "action": "Down", - }).Info("Cannot undo migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_98.go b/api/store/mongo/migrations/migration_98.go deleted file mode 100644 index 5aa173dfce0..00000000000 --- a/api/store/mongo/migrations/migration_98.go +++ /dev/null @@ -1,46 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration98 = migrate.Migration{ - Version: 98, - Description: "Set namespace type to team when type is empty", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 98, - "action": "Up", - }).Info("Applying migration") - - if _, err := db. - Collection("namespaces"). - UpdateMany(ctx, bson.M{ - "type": "", - }, bson.M{ - "$set": bson.M{ - "type": models.TypePersonal, - }, - }); err != nil { - return err - } - - return nil - }), - Down: migrate.MigrationFunc(func(_ context.Context, _ *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 98, - "action": "Down", - }).Info("Cannot undo migration") - - return nil - }), -} diff --git a/api/store/mongo/migrations/migration_98_test.go b/api/store/mongo/migrations/migration_98_test.go deleted file mode 100644 index a5dc3dd70c4..00000000000 --- a/api/store/mongo/migrations/migration_98_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration98Up(t *testing.T) { - ctx := context.Background() - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "Session with empty type should be updated to personal", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, bson.M{ - "tenant_id": "empty-type-test", - "type": "", - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("namespaces"). - FindOne(ctx, bson.M{"tenant_id": "empty-type-test"}) - namespace := make(map[string]any) - require.NoError(tt, query.Decode(&namespace)) - - assert.Equal(tt, "personal", namespace["type"], - "Session type should be updated to 'personal' when originally empty") - }, - }, - { - description: "Session with non-empty type should remain unchanged", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertOne(ctx, bson.M{ - "tenant_id": "existing-type-test", - "type": "existing", - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("namespaces"). - FindOne(ctx, bson.M{"tenant_id": "existing-type-test"}) - namespace := make(map[string]any) - require.NoError(tt, query.Decode(&namespace)) - - assert.Equal(tt, "existing", namespace["type"], - "Session type should remain unchanged when not empty") - }, - }, - { - description: "Multiple namespaces with empty type should be updated", - setup: func() error { - _, err := c. - Database("test"). - Collection("namespaces"). - InsertMany(ctx, []any{ - bson.M{ - "tenant_id": "multi-empty-1", - "type": "", - }, - bson.M{ - "tenant_id": "multi-empty-2", - "type": "", - }, - bson.M{ - "tenant_id": "multi-existing", - "type": "existing", - }, - }) - - return err - }, - verify: func(tt *testing.T) { - query := c. - Database("test"). - Collection("namespaces"). - FindOne(ctx, bson.M{"tenant_id": "multi-empty-1"}) - namespace1 := make(map[string]any) - require.NoError(tt, query.Decode(&namespace1)) - assert.Equal(tt, "personal", namespace1["type"]) - - query = c. - Database("test"). - Collection("namespaces"). - FindOne(ctx, bson.M{"tenant_id": "multi-empty-2"}) - namespace2 := make(map[string]any) - require.NoError(tt, query.Decode(&namespace2)) - assert.Equal(tt, "personal", namespace2["type"]) - - query = c. - Database("test"). - Collection("namespaces"). - FindOne(ctx, bson.M{"tenant_id": "multi-existing"}) - namespaceExisting := make(map[string]any) - require.NoError(tt, query.Decode(&namespaceExisting)) - assert.Equal(tt, "existing", namespaceExisting["type"]) - }, - }, - { - description: "No namespaces with empty type should handle gracefully", - setup: func() error { - return nil - }, - verify: func(tt *testing.T) { - count, err := c. - Database("test"). - Collection("namespaces"). - CountDocuments(ctx, bson.M{"type": ""}) - require.NoError(tt, err) - assert.Equal(tt, int64(0), count) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[97]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_99.go b/api/store/mongo/migrations/migration_99.go deleted file mode 100644 index 6792aa83607..00000000000 --- a/api/store/mongo/migrations/migration_99.go +++ /dev/null @@ -1,37 +0,0 @@ -package migrations - -import ( - "context" - - "github.com/sirupsen/logrus" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var migration99 = migrate.Migration{ - Version: 99, - Description: "Convert the username's to nil when it's a blank string", - Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 99, - "action": "Up", - }).Info("Applying migration") - - _, err := db.Collection("users").UpdateMany(ctx, bson.M{"username": ""}, bson.M{"$set": bson.M{"username": nil}}) - - return err - }), - Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { - logrus.WithFields(logrus.Fields{ - "component": "migration", - "version": 99, - "action": "Down", - }).Info("Cannot undo migration") - - _, err := db.Collection("users").UpdateMany(ctx, bson.M{"username": nil}, bson.M{"$set": bson.M{"username": ""}}) - - return err - }), -} diff --git a/api/store/mongo/migrations/migration_99_test.go b/api/store/mongo/migrations/migration_99_test.go deleted file mode 100644 index ce5cb963a3e..00000000000 --- a/api/store/mongo/migrations/migration_99_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration99Up(t *testing.T) { - ctx := context.Background() - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "succeeds", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, bson.M{"email": "john.doe@test.com", "username": ""}) - - return err - }, - verify: func(tt *testing.T) { - user := make(map[string]any) - require.NoError(tt, c.Database("test").Collection("users").FindOne(ctx, bson.M{"email": "john.doe@test.com"}).Decode(&user)) - - username, ok := user["username"] - require.Equal(tt, true, ok) - require.Equal(tt, nil, username) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[98]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} - -func TestMigration99Down(t *testing.T) { - ctx := context.Background() - - cases := []struct { - description string - setup func() error - verify func(tt *testing.T) - }{ - { - description: "succeeds", - setup: func() error { - _, err := c. - Database("test"). - Collection("users"). - InsertOne(ctx, bson.M{"email": "john.doe@test.com", "username": nil}) - - return err - }, - verify: func(tt *testing.T) { - user := make(map[string]any) - require.NoError(tt, c.Database("test").Collection("users").FindOne(ctx, bson.M{"email": "john.doe@test.com"}).Decode(&user)) - - username, ok := user["username"] - require.Equal(tt, true, ok) - require.Equal(tt, "", username) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - require.NoError(tt, tc.setup()) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[98]) - require.NoError(tt, migrates.Up(ctx, migrate.AllAvailable)) - require.NoError(tt, migrates.Down(ctx, migrate.AllAvailable)) - - tc.verify(tt) - }) - } -} diff --git a/api/store/mongo/migrations/migration_9_test.go b/api/store/mongo/migrations/migration_9_test.go deleted file mode 100644 index c00d84a0259..00000000000 --- a/api/store/mongo/migrations/migration_9_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" -) - -func TestMigration9(t *testing.T) { - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:8]...) - err := migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - device := models.Device{ - Name: "Test", - } - - _, err = c.Database("test").Collection("devices").InsertOne(context.TODO(), device) - assert.NoError(t, err) - - migrates = migrate.NewMigrate(c.Database("test"), GenerateMigrations()[:9]...) - err = migrates.Up(context.Background(), migrate.AllAvailable) - assert.NoError(t, err) - - err = c.Database("test").Collection("devices").FindOne(context.TODO(), bson.M{"name": "test"}).Decode(&device) - assert.NoError(t, err) -} diff --git a/api/store/mongo/namespace.go b/api/store/mongo/namespace.go deleted file mode 100644 index d921affd2ca..00000000000 --- a/api/store/mongo/namespace.go +++ /dev/null @@ -1,394 +0,0 @@ -package mongo - -import ( - "context" - "strings" - "time" - - "github.com/shellhub-io/shellhub/api/pkg/gateway" - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo/queries" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" -) - -func (s *Store) NamespaceList(ctx context.Context, paginator query.Paginator, filters query.Filters, opts ...store.NamespaceQueryOption) ([]models.Namespace, int, error) { - query := []bson.M{} - - queryMatch, err := queries.FromFilters(&filters) - if err != nil { - return nil, 0, FromMongoError(err) - } - query = append(query, queryMatch...) - - // Only match for the respective tenant if requested - if id := gateway.IDFromContext(ctx); id != nil { - user, _, err := s.UserGetByID(ctx, id.ID, false) - if err != nil { - return nil, 0, err - } - - query = append(query, bson.M{ - "$match": bson.M{ - "members": bson.M{ - "$elemMatch": bson.M{ - "id": user.ID, - "status": bson.M{ - "$ne": models.MemberStatusPending, - }, - }, - }, - }, - }) - } - - queryCount := query - queryCount = append(queryCount, bson.M{"$count": "count"}) - count, err := AggregateCount(ctx, s.db.Collection("namespaces"), queryCount) - if err != nil { - return nil, 0, err - } - - query = append(query, queries.FromPaginator(&paginator)...) - - namespaces := make([]models.Namespace, 0) - cursor, err := s.db.Collection("namespaces").Aggregate(ctx, query) - if err != nil { - return nil, 0, err - } - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - namespace := new(models.Namespace) - if err := cursor.Decode(namespace); err != nil { - return namespaces, count, err - } - - for _, opt := range opts { - if err := opt(context.WithValue(ctx, "db", s.db), namespace); err != nil { //nolint:revive - return nil, 0, err - } - } - - namespaces = append(namespaces, *namespace) - } - - return namespaces, count, err -} - -func (s *Store) NamespaceGet(ctx context.Context, tenantID string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { - var ns *models.Namespace - - if _ = s.cache.Get(ctx, strings.Join([]string{"namespace", tenantID}, "/"), &ns); ns != nil && ns.TenantID != "" { - goto Opts - } - - if err := s.db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": tenantID}).Decode(&ns); err != nil { - return ns, FromMongoError(err) - } - - if err := s.cache.Set(ctx, strings.Join([]string{"namespace", tenantID}, "/"), ns, time.Minute); err != nil { - log.Error(err) - } - -Opts: - for _, opt := range opts { - if err := opt(context.WithValue(ctx, "db", s.db), ns); err != nil { //nolint:revive - return nil, err - } - } - - return ns, nil -} - -func (s *Store) NamespaceGetByName(ctx context.Context, name string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { - var ns *models.Namespace - - if _ = s.cache.Get(ctx, strings.Join([]string{"namespace", name}, "/"), &ns); ns != nil && ns.TenantID != "" { - goto Opts - } - - if ns != nil { - return ns, nil - } - - if err := s.db.Collection("namespaces").FindOne(ctx, bson.M{"name": name}).Decode(&ns); err != nil { - return nil, FromMongoError(err) - } - -Opts: - for _, opt := range opts { - if err := opt(context.WithValue(ctx, "db", s.db), ns); err != nil { //nolint:revive - return nil, err - } - } - - return ns, nil -} - -func (s *Store) NamespaceGetPreferred(ctx context.Context, userID string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { - filter := bson.M{"members.id": userID} - - if user, _, _ := s.UserGetByID(ctx, userID, false); user != nil { - if user.Preferences.PreferredNamespace != "" { - filter["tenant_id"] = user.Preferences.PreferredNamespace - } - } - - ns := new(models.Namespace) - if err := s.db.Collection("namespaces").FindOne(ctx, filter).Decode(ns); err != nil { - return nil, FromMongoError(err) - } - - for _, opt := range opts { - if err := opt(context.WithValue(ctx, "db", s.db), ns); err != nil { //nolint:revive - return nil, err - } - } - - return ns, nil -} - -func (s *Store) NamespaceCreate(ctx context.Context, namespace *models.Namespace) (*models.Namespace, error) { - namespace.CreatedAt = clock.Now() - - _, err := s.db.Collection("namespaces").InsertOne(ctx, namespace) - if err != nil { - return nil, err - } - - return namespace, err -} - -func (s *Store) NamespaceDelete(ctx context.Context, tenantID string) error { - session, err := s.db.Client().StartSession() - if err != nil { - return err - } - defer session.EndSession(ctx) - - if _, err := session.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) { - r, err := s.db.Collection("namespaces").DeleteOne(sessCtx, bson.M{"tenant_id": tenantID}) - if err != nil { - return nil, FromMongoError(err) - } - - if r.DeletedCount < 1 { - return nil, store.ErrNoDocuments - } - - if err := s.cache.Delete(ctx, strings.Join([]string{"namespace", tenantID}, "/")); err != nil { - log.Error(err) - } - - collections := []string{"devices", "sessions", "firewall_rules", "public_keys", "recorded_sessions", "api_keys"} - for _, collection := range collections { - if _, err := s.db.Collection(collection).DeleteMany(sessCtx, bson.M{"tenant_id": tenantID}); err != nil { - return nil, FromMongoError(err) - } - } - - _, err = s.db. - Collection("users"). - UpdateMany(ctx, bson.M{"preferred_namespace": tenantID}, bson.M{"$set": bson.M{"preferred_namespace": ""}}) - if err != nil { - return nil, FromMongoError(err) - } - - return nil, nil - }); err != nil { - return err - } - - return nil -} - -func (s *Store) NamespaceEdit(ctx context.Context, tenant string, changes *models.NamespaceChanges) error { - res, err := s.db. - Collection("namespaces"). - UpdateOne(ctx, bson.M{"tenant_id": tenant}, bson.M{"$set": changes}) - if err != nil { - return FromMongoError(err) - } - - if res.MatchedCount < 1 { - return store.ErrNoDocuments - } - - if err := s.cache.Delete(ctx, strings.Join([]string{"namespace", tenant}, "/")); err != nil { - log.Error(err) - } - - return nil -} - -func (s *Store) NamespaceUpdate(ctx context.Context, tenantID string, namespace *models.Namespace) error { - ns, err := s.db.Collection("namespaces").UpdateOne( - ctx, - bson.M{ - "tenant_id": tenantID, - }, - bson.M{ - "$set": bson.M{ - "name": namespace.Name, - "max_devices": namespace.MaxDevices, - "settings.session_record": namespace.Settings.SessionRecord, - }, - }, - ) - if err != nil { - return FromMongoError(err) - } - - if ns.MatchedCount < 1 { - return store.ErrNoDocuments - } - - if err := s.cache.Delete(ctx, strings.Join([]string{"namespace", tenantID}, "/")); err != nil { - log.Error(err) - } - - return nil -} - -func (s *Store) NamespaceAddMember(ctx context.Context, tenantID string, member *models.Member) error { - err := s.db. - Collection("namespaces"). - FindOne(ctx, bson.M{"tenant_id": tenantID, "members": bson.M{"$elemMatch": bson.M{"id": member.ID}}}). - Err() - if err == nil { - return ErrNamespaceDuplicatedMember - } - - memberBson := bson.M{ - "id": member.ID, - "added_at": member.AddedAt, - "expires_at": member.ExpiresAt, - "role": member.Role, - "status": member.Status, - } - - res, err := s.db. - Collection("namespaces"). - UpdateOne(ctx, bson.M{"tenant_id": tenantID}, bson.M{"$addToSet": bson.M{"members": memberBson}}) - if err != nil { - return FromMongoError(err) - } - - if res.MatchedCount < 1 { - return store.ErrNoDocuments - } - - if err := s.cache.Delete(ctx, strings.Join([]string{"namespace", tenantID}, "/")); err != nil { - log.Error(err) - } - - return nil -} - -func (s *Store) NamespaceUpdateMember(ctx context.Context, tenantID string, memberID string, changes *models.MemberChanges) error { - filter := bson.M{"tenant_id": tenantID, "members": bson.M{"$elemMatch": bson.M{"id": memberID}}} - update := bson.M{} - - if changes.Role != "" { - update["members.$.role"] = changes.Role - } - - if changes.Status != "" { - update["members.$.status"] = changes.Status - } - - if changes.ExpiresAt != nil { - update["members.$.expires_at"] = *changes.ExpiresAt - } - - ns, err := s.db.Collection("namespaces").UpdateOne(ctx, filter, bson.M{"$set": update}) - if err != nil { - return FromMongoError(err) - } - - if ns.MatchedCount < 1 { - return ErrUserNotFound - } - - if err := s.cache.Delete(ctx, strings.Join([]string{"namespace", tenantID}, "/")); err != nil { - log.Error(err) - } - - return nil -} - -func (s *Store) NamespaceRemoveMember(ctx context.Context, tenantID string, memberID string) error { - session, err := s.db.Client().StartSession() - if err != nil { - return err - } - defer session.EndSession(ctx) - - fn := func(_ mongo.SessionContext) (interface{}, error) { - res, err := s.db. - Collection("namespaces"). - UpdateOne(ctx, bson.M{"tenant_id": tenantID}, bson.M{"$pull": bson.M{"members": bson.M{"id": memberID}}}) - if err != nil { - return nil, FromMongoError(err) - } - - switch { - case res.MatchedCount < 1: // tenant not found - return nil, store.ErrNoDocuments - case res.ModifiedCount < 1: // member not found - return nil, ErrUserNotFound - } - - objID, err := primitive.ObjectIDFromHex(memberID) - if err != nil { - return nil, err - } - - _, err = s.db. - Collection("users"). - UpdateOne(ctx, bson.M{"_id": objID, "preferred_namespace": tenantID}, bson.M{"$set": bson.M{"preferred_namespace": ""}}) - - return nil, FromMongoError(err) - } - - if _, err := session.WithTransaction(ctx, fn); err != nil { - return err - } - - if err := s.cache.Delete(ctx, strings.Join([]string{"namespace", tenantID}, "/")); err != nil { - log.Error(err) - } - - return nil -} - -func (s *Store) NamespaceSetSessionRecord(ctx context.Context, sessionRecord bool, tenantID string) error { - ns, err := s.db.Collection("namespaces").UpdateOne(ctx, bson.M{"tenant_id": tenantID}, bson.M{"$set": bson.M{"settings.session_record": sessionRecord}}) - if err != nil { - return FromMongoError(err) - } - - if ns.MatchedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) NamespaceGetSessionRecord(ctx context.Context, tenantID string) (bool, error) { - var settings struct { - Settings *models.NamespaceSettings `json:"settings" bson:"settings"` - } - - if err := s.db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": tenantID}).Decode(&settings); err != nil { - return false, FromMongoError(err) - } - - return settings.Settings.SessionRecord, nil -} diff --git a/api/store/mongo/namespace_test.go b/api/store/mongo/namespace_test.go deleted file mode 100644 index 93d2b6195f5..00000000000 --- a/api/store/mongo/namespace_test.go +++ /dev/null @@ -1,820 +0,0 @@ -package mongo_test - -import ( - "context" - "sort" - "testing" - "time" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo" - "github.com/shellhub-io/shellhub/pkg/api/authorizer" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/clock" - clockmocks "github.com/shellhub-io/shellhub/pkg/clock/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" -) - -func TestNamespaceList(t *testing.T) { - type Expected struct { - ns []models.Namespace - count int - err error - } - - cases := []struct { - description string - page query.Paginator - filters query.Filters - fixtures []string - expected Expected - }{ - { - description: "succeeds when namespaces list is not empty", - page: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{}, - fixtures: []string{fixtureNamespaces}, - expected: Expected{ - ns: []models.Namespace{ - { - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "namespace-1", - Owner: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Members: []models.Member{ - { - ID: "507f1f77bcf86cd799439011", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleOwner, - Status: models.MemberStatusAccepted, - }, - { - ID: "6509e169ae6144b2f56bf288", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleObserver, - Status: models.MemberStatusPending, - }, - }, - MaxDevices: -1, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - { - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "namespace-2", - Owner: "6509e169ae6144b2f56bf288", - TenantID: "00000000-0000-4001-0000-000000000000", - Members: []models.Member{ - { - ID: "6509e169ae6144b2f56bf288", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleOwner, - Status: models.MemberStatusAccepted, - }, - { - ID: "907f1f77bcf86cd799439022", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleOperator, - Status: models.MemberStatusAccepted, - }, - }, - MaxDevices: 10, - Settings: &models.NamespaceSettings{SessionRecord: false}, - }, - { - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "namespace-3", - Owner: "657b0e3bff780d625f74e49a", - TenantID: "00000000-0000-4002-0000-000000000000", - Members: []models.Member{ - { - ID: "657b0e3bff780d625f74e49a", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleOwner, - Status: models.MemberStatusAccepted, - }, - }, - MaxDevices: 3, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - { - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "namespace-4", - Owner: "6577267d8752d05270a4c07d", - TenantID: "00000000-0000-4003-0000-000000000000", - Members: []models.Member{ - { - ID: "6577267d8752d05270a4c07d", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleOwner, - Status: models.MemberStatusAccepted, - }, - }, - MaxDevices: -1, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - }, - count: 4, - err: nil, - }, - }, - } - - // Due to the non-deterministic order of applying fixtures when dealing with multiple datasets, - // we ensure that both the expected and result arrays are correctly sorted. - sort := func(ns []models.Namespace) { - sort.Slice(ns, func(i, j int) bool { - return ns[i].TenantID < ns[j].TenantID - }) - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - ns, count, err := s.NamespaceList(ctx, tc.page, tc.filters) - sort(tc.expected.ns) - sort(ns) - assert.Equal(t, tc.expected, Expected{ns: ns, count: count, err: err}) - }) - } -} - -func TestNamespaceGet(t *testing.T) { - type Expected struct { - ns *models.Namespace - err error - } - - cases := []struct { - description string - tenant string - fixtures []string - expected Expected - }{ - { - description: "fails when tenant is not found", - tenant: "nonexistent", - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - ns: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when tenant is found", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{ - ns: &models.Namespace{ - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "namespace-1", - Owner: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Members: []models.Member{ - { - ID: "507f1f77bcf86cd799439011", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleOwner, - Status: models.MemberStatusAccepted, - }, - { - ID: "6509e169ae6144b2f56bf288", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleObserver, - Status: models.MemberStatusPending, - }, - }, - MaxDevices: -1, - Settings: &models.NamespaceSettings{SessionRecord: true}, - DevicesCount: 0, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - ns, err := s.NamespaceGet(ctx, tc.tenant) - assert.Equal(t, tc.expected, Expected{ns: ns, err: err}) - }) - } -} - -func TestNamespaceGetByName(t *testing.T) { - type Expected struct { - ns *models.Namespace - err error - } - - cases := []struct { - description string - name string - fixtures []string - expected Expected - }{ - { - description: "fails when namespace is not found", - name: "nonexistent", - fixtures: []string{fixtureNamespaces}, - expected: Expected{ - ns: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when namespace is found", - name: "namespace-1", - fixtures: []string{fixtureNamespaces}, - expected: Expected{ - ns: &models.Namespace{ - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "namespace-1", - Owner: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Members: []models.Member{ - { - ID: "507f1f77bcf86cd799439011", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleOwner, - Status: models.MemberStatusAccepted, - }, - { - ID: "6509e169ae6144b2f56bf288", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleObserver, - Status: models.MemberStatusPending, - }, - }, - MaxDevices: -1, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - ns, err := s.NamespaceGetByName(ctx, tc.name) - assert.Equal(t, tc.expected, Expected{ns: ns, err: err}) - }) - } -} - -func TestNamespaceGetPreferred(t *testing.T) { - type Expected struct { - ns *models.Namespace - err error - } - - cases := []struct { - description string - memberID string - fixtures []string - expected Expected - }{ - { - description: "fails when member is not found", - memberID: "000000000000000000000000", - fixtures: []string{fixtureNamespaces}, - expected: Expected{ - ns: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when member is found and tenantID is empty", - memberID: "507f1f77bcf86cd799439011", - fixtures: []string{fixtureNamespaces}, - expected: Expected{ - ns: &models.Namespace{ - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "namespace-1", - Owner: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Members: []models.Member{ - { - ID: "507f1f77bcf86cd799439011", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleOwner, - Status: models.MemberStatusAccepted, - }, - { - ID: "6509e169ae6144b2f56bf288", - AddedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Role: authorizer.RoleObserver, - Status: models.MemberStatusPending, - }, - }, - MaxDevices: -1, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - ns, err := s.NamespaceGetPreferred(ctx, tc.memberID) - assert.Equal(t, tc.expected, Expected{ns: ns, err: err}) - }) - } -} - -func TestNamespaceCreate(t *testing.T) { - now := time.Now() - - clockMock := new(clockmocks.Clock) - clockMock.On("Now").Return(now) - clock.DefaultBackend = clockMock - - type Expected struct { - ns *models.Namespace - err error - } - - cases := []struct { - description string - ns *models.Namespace - fixtures []string - expected Expected - }{ - { - description: "succeeds when data is valid", - ns: &models.Namespace{ - Name: "namespace-1", - Owner: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Members: []models.Member{ - { - ID: "507f1f77bcf86cd799439011", - Role: authorizer.RoleOwner, - }, - }, - MaxDevices: -1, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - fixtures: []string{}, - expected: Expected{ - ns: &models.Namespace{ - CreatedAt: now, - Name: "namespace-1", - Owner: "507f1f77bcf86cd799439011", - TenantID: "00000000-0000-4000-0000-000000000000", - Members: []models.Member{ - { - ID: "507f1f77bcf86cd799439011", - Role: authorizer.RoleOwner, - }, - }, - MaxDevices: -1, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - ns, err := s.NamespaceCreate(ctx, tc.ns) - assert.Equal(t, tc.expected, Expected{ns: ns, err: err}) - }) - } -} - -func TestNamespaceEdit(t *testing.T) { - cases := []struct { - description string - tenant string - changes *models.NamespaceChanges - fixtures []string - expected error - }{ - { - description: "fails when tenant is not found", - tenant: "nonexistent", - changes: &models.NamespaceChanges{ - Name: "edited-namespace", - }, - fixtures: []string{fixtureNamespaces}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when tenant is found", - tenant: "00000000-0000-4000-0000-000000000000", - changes: &models.NamespaceChanges{ - Name: "edited-namespace", - }, - fixtures: []string{fixtureNamespaces}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.NamespaceEdit(ctx, tc.tenant, tc.changes) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestNamespaceUpdate(t *testing.T) { - cases := []struct { - description string - tenant string - ns *models.Namespace - fixtures []string - expected error - }{ - { - description: "fails when tenant is not found", - tenant: "nonexistent", - ns: &models.Namespace{ - Name: "edited-namespace", - MaxDevices: 3, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - fixtures: []string{fixtureNamespaces}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when tenant is found", - tenant: "00000000-0000-4000-0000-000000000000", - ns: &models.Namespace{ - Name: "edited-namespace", - MaxDevices: 3, - Settings: &models.NamespaceSettings{SessionRecord: true}, - }, - fixtures: []string{fixtureNamespaces}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.NamespaceUpdate(ctx, tc.tenant, tc.ns) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestNamespaceDelete(t *testing.T) { - cases := []struct { - description string - tenant string - fixtures []string - expected error - }{ - { - description: "fails when namespace is not found", - tenant: "nonexistent", - fixtures: []string{fixtureNamespaces}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when namespace is found", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixtureNamespaces}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.NamespaceDelete(ctx, tc.tenant) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestNamespaceAddMember(t *testing.T) { - type Expected struct { - err error - } - - cases := []struct { - description string - tenantID string - member *models.Member - fixtures []string - expected Expected - }{ - { - description: "fails when tenant is not found", - tenantID: "nonexistent", - member: &models.Member{ - ID: "6509de884238881ac1b2b289", - Role: authorizer.RoleObserver, - Status: models.MemberStatusAccepted, - }, - fixtures: []string{fixtureNamespaces}, - expected: Expected{err: store.ErrNoDocuments}, - }, - { - description: "fails when member has already been added", - tenantID: "00000000-0000-4000-0000-000000000000", - member: &models.Member{ - ID: "6509e169ae6144b2f56bf288", - Role: authorizer.RoleObserver, - Status: models.MemberStatusAccepted, - }, - fixtures: []string{fixtureNamespaces}, - expected: Expected{err: mongo.ErrNamespaceDuplicatedMember}, - }, - { - description: "succeeds when tenant is found", - tenantID: "00000000-0000-4000-0000-000000000000", - member: &models.Member{ - ID: "6509de884238881ac1b2b289", - Role: authorizer.RoleObserver, - Status: models.MemberStatusAccepted, - }, - fixtures: []string{fixtureNamespaces}, - expected: Expected{err: nil}, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - if err := s.NamespaceAddMember(ctx, tc.tenantID, tc.member); tc.expected.err != nil { - require.Equal(t, tc.expected.err, err) - - return - } - - require.NoError(t, db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": tc.tenantID, "members.id": tc.member.ID}).Err()) - }) - } -} - -func TestNamespaceUpdateMember(t *testing.T) { - type Expected struct { - err error - } - - cases := []struct { - description string - tenantID string - memberID string - changes *models.MemberChanges - fixtures []string - expected Expected - }{ - { - description: "fails when user is not found", - tenantID: "00000000-0000-4000-0000-000000000000", - memberID: "000000000000000000000000", - changes: &models.MemberChanges{ - Role: authorizer.RoleObserver, - Status: models.MemberStatusPending, - }, - fixtures: []string{fixtureNamespaces}, - expected: Expected{err: mongo.ErrUserNotFound}, - }, - { - description: "succeeds when tenant and user is found", - tenantID: "00000000-0000-4000-0000-000000000000", - memberID: "6509e169ae6144b2f56bf288", - changes: &models.MemberChanges{ - Role: authorizer.RoleAdministrator, - Status: models.MemberStatusPending, - }, - fixtures: []string{fixtureNamespaces}, - expected: Expected{err: nil}, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - if err := s.NamespaceUpdateMember(ctx, tc.tenantID, tc.memberID, tc.changes); tc.expected.err != nil { - require.Equal(t, tc.expected.err, err) - - return - } - - namespace := new(models.Namespace) - require.NoError(t, db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": tc.tenantID, "members.id": tc.memberID}).Decode(namespace)) - require.Equal(t, 2, len(namespace.Members)) - require.Equal(t, tc.memberID, namespace.Members[1].ID) - require.Equal(t, tc.changes.Role, namespace.Members[1].Role) - require.Equal(t, tc.changes.Status, namespace.Members[1].Status) - }) - } -} - -func TestNamespaceRemoveMember(t *testing.T) { - type Expected struct { - err error - } - - cases := []struct { - description string - tenantID string - memberID string - fixtures []string - expected Expected - }{ - { - description: "fails when tenant is not found", - tenantID: "nonexistent", - memberID: "6509de884238881ac1b2b289", - fixtures: []string{fixtureNamespaces}, - expected: Expected{err: store.ErrNoDocuments}, - }, - { - description: "fails when member is not found", - tenantID: "00000000-0000-4000-0000-000000000000", - memberID: "nonexistent", - fixtures: []string{fixtureNamespaces}, - expected: Expected{err: mongo.ErrUserNotFound}, - }, - { - description: "succeeds when tenant and user is found", - tenantID: "00000000-0000-4000-0000-000000000000", - memberID: "6509e169ae6144b2f56bf288", - fixtures: []string{fixtureNamespaces}, - expected: Expected{err: nil}, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - if err := s.NamespaceRemoveMember(ctx, tc.tenantID, tc.memberID); tc.expected.err != nil { - require.Equal(t, tc.expected.err, err) - - return - } - - namespace := new(models.Namespace) - require.NoError(t, db.Collection("namespaces").FindOne(ctx, bson.M{"tenant_id": tc.tenantID}).Decode(namespace)) - require.Equal(t, 1, len(namespace.Members)) - }) - } -} - -func TestNamespaceSetSessionRecord(t *testing.T) { - cases := []struct { - description string - tenant string - sessionRec bool - fixtures []string - expected error - }{ - { - description: "fails when tenant is not found", - tenant: "nonexistent", - sessionRec: true, - fixtures: []string{fixtureNamespaces}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when tenant is found", - tenant: "00000000-0000-4000-0000-000000000000", - sessionRec: true, - fixtures: []string{fixtureNamespaces}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.NamespaceSetSessionRecord(ctx, tc.sessionRec, tc.tenant) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestNamespaceGetSessionRecord(t *testing.T) { - type Expected struct { - set bool - err error - } - - cases := []struct { - description string - tenant string - fixtures []string - expected Expected - }{ - { - description: "fails when tenant is not found", - tenant: "nonexistent", - fixtures: []string{fixtureNamespaces}, - expected: Expected{ - set: false, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when tenant is found", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixtureNamespaces}, - expected: Expected{ - set: true, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - set, err := s.NamespaceGetSessionRecord(ctx, tc.tenant) - assert.Equal(t, tc.expected, Expected{set: set, err: err}) - }) - } -} diff --git a/api/store/mongo/options/options.go b/api/store/mongo/options/options.go deleted file mode 100644 index 1e41185ac39..00000000000 --- a/api/store/mongo/options/options.go +++ /dev/null @@ -1,106 +0,0 @@ -package options - -import ( - "context" - - "github.com/pkg/errors" - "github.com/shellhub-io/shellhub/api/store/mongo/migrations" - "github.com/sirupsen/logrus" - lock "github.com/square/mongo-lock" - migrate "github.com/xakep666/mongo-migrate" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - "go.mongodb.org/mongo-driver/mongo/writeconcern" -) - -type DatabaseOpt func(ctx context.Context, db *mongo.Database) error - -func RunMigatrions(ctx context.Context, db *mongo.Database) error { - logrus.Info("Creating lock for the resource migrations") - - lockClient := lock.NewClient(db.Collection("locks", options.Collection().SetWriteConcern(writeconcern.Majority()))) - if err := lockClient.CreateIndexes(context.TODO()); err != nil { - logrus.WithError(err).Fatal("Failed to create a lock for the database") - } - - logrus.Info("Locking the resource migrations") - - lockID := "0" - - if err := lockClient.XLock(ctx, "migrations", lockID, lock.LockDetails{}); err != nil { - logrus.WithError(err).Fatal("Failed to lock the migrations") - } - - defer func() { - logrus.Info("Unlocking the resource migrations") - - if _, err := lockClient.Unlock(ctx, lockID); err != nil { - logrus.WithError(err).Fatal("Failed to unlock the migrations") - } - }() - - if err := fixMigrations072(db); err != nil { - logrus.WithError(err).Fatal("Failed to fix the migrations lock bug") - } - - list := migrations.GenerateMigrations() - migration := migrate.NewMigrate(db, list...) - - current, _, err := migration.Version(ctx) - if err != nil { - logrus.WithError(err).Fatal("Failed to get current migration version") - } - - latest := list[len(list)-1] - - if current == latest.Version { - logrus.Info("No migrations to apply") - - return nil - } - - logrus.WithFields(logrus.Fields{ - "from": current, - "to": latest.Version, - }).Info("Migrating database") - - return migration.Up(ctx, migrate.AllAvailable) -} - -// This function is necessary due the lock bug on v0.7.2. -func fixMigrations072(db *mongo.Database) error { - // Search for lock in migrations collection. - if _, err := db.Collection("migrations").Find(context.TODO(), - bson.M{"resource": "migrations"}, - ); err != nil && err == mongo.ErrNoDocuments { - // No documents found, nothing to do. - return nil - } else if err != nil { - return errors.Wrap(err, "Failed to find a lock for the migrations") - } - - // Creates a temporary collection containing unique migration documents. - if _, err := db.Collection("migrations").Aggregate(context.TODO(), []bson.M{ - {"$match": bson.M{"version": bson.M{"$ne": nil}}}, - {"$sort": bson.M{"_id": 1}}, - {"$group": bson.M{"_id": "$version", "doc": bson.M{"$first": "$$ROOT"}}}, - {"$replaceRoot": bson.M{"newRoot": "$doc"}}, - {"$out": "migrations_tmp"}, - }); err != nil { - return errors.Wrap(err, "Failed to create a temporary collection") - } - - // Cleanup migrations collection. - if _, err := db.Collection("migrations").DeleteMany(context.TODO(), bson.M{}); err != nil { - return errors.Wrap(err, "Failed to cleanup the migrations collection") - } - - // Copy documents from temporary collection to migrations collection. - if _, err := db.Collection("migrations_tmp").Aggregate(context.TODO(), []bson.M{{"$out": "migrations"}}); err != nil { - return errors.Wrap(err, "Failed to copy the documents to a new migration collection") - } - - // Drop temporary collection. - return db.Collection("migrations_tmp").Drop(context.TODO()) -} diff --git a/api/store/mongo/privatekey.go b/api/store/mongo/privatekey.go deleted file mode 100644 index 5bf044668d9..00000000000 --- a/api/store/mongo/privatekey.go +++ /dev/null @@ -1,23 +0,0 @@ -package mongo - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" - "go.mongodb.org/mongo-driver/bson" -) - -func (s *Store) PrivateKeyCreate(ctx context.Context, key *models.PrivateKey) error { - _, err := s.db.Collection("private_keys").InsertOne(ctx, key) - - return FromMongoError(err) -} - -func (s *Store) PrivateKeyGet(ctx context.Context, fingerprint string) (*models.PrivateKey, error) { - privKey := new(models.PrivateKey) - if err := s.db.Collection("private_keys").FindOne(ctx, bson.M{"fingerprint": fingerprint}).Decode(&privKey); err != nil { - return nil, FromMongoError(err) - } - - return privKey, nil -} diff --git a/api/store/mongo/privatekey_test.go b/api/store/mongo/privatekey_test.go deleted file mode 100644 index 2a004af93cd..00000000000 --- a/api/store/mongo/privatekey_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package mongo_test - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" -) - -func TestPrivateKeyCreate(t *testing.T) { - cases := []struct { - description string - priKey *models.PrivateKey - fixtures []string - expected error - }{ - { - description: "succeeds when data is valid", - priKey: &models.PrivateKey{ - Data: []byte("test"), - Fingerprint: "fingerprint", - CreatedAt: time.Now(), - }, - fixtures: []string{}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.PrivateKeyCreate(ctx, tc.priKey) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestPrivateKeyGet(t *testing.T) { - type Expected struct { - privKey *models.PrivateKey - err error - } - - cases := []struct { - description string - fingerprint string - fixtures []string - expected Expected - }{ - { - description: "fails when private key is not found", - fingerprint: "nonexistent", - fixtures: []string{fixturePrivateKeys}, - expected: Expected{ - privKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when private key is found", - fingerprint: "fingerprint", - fixtures: []string{fixturePrivateKeys}, - expected: Expected{ - privKey: &models.PrivateKey{ - Data: []byte("test"), - Fingerprint: "fingerprint", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - privKey, err := s.PrivateKeyGet(ctx, tc.fingerprint) - assert.Equal(t, tc.expected, Expected{privKey: privKey, err: err}) - }) - } -} diff --git a/api/store/mongo/publickey.go b/api/store/mongo/publickey.go deleted file mode 100644 index 4b4e0339b5d..00000000000 --- a/api/store/mongo/publickey.go +++ /dev/null @@ -1,100 +0,0 @@ -package mongo - -import ( - "context" - - "github.com/shellhub-io/shellhub/api/pkg/gateway" - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo/queries" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/models" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo/options" -) - -func (s *Store) PublicKeyGet(ctx context.Context, fingerprint string, tenantID string) (*models.PublicKey, error) { - pubKey := new(models.PublicKey) - if err := s.db.Collection("public_keys").FindOne(ctx, bson.M{"fingerprint": fingerprint, "tenant_id": tenantID}).Decode(&pubKey); err != nil { - return nil, FromMongoError(err) - } - - return pubKey, nil -} - -func (s *Store) PublicKeyList(ctx context.Context, paginator query.Paginator) ([]models.PublicKey, int, error) { - query := []bson.M{ - { - "$sort": bson.M{ - "created_at": 1, - }, - }, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append(query, bson.M{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }) - } - - queryCount := query - queryCount = append(queryCount, bson.M{"$count": "count"}) - count, err := AggregateCount(ctx, s.db.Collection("public_keys"), queryCount) - if err != nil { - return nil, 0, err - } - - query = append(query, queries.FromPaginator(&paginator)...) - - list := make([]models.PublicKey, 0) - cursor, err := s.db.Collection("public_keys").Aggregate(ctx, query) - if err != nil { - return nil, 0, err - } - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - key := new(models.PublicKey) - err = cursor.Decode(&key) - if err != nil { - return list, count, err - } - - list = append(list, *key) - } - - return list, count, err -} - -func (s *Store) PublicKeyCreate(ctx context.Context, key *models.PublicKey) error { - _, err := s.db.Collection("public_keys").InsertOne(ctx, key) - - return FromMongoError(err) -} - -func (s *Store) PublicKeyUpdate(ctx context.Context, fingerprint string, tenantID string, key *models.PublicKeyUpdate) (*models.PublicKey, error) { - opts := options.FindOneAndUpdate().SetReturnDocument(options.After) - filter := bson.M{"fingerprint": fingerprint, "tenant_id": tenantID} - - pubKey := new(models.PublicKey) - if err := s.db.Collection("public_keys").FindOneAndUpdate(ctx, filter, bson.M{"$set": key}, opts).Decode(&pubKey); err != nil { - return nil, FromMongoError(err) - } - - return pubKey, nil -} - -func (s *Store) PublicKeyDelete(ctx context.Context, fingerprint string, tenantID string) error { - pubKey, err := s.db.Collection("public_keys").DeleteOne(ctx, bson.M{"fingerprint": fingerprint, "tenant_id": tenantID}) - if err != nil { - return FromMongoError(err) - } - - if pubKey.DeletedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} diff --git a/api/store/mongo/publickey_tags.go b/api/store/mongo/publickey_tags.go deleted file mode 100644 index 8a11753d90b..00000000000 --- a/api/store/mongo/publickey_tags.go +++ /dev/null @@ -1,63 +0,0 @@ -package mongo - -import ( - "context" - - "github.com/shellhub-io/shellhub/api/store" - "go.mongodb.org/mongo-driver/bson" -) - -func (s *Store) PublicKeyPushTag(ctx context.Context, tenant, fingerprint, tag string) error { - result, err := s.db.Collection("public_keys").UpdateOne(ctx, bson.M{"tenant_id": tenant, "fingerprint": fingerprint}, bson.M{"$addToSet": bson.M{"filter.tags": tag}}) - if err != nil { - return err - } - - if result.ModifiedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) PublicKeyPullTag(ctx context.Context, tenant, fingerprint, tag string) error { - result, err := s.db.Collection("public_keys").UpdateOne(ctx, bson.M{"tenant_id": tenant, "fingerprint": fingerprint}, bson.M{"$pull": bson.M{"filter.tags": tag}}) - if err != nil { - return err - } - - if result.ModifiedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) PublicKeySetTags(ctx context.Context, tenant, fingerprint string, tags []string) (int64, int64, error) { - res, err := s.db.Collection("public_keys").UpdateOne(ctx, bson.M{"tenant_id": tenant, "fingerprint": fingerprint}, bson.M{"$set": bson.M{"filter.tags": tags}}) - - return res.MatchedCount, res.ModifiedCount, FromMongoError(err) -} - -func (s *Store) PublicKeyBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (int64, error) { - res, err := s.db.Collection("public_keys").UpdateMany(ctx, bson.M{"tenant_id": tenant, "filter.tags": currentTag}, bson.M{"$set": bson.M{"filter.tags.$": newTag}}) - - return res.ModifiedCount, FromMongoError(err) -} - -func (s *Store) PublicKeyBulkDeleteTag(ctx context.Context, tenant, tag string) (int64, error) { - res, err := s.db.Collection("public_keys").UpdateMany(ctx, bson.M{"tenant_id": tenant}, bson.M{"$pull": bson.M{"filter.tags": tag}}) - - return res.ModifiedCount, FromMongoError(err) -} - -func (s *Store) PublicKeyGetTags(ctx context.Context, tenant string) ([]string, int, error) { - list, err := s.db.Collection("public_keys").Distinct(ctx, "filter.tags", bson.M{"tenant_id": tenant}) - - tags := make([]string, len(list)) - for i, item := range list { - tags[i] = item.(string) //nolint:forcetypeassert - } - - return tags, len(tags), FromMongoError(err) -} diff --git a/api/store/mongo/publickey_tags_test.go b/api/store/mongo/publickey_tags_test.go deleted file mode 100644 index 27f4c64d124..00000000000 --- a/api/store/mongo/publickey_tags_test.go +++ /dev/null @@ -1,363 +0,0 @@ -package mongo_test - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/stretchr/testify/assert" -) - -func TestPublicKeyPushTag(t *testing.T) { - cases := []struct { - description string - fingerprint string - tenant string - tag string - fixtures []string - expected error - }{ - { - description: "fails when public key is not found due to fingerprint", - fingerprint: "nonexistent", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "new-tag", - fixtures: []string{fixturePublicKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "fails when public key is not found due to tenant", - fingerprint: "fingerprint", - tenant: "nonexistent", - tag: "new-tag", - fixtures: []string{fixturePublicKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when public key is found", - fingerprint: "fingerprint", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "new-tag", - fixtures: []string{fixturePublicKeys}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.PublicKeyPushTag(ctx, tc.tenant, tc.fingerprint, tc.tag) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestPublicKeyPullTag(t *testing.T) { - cases := []struct { - description string - fingerprint string - tenant string - tag string - fixtures []string - expected error - }{ - { - description: "fails when public key is not found due to fingerprint", - fingerprint: "nonexistent", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "tag-1", - fixtures: []string{fixturePublicKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "fails when public key is not found due to tenant", - fingerprint: "fingerprint", - tenant: "nonexistent", - tag: "tag-1", - fixtures: []string{fixturePublicKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "fails when public key is not found due to tag", - fingerprint: "fingerprint", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "nonexistent", - fixtures: []string{fixturePublicKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when public key is found", - fingerprint: "fingerprint", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "tag-1", - fixtures: []string{fixturePublicKeys}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.PublicKeyPullTag(ctx, tc.tenant, tc.fingerprint, tc.tag) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestPublicKeySetTags(t *testing.T) { - type Expected struct { - matchedCount int64 - updatedCount int64 - err error - } - - cases := []struct { - description string - fingerprint string - tenant string - tags []string - fixtures []string - expected Expected - }{ - { - description: "fails when public key is not found due to fingerprint", - fingerprint: "nonexistent", - tenant: "00000000-0000-4000-0000-000000000000", - tags: []string{"tag-1"}, - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - matchedCount: 0, - updatedCount: 0, - err: nil, - }, - }, - { - description: "fails when public key is not found due to tenant", - fingerprint: "fingerprint", - tenant: "nonexistent", - tags: []string{"tag-1"}, - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - matchedCount: 0, - updatedCount: 0, - err: nil, - }, - }, - { - description: "succeeds when tags public key is found and tags are equal than current public key tags", - fingerprint: "fingerprint", - tenant: "00000000-0000-4000-0000-000000000000", - tags: []string{"tag-1"}, - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - matchedCount: 1, - updatedCount: 0, - err: nil, - }, - }, - { - description: "succeeds when tags public key is found", - fingerprint: "fingerprint", - tenant: "00000000-0000-4000-0000-000000000000", - tags: []string{"new-tag"}, - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - matchedCount: 1, - updatedCount: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - matchedCount, updatedCount, err := s.PublicKeySetTags(ctx, tc.tenant, tc.fingerprint, tc.tags) - assert.Equal(t, tc.expected, Expected{matchedCount, updatedCount, err}) - }) - } -} - -func TestPublicKeyBulkRenameTag(t *testing.T) { - type Expected struct { - count int64 - err error - } - - cases := []struct { - description string - fingerprint string - tenant string - oldTag string - newTag string - fixtures []string - expected Expected - }{ - { - description: "fails when public key is not found due to tenant", - fingerprint: "fingerprint", - tenant: "nonexistent", - oldTag: "tag-1", - newTag: "edited-tag", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - count: 0, - err: nil, - }, - }, - { - description: "fails when public key is not found due to tag", - tenant: "00000000-0000-4000-0000-000000000000", - oldTag: "nonexistent", - newTag: "edited-tag", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - count: 0, - err: nil, - }, - }, - { - description: "succeeds when public key is found", - tenant: "00000000-0000-4000-0000-000000000000", - oldTag: "tag-1", - newTag: "edited-tag", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - count: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - count, err := s.PublicKeyBulkRenameTag(ctx, tc.tenant, tc.oldTag, tc.newTag) - assert.Equal(t, tc.expected, Expected{count, err}) - }) - } -} - -func TestPublicKeyBulkDeleteTag(t *testing.T) { - type Expected struct { - count int64 - err error - } - - cases := []struct { - description string - tenant string - tag string - fixtures []string - expected Expected - }{ - { - description: "fails when public key is not found due to tenant", - tenant: "nonexistent", - tag: "tag-1", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - count: 0, - err: nil, - }, - }, - { - description: "fails when public key is not found due to tag", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "nonexistent", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - count: 0, - err: nil, - }, - }, - { - description: "succeeds when public key is found", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "tag-1", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - count: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - count, err := s.PublicKeyBulkDeleteTag(ctx, tc.tenant, tc.tag) - assert.Equal(t, tc.expected, Expected{count, err}) - }) - } -} - -func TestPublicKeyGetTags(t *testing.T) { - type Expected struct { - tags []string - len int - err error - } - - cases := []struct { - description string - tenant string - fixtures []string - expected Expected - }{ - { - description: "succeeds when tags list is greater than 1", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - tags: []string{"tag-1"}, - len: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - tags, count, err := s.PublicKeyGetTags(ctx, tc.tenant) - assert.Equal(t, tc.expected, Expected{tags: tags, len: count, err: err}) - }) - } -} diff --git a/api/store/mongo/publickey_test.go b/api/store/mongo/publickey_test.go deleted file mode 100644 index 87059310d21..00000000000 --- a/api/store/mongo/publickey_test.go +++ /dev/null @@ -1,320 +0,0 @@ -package mongo_test - -import ( - "context" - "testing" - "time" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" -) - -func TestPublicKeyGet(t *testing.T) { - type Expected struct { - pubKey *models.PublicKey - err error - } - - cases := []struct { - description string - fingerprint string - tenant string - fixtures []string - expected Expected - }{ - { - description: "succeeds when public key is not found due to fingerprint", - fingerprint: "nonexistent", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - pubKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when public key is not found due to tenant", - fingerprint: "fingerprint", - tenant: "nonexistent", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - pubKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when public key is found", - fingerprint: "fingerprint", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - pubKey: &models.PublicKey{ - Data: []byte("test"), - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Fingerprint: "fingerprint", - TenantID: "00000000-0000-4000-0000-000000000000", - PublicKeyFields: models.PublicKeyFields{ - Name: "public_key", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - Tags: []string{"tag-1"}, - }, - }, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - pubKey, err := s.PublicKeyGet(ctx, tc.fingerprint, tc.tenant) - assert.Equal(t, tc.expected, Expected{pubKey: pubKey, err: err}) - }) - } -} - -func TestPublicKeyList(t *testing.T) { - type Expected struct { - pubKey []models.PublicKey - len int - err error - } - - cases := []struct { - description string - fixtures []string - expected Expected - }{ - { - description: "succeeds when public key list is empty", - fixtures: []string{}, - expected: Expected{ - pubKey: []models.PublicKey{}, - len: 0, - err: nil, - }, - }, - { - description: "succeeds when public key list len is greater than 1", - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - pubKey: []models.PublicKey{ - { - Data: []byte("test"), - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Fingerprint: "fingerprint", - TenantID: "00000000-0000-4000-0000-000000000000", - PublicKeyFields: models.PublicKeyFields{ - Name: "public_key", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - Tags: []string{"tag-1"}, - }, - }, - }, - }, - len: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - pubKey, count, err := s.PublicKeyList(ctx, query.Paginator{Page: -1, PerPage: -1}) - assert.Equal(t, tc.expected, Expected{pubKey: pubKey, len: count, err: err}) - }) - } -} - -func TestPublicKeyCreate(t *testing.T) { - cases := []struct { - description string - key *models.PublicKey - fixtures []string - expected error - }{ - { - description: "succeeds when data is valid", - key: &models.PublicKey{ - Data: []byte("test"), - Fingerprint: "fingerprint", - TenantID: "00000000-0000-4000-0000-000000000000", - PublicKeyFields: models.PublicKeyFields{Name: "public_key", Filter: models.PublicKeyFilter{Hostname: ".*"}}, - }, - fixtures: []string{}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.PublicKeyCreate(ctx, tc.key) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestPublicKeyUpdate(t *testing.T) { - type Expected struct { - pubKey *models.PublicKey - err error - } - - cases := []struct { - description string - fingerprint string - tenant string - key *models.PublicKeyUpdate - fixtures []string - expected Expected - }{ - { - description: "succeeds when public key is not found due to fingerprint", - fingerprint: "nonexistent", - tenant: "00000000-0000-4000-0000-000000000000", - key: &models.PublicKeyUpdate{ - PublicKeyFields: models.PublicKeyFields{ - Name: "edited_name", - Filter: models.PublicKeyFilter{Hostname: ".*"}, - }, - }, - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - pubKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when public key is not found due to tenant", - fingerprint: "fingerprint", - tenant: "nonexistent", - key: &models.PublicKeyUpdate{ - PublicKeyFields: models.PublicKeyFields{ - Name: "edited_name", - Filter: models.PublicKeyFilter{Hostname: ".*"}, - }, - }, - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - pubKey: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when public key is found", - fingerprint: "fingerprint", - tenant: "00000000-0000-4000-0000-000000000000", - key: &models.PublicKeyUpdate{ - PublicKeyFields: models.PublicKeyFields{ - Name: "edited_key", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - Tags: []string{"edited-tag"}, - }, - }, - }, - fixtures: []string{fixturePublicKeys}, - expected: Expected{ - pubKey: &models.PublicKey{ - Data: []byte("test"), - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Fingerprint: "fingerprint", - TenantID: "00000000-0000-4000-0000-000000000000", - PublicKeyFields: models.PublicKeyFields{ - Name: "edited_key", - Filter: models.PublicKeyFilter{ - Hostname: ".*", - Tags: []string{"edited-tag"}, - }, - }, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - pubKey, err := s.PublicKeyUpdate(ctx, tc.fingerprint, tc.tenant, tc.key) - assert.Equal(t, tc.expected, Expected{pubKey: pubKey, err: err}) - }) - } -} - -func TestPublicKeyDelete(t *testing.T) { - cases := []struct { - description string - fingerprint string - tenant string - fixtures []string - expected error - }{ - { - description: "fails when public key is not found due to fingerprint", - fingerprint: "nonexistent", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixturePublicKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "fails when public key is not found due to tenant", - fingerprint: "fingerprint", - tenant: "nonexistent", - fixtures: []string{fixturePublicKeys}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when public key is found", - fingerprint: "fingerprint", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixturePublicKeys}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.PublicKeyDelete(ctx, tc.fingerprint, tc.tenant) - assert.Equal(t, tc.expected, err) - }) - } -} diff --git a/api/store/mongo/queries/builder.go b/api/store/mongo/queries/builder.go deleted file mode 100644 index fb14fc57cc7..00000000000 --- a/api/store/mongo/queries/builder.go +++ /dev/null @@ -1,102 +0,0 @@ -package queries - -import ( - "github.com/shellhub-io/shellhub/api/store/mongo/queries/internal" - "github.com/shellhub-io/shellhub/pkg/api/query" - "go.mongodb.org/mongo-driver/bson" -) - -// FromPaginator converts the Paginator instance to a BSON pagination expression for MongoDB queries. -// If the per-page count is less than 1, it returns nil. -func FromPaginator(p *query.Paginator) []bson.M { - if p.PerPage < 1 { - return nil - } - - return []bson.M{ - {"$skip": p.PerPage * (p.Page - 1)}, - {"$limit": p.PerPage}, - } -} - -// FromSorter converts the Sort instance to a BSON sorting expression for MongoDB queries. -// If an invalid value of `Sort.By` is provided, it defaults to ascending order (OrderAsc). -func FromSorter(s *query.Sorter) []bson.M { - options := map[string]int{ - query.OrderAsc: 1, - query.OrderDesc: -1, - } - - order, ok := options[s.Order] - if !ok { - order = -1 - } - - return []bson.M{ - { - "$sort": bson.M{ - s.By: order, - }, - }, - } -} - -// FromFilters converts the Filters instance to a BSON filter expression for MongoDB queries. -// Returns an error when an invalid filter is found. -func FromFilters(fs *query.Filters) ([]bson.M, error) { - if len(fs.Data) < 1 { - return []bson.M{}, nil - } - - queryFilter := make([]bson.M, 0) - queryMatcher := make([]bson.M, 0) - - for _, filter := range fs.Data { - switch filter.Type { - case query.FilterTypeProperty: - param, ok := filter.Params.(*query.FilterProperty) - if !ok { - return nil, query.ErrFilterInvalid - } - - prop, ok, err := internal.ParseFilterProperty(param) - if err != nil { - return nil, query.ErrFilterPropertyInvalid - } - - if !ok { - continue - } - - queryFilter = append(queryFilter, bson.M{param.Name: prop}) - case query.FilterTypeOperator: - param, ok := filter.Params.(*query.FilterOperator) - if !ok { - return nil, query.ErrFilterInvalid - } - - op, ok := internal.ParseFilterOperator(param) - if !ok { - continue - } - - queryMatcher = append(queryMatcher, bson.M{ - "$match": bson.M{op: queryFilter}, - }) - - queryFilter = nil - default: - return nil, query.ErrFilterInvalid - } - } - - if len(queryFilter) > 0 { - queryMatcher = []bson.M{ - { - "$match": bson.M{"$or": queryFilter}, - }, - } - } - - return queryMatcher, nil -} diff --git a/api/store/mongo/queries/builder_test.go b/api/store/mongo/queries/builder_test.go deleted file mode 100644 index 83ac55ff842..00000000000 --- a/api/store/mongo/queries/builder_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package queries - -import ( - "testing" - - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/stretchr/testify/assert" - "go.mongodb.org/mongo-driver/bson" -) - -func TestFromPaginator(t *testing.T) { - cases := []struct { - description string - paginator *query.Paginator - expected []bson.M - }{ - { - description: "succeeds with nil when PerPage is 0", - paginator: &query.Paginator{Page: 1, PerPage: 0}, - expected: nil, - }, - { - description: "skips 0 documents when Page is 1", - paginator: &query.Paginator{Page: 1, PerPage: 10}, - expected: []bson.M{ - {"$skip": 0}, - {"$limit": 10}, - }, - }, - { - description: "skips N documents when Page is > 1", - paginator: &query.Paginator{Page: 3, PerPage: 100}, - expected: []bson.M{ - {"$skip": 200}, - {"$limit": 100}, - }, - }, - } - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - assert.Equal(t, tc.expected, FromPaginator(tc.paginator)) - }) - } -} - -func TestFromSorter(t *testing.T) { - cases := []struct { - description string - sorter *query.Sorter - expected []bson.M - }{ - { - description: "sets sort to -1 when order.By is invalid", - sorter: &query.Sorter{By: "date", Order: "foo"}, - expected: []bson.M{ - { - "$sort": bson.M{ - "date": -1, - }, - }, - }, - }, - { - description: "sets sort to 1 when order.By is asc", - sorter: &query.Sorter{By: "date", Order: "asc"}, - expected: []bson.M{ - { - "$sort": bson.M{ - "date": 1, - }, - }, - }, - }, - { - description: "sets sort to -1 when order.By is desc", - sorter: &query.Sorter{By: "date", Order: "desc"}, - expected: []bson.M{ - { - "$sort": bson.M{ - "date": -1, - }, - }, - }, - }, - } - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - assert.Equal(t, tc.expected, FromSorter(tc.sorter)) - }) - } -} - -func TestFromFilters(t *testing.T) { - type Expected struct { - data []bson.M - err error - } - cases := []struct { - description string - filters *query.Filters - expected Expected - }{ - { - description: "Fail when filter type is not valid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "invalid", - Params: &query.FilterProperty{ - Name: "test", - Operator: "valid", - Value: "test", - }, - }, - }, - }, - expected: Expected{nil, query.ErrFilterInvalid}, - }, - { - description: "Fail when operator in property is invalid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "property", - Params: &query.FilterProperty{ - Name: "test", - Operator: "invalid", - Value: "valid", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{}, - err: nil, - }, - }, - { - description: "Success when one operator in property is valid and other is invalid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "property", - Params: &query.FilterProperty{ - Name: "test", - Operator: "invalid", - Value: "test", - }, - }, - { - Type: "property", - Params: &query.FilterProperty{ - Name: "test", - Operator: "eq", - Value: "valid", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{{"$match": bson.M{"$or": []bson.M{{"test": bson.M{"$eq": "valid"}}}}}}, - err: nil, - }, - }, - { - description: "Success when operator in property is valid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "property", - Params: &query.FilterProperty{ - Name: "test", - Operator: "eq", - Value: "valid", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{{"$match": bson.M{"$or": []bson.M{{"test": bson.M{"$eq": "valid"}}}}}}, - err: nil, - }, - }, - { - description: "Fail when operator in operator is invalid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "operator", - Params: &query.FilterOperator{ - Name: "invalid", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{}, - err: nil, - }, - }, - { - description: "Fail when operator in operator is valid and other invalid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "operator", - Params: &query.FilterOperator{ - Name: "and", - }, - }, - { - Type: "operator", - Params: &query.FilterOperator{ - Name: "invalid", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{{"$match": bson.M{"$and": []bson.M{}}}}, - err: nil, - }, - }, - { - description: "Success when operator in operator is valid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "operator", - Params: &query.FilterOperator{ - Name: "and", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{{"$match": bson.M{"$and": []bson.M{}}}}, - err: nil, - }, - }, - { - description: "Fail when property operator is invalid and operator is valid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "property", - Params: &query.FilterProperty{ - Name: "test", - Operator: "invalid", - Value: "test", - }, - }, - { - Type: "operator", - Params: &query.FilterOperator{ - Name: "and", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{{"$match": bson.M{"$and": []bson.M{}}}}, - err: nil, - }, - }, - { - description: "Fail when property operator is valid and operator is invalid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "property", - Params: &query.FilterProperty{ - Name: "test", - Operator: "eq", - Value: "test", - }, - }, - { - Type: "operator", - Params: &query.FilterOperator{ - Name: "invalid", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{{"$match": bson.M{"$or": []bson.M{{"test": bson.M{"$eq": "test"}}}}}}, - err: nil, - }, - }, - { - description: "Success when property and operator is valid", - filters: &query.Filters{ - Data: []query.Filter{ - { - Type: "property", - Params: &query.FilterProperty{ - Name: "test", - Operator: "eq", - Value: "test", - }, - }, - { - Type: "operator", - Params: &query.FilterOperator{ - Name: "and", - }, - }, - }, - }, - expected: Expected{ - data: []bson.M{{"$match": bson.M{"$and": []bson.M{{"test": bson.M{"$eq": "test"}}}}}}, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - query, err := FromFilters(tc.filters) - assert.Equal(t, tc.expected, Expected{query, err}) - }) - } -} diff --git a/api/store/mongo/queries/internal/filters.go b/api/store/mongo/queries/internal/filters.go deleted file mode 100644 index eb14c3fa64b..00000000000 --- a/api/store/mongo/queries/internal/filters.go +++ /dev/null @@ -1,106 +0,0 @@ -package internal - -import ( - "errors" - "fmt" - "strconv" - - "github.com/shellhub-io/shellhub/pkg/api/query" - "go.mongodb.org/mongo-driver/bson" -) - -// ParseFilterOperator constructs the filter operator, returning its Bson representation and a boolean -// indicating whether the operator is valid or not. -func ParseFilterOperator(fo *query.FilterOperator) (string, bool) { - validProperties := []string{"and", "or"} - for _, op := range validProperties { - if op == fo.Name { - return fmt.Sprintf("$%s", fo.Name), true - } - } - - return "", false -} - -// ParseFilterProperty constructs the property, returning the BSON representation of the property, a boolean -// indicating whether the operator is valid or not, and an error if any. -func ParseFilterProperty(fp *query.FilterProperty) (bson.M, bool, error) { - var res bson.M - var err error - var ok bool - - switch fp.Operator { - case "contains": - res, err = fromContains(fp.Value) - ok = true - case "eq": - res, err = fromEq(fp.Value) - ok = true - case "bool": - res, err = fromBool(fp.Value) - ok = true - case "gt": - res, err = fromGt(fp.Value) - ok = true - case "ne": - res, err = fromNe(fp.Value) - ok = true - default: - return nil, false, nil - } - - return res, ok, err -} - -// fromContains converts a "contains" JSON expression to a Bson expression using "$regex" or "$all". -func fromContains(value interface{}) (bson.M, error) { - switch value.(type) { - case string: - return bson.M{"$regex": value, "$options": "i"}, nil - case []interface{}: - return bson.M{"$all": value}, nil - } - - return nil, errors.New("invalid value type for fromContains") -} - -// fromEq converts an "eq" JSON expression to a Bson expression using "$eq". -func fromEq(value interface{}) (bson.M, error) { - return bson.M{"$eq": value}, nil -} - -// fromBool converts a "bool" JSON expression to a Bson expression using "$eq" for comparing boolean values. -func fromBool(value interface{}) (bson.M, error) { - switch v := value.(type) { - case int: - value = v != 0 - case string: - var err error - value, err = strconv.ParseBool(v) - if err != nil { - return nil, err - } - } - - return bson.M{"$eq": value}, nil -} - -// fromGt converts a "gt" JSON expression to a Bson expression using "$gt". -func fromGt(value interface{}) (bson.M, error) { - switch v := value.(type) { - case int: - value = v - case string: - var err error - value, err = strconv.Atoi(v) - if err != nil { - return nil, err - } - } - - return bson.M{"$gt": value}, nil -} - -func fromNe(value interface{}) (bson.M, error) { - return bson.M{"$ne": value}, nil -} diff --git a/api/store/mongo/query-options.go b/api/store/mongo/query-options.go deleted file mode 100644 index 640de702f0a..00000000000 --- a/api/store/mongo/query-options.go +++ /dev/null @@ -1,61 +0,0 @@ -package mongo - -import ( - "context" - "errors" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" -) - -func (s *Store) Options() store.QueryOptions { - return s.options -} - -func (*queryOptions) CountAcceptedDevices() store.NamespaceQueryOption { - return func(ctx context.Context, ns *models.Namespace) error { - db, ok := ctx.Value("db").(*mongo.Database) - if !ok { - return errors.New("db not found in context") - } - - countDevice, err := db.Collection("devices").CountDocuments(ctx, bson.M{"tenant_id": ns.TenantID, "status": "accepted"}) - if err != nil { - return FromMongoError(err) - } - - ns.DevicesCount = int(countDevice) - - return nil - } -} - -func (*queryOptions) EnrichMembersData() store.NamespaceQueryOption { - return func(ctx context.Context, ns *models.Namespace) error { - db, ok := ctx.Value("db").(*mongo.Database) - if !ok { - return errors.New("db not found in context") - } - - for i, member := range ns.Members { - user := new(models.User) - objID, _ := primitive.ObjectIDFromHex(member.ID) - - if err := db.Collection("users").FindOne(ctx, bson.M{"_id": objID}).Decode(&user); err != nil { - log.WithError(err). - WithField("id", member.ID). - Error("member not found") - - continue - } - - ns.Members[i].Email = user.Email - } - - return nil - } -} diff --git a/api/store/mongo/query-options_test.go b/api/store/mongo/query-options_test.go deleted file mode 100644 index 01af4fa6d88..00000000000 --- a/api/store/mongo/query-options_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package mongo_test - -import ( - "context" - "errors" - "slices" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/require" -) - -func TestCountAcceptedDevices(t *testing.T) { - type Expected struct { - count int - err error - } - - cases := []struct { - description string - tenant string - ctx func() context.Context - fixtures []string - expected Expected - }{ - { - description: "fails when context does not have db in values", - tenant: "00000000-0000-4000-0000-000000000000", - ctx: func() context.Context { - return context.Background() - }, - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{count: 0, err: errors.New("db not found in context")}, - }, - { - description: "succeeds", - tenant: "00000000-0000-4000-0000-000000000000", - ctx: func() context.Context { - return context.WithValue(context.Background(), "db", db) //nolint:revive - }, - fixtures: []string{fixtureNamespaces, fixtureDevices}, - expected: Expected{count: 3, err: nil}, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - ctx := tc.ctx() - - require.NoError(tt, srv.Apply(tc.fixtures...)) - tt.Cleanup(func() { - require.NoError(tt, srv.Reset()) - }) - - ns := &models.Namespace{TenantID: "00000000-0000-4000-0000-000000000000"} - err := s.Options().CountAcceptedDevices()(ctx, ns) - require.Equal(tt, tc.expected, Expected{ns.DevicesCount, err}) - }) - } -} - -func TestEnrichMembersData(t *testing.T) { - type Expected struct { - emails []string - err error - } - - cases := []struct { - description string - tenant string - ctx func() context.Context - fixtures []string - expected Expected - }{ - { - description: "fails when context does not have db in values", - tenant: "00000000-0000-4000-0000-000000000000", - ctx: func() context.Context { - return context.Background() - }, - fixtures: []string{fixtureNamespaces, fixtureUsers}, - expected: Expected{ - emails: []string{}, - err: errors.New("db not found in context"), - }, - }, - { - description: "succeeds", - tenant: "00000000-0000-4000-0000-000000000000", - ctx: func() context.Context { - return context.WithValue(context.Background(), "db", db) //nolint:revive - }, - fixtures: []string{fixtureNamespaces, fixtureUsers}, - expected: Expected{ - emails: []string{"john.doe@test.com", "maria.garcia@test.com"}, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - ctx := tc.ctx() - - require.NoError(tt, srv.Apply(tc.fixtures...)) - tt.Cleanup(func() { - require.NoError(tt, srv.Reset()) - }) - - ns := &models.Namespace{ - TenantID: "00000000-0000-4000-0000-000000000000", - Members: []models.Member{{ID: "507f1f77bcf86cd799439011"}, {ID: "6509e169ae6144b2f56bf288"}}, - } - - err := s.Options().EnrichMembersData()(ctx, ns) - require.Equal(tt, tc.expected.err, err) - - if err == nil { - for _, m := range ns.Members { - require.Equal(tt, true, slices.Contains(tc.expected.emails, m.Email)) - } - } - }) - } -} diff --git a/api/store/mongo/session.go b/api/store/mongo/session.go deleted file mode 100644 index 6c51c528622..00000000000 --- a/api/store/mongo/session.go +++ /dev/null @@ -1,357 +0,0 @@ -package mongo - -import ( - "context" - - "github.com/shellhub-io/shellhub/api/pkg/gateway" - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo/queries" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -func (s *Store) SessionList(ctx context.Context, paginator query.Paginator) ([]models.Session, int, error) { - query := []bson.M{ - { - "$match": bson.M{ - "uid": bson.M{ - "$ne": nil, - }, - }, - }, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append(query, bson.M{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }) - } - - queryCount := query - queryCount = append(queryCount, bson.M{"$count": "count"}) - count, err := AggregateCount(ctx, s.db.Collection("sessions"), queryCount) - if err != nil { - return nil, 0, FromMongoError(err) - } - - query = append(query, bson.M{ - "$sort": bson.M{ - "started_at": -1, - }, - }) - - query = append(query, queries.FromPaginator(&paginator)...) - query = append(query, []bson.M{ - { - "$lookup": bson.M{ - "from": "active_sessions", - "localField": "uid", - "foreignField": "uid", - "as": "active", - }, - }, - { - "$addFields": bson.M{ - "active": bson.M{"$anyElementTrue": []interface{}{"$active"}}, - }, - }, - }...) - - sessions := make([]models.Session, 0) - cursor, err := s.db.Collection("sessions").Aggregate(ctx, query) - if err != nil { - return sessions, count, FromMongoError(err) - } - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - session := new(models.Session) - err = cursor.Decode(&session) - if err != nil { - return sessions, count, err - } - - device, err := s.DeviceGet(ctx, session.DeviceUID) - if err != nil { - return sessions, count, err - } - - session.Device = device - sessions = append(sessions, *session) - } - - return sessions, count, err -} - -func (s *Store) SessionGet(ctx context.Context, uid models.UID) (*models.Session, error) { - query := []bson.M{ - { - "$match": bson.M{"uid": uid}, - }, - { - "$lookup": bson.M{ - "from": "active_sessions", - "localField": "uid", - "foreignField": "uid", - "as": "active", - }, - }, - { - "$addFields": bson.M{ - "active": bson.M{"$anyElementTrue": []interface{}{"$active"}}, - }, - }, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append(query, bson.M{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }) - } - - session := new(models.Session) - - cursor, err := s.db.Collection("sessions").Aggregate(ctx, query) - if err != nil { - return nil, FromMongoError(err) - } - defer cursor.Close(ctx) - cursor.Next(ctx) - - err = cursor.Decode(&session) - if err != nil { - return nil, FromMongoError(err) - } - - device, err := s.DeviceGet(ctx, session.DeviceUID) - if err != nil { - return nil, FromMongoError(err) - } - - session.Device = device - - return session, nil -} - -func (s *Store) SessionUpdate(ctx context.Context, uid models.UID, model *models.Session) error { - result, err := s.db.Collection("sessions").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$set": model}) - if err != nil { - return FromMongoError(err) - } - - if result.MatchedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) SessionSetRecorded(ctx context.Context, uid models.UID, recorded bool) error { - session, err := s.db.Collection("sessions").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$set": bson.M{"recorded": recorded}}) - if err != nil { - return FromMongoError(err) - } - - if session.MatchedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) SessionCreate(ctx context.Context, session models.Session) (*models.Session, error) { - session.StartedAt = clock.Now() - session.LastSeen = session.StartedAt - session.Recorded = false - - device, err := s.DeviceGet(ctx, session.DeviceUID) - if err != nil { - return nil, FromMongoError(err) - } - - session.TenantID = device.TenantID - - if _, err := s.db.Collection("sessions").InsertOne(ctx, &session); err != nil { - return nil, FromMongoError(err) - } - - return &session, nil -} - -func (s *Store) SessionSetLastSeen(ctx context.Context, uid models.UID) error { - session := models.Session{} - - err := s.db.Collection("sessions").FindOne(ctx, bson.M{"uid": uid}).Decode(&session) - if err != nil { - return FromMongoError(err) - } - - if session.Closed { - return nil - } - - session.LastSeen = clock.Now() - - opts := options.Update().SetUpsert(true) - _, err = s.db.Collection("sessions").UpdateOne(ctx, bson.M{"uid": session.UID}, bson.M{"$set": session}, opts) - if err != nil { - return FromMongoError(err) - } - - if _, err := s.db.Collection("active_sessions").UpdateOne(ctx, bson.M{"uid": uid}, bson.M{"$set": bson.M{"last_seen": clock.Now()}}); err != nil { - return FromMongoError(err) - } - - return nil -} - -// SessionDeleteActives sets a session's "closed" status to true and deletes all related active_sessions. -func (s *Store) SessionDeleteActives(ctx context.Context, uid models.UID) error { - mongoSession, err := s.db.Client().StartSession() - if err != nil { - return FromMongoError(err) - } - defer mongoSession.EndSession(ctx) - - _, err = mongoSession.WithTransaction(ctx, func(_ mongo.SessionContext) (interface{}, error) { - session := new(models.Session) - - query := bson.M{"uid": uid} - update := bson.M{"$set": bson.M{"last_seen": clock.Now(), "closed": true}} - - if err := s.db.Collection("sessions").FindOneAndUpdate(ctx, query, update).Decode(&session); err != nil { - return nil, FromMongoError(err) - } - - _, err := s.db.Collection("active_sessions").DeleteMany(ctx, bson.M{"uid": session.UID}) - - return nil, FromMongoError(err) - }) - - return err -} - -func (s *Store) SessionUpdateDeviceUID(ctx context.Context, oldUID models.UID, newUID models.UID) error { - session, err := s.db.Collection("sessions").UpdateMany(ctx, bson.M{"device_uid": oldUID}, bson.M{"$set": bson.M{"device_uid": newUID}}) - if err != nil { - return FromMongoError(err) - } - - if session.MatchedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) SessionActiveCreate(ctx context.Context, uid models.UID, session *models.Session) error { - _, err := s.db.Collection("active_sessions").InsertOne(ctx, &models.ActiveSession{ - UID: uid, - LastSeen: session.StartedAt, - TenantID: session.TenantID, - }) - if err != nil { - return FromMongoError(err) - } - - return nil -} - -// SessionEvent saves a [models.SessionEvent] into the database. -// -// It pushes the event into events type array, and the event type into a separated set. The set is used to improve the -// performance of indexing when looking for sessions. -func (s *Store) SessionEvent(ctx context.Context, uid models.UID, event *models.SessionEvent) error { - session, err := s.db.Client().StartSession() - if err != nil { - return FromMongoError(err) - } - - defer session.EndSession(ctx) - - if _, err := session.WithTransaction(ctx, func(ctx mongo.SessionContext) (interface{}, error) { - if _, err := s.db.Collection("sessions").UpdateOne(ctx, - bson.M{"uid": uid}, - bson.M{ - "$addToSet": bson.M{ - "events.types": event.Type, - "events.seats": event.Seat, - }, - }, - ); err != nil { - return nil, FromMongoError(err) - } - - if _, err := s.db.Collection("sessions_events").InsertOne(ctx, event); err != nil { - return nil, FromMongoError(err) - } - - return nil, nil - }); err != nil { - return FromMongoError(err) - } - - return nil -} - -func (s *Store) SessionListEvents(ctx context.Context, uid models.UID, seat int, event models.SessionEventType, paginator query.Paginator) ([]models.SessionEvent, int, error) { - query := []bson.M{ - { - "$match": bson.M{ - "session": uid, - "seat": seat, - "type": event, - }, - }, - { - "$sort": bson.M{ - "timestamp": 1, - }, - }, - } - - queryCount := query - queryCount = append(queryCount, bson.M{"$count": "count"}) - count, err := AggregateCount(ctx, s.db.Collection("firewall_rules"), queryCount) - if err != nil { - return nil, 0, FromMongoError(err) - } - - query = append(query, queries.FromPaginator(&paginator)...) - - cursosr, err := s.db.Collection("sessions_events").Aggregate(ctx, query) - if err != nil { - return nil, 0, FromMongoError(err) - } - - events := make([]models.SessionEvent, 0) - if err := cursosr.All(ctx, events); err != nil { - return nil, 0, FromMongoError(err) - } - - return events, count, nil -} - -func (s *Store) SessionDeleteEvents(ctx context.Context, uid models.UID, seat int, event models.SessionEventType) error { - filter := bson.M{ - "session": uid, - "seat": seat, - "type": event, - } - - if _, err := s.db.Collection("sessions_events").DeleteMany(ctx, filter); err != nil { - return FromMongoError(err) - } - - return nil -} diff --git a/api/store/mongo/session_test.go b/api/store/mongo/session_test.go deleted file mode 100644 index eed56a390c0..00000000000 --- a/api/store/mongo/session_test.go +++ /dev/null @@ -1,521 +0,0 @@ -package mongo_test - -import ( - "context" - "sort" - "testing" - "time" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" -) - -func TestSessionList(t *testing.T) { - type Expected struct { - s []models.Session - count int - err error - } - - cases := []struct { - description string - paginator query.Paginator - fixtures []string - expected Expected - }{ - { - description: "succeeds when sessions are found", - paginator: query.Paginator{Page: -1, PerPage: -1}, - fixtures: []string{ - fixtureNamespaces, - fixtureDevices, - fixtureSessions, - fixtureActiveSessions, - }, - expected: Expected{ - s: []models.Session{ - { - StartedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UID: "a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68", - DeviceUID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - TenantID: "00000000-0000-4000-0000-000000000000", - Username: "john_doe", - IPAddress: "0.0.0.0", - Device: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - Active: true, - Closed: true, - Authenticated: true, - Recorded: false, - Type: "shell", - Term: "xterm", - Position: models.SessionPosition{Longitude: 0, Latitude: 0}, - }, - { - StartedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - UID: "e7f3a56d8b9e1dc4c285c98c8ea9c33032a17bda5b6c6b05a6213c2a02f97824", - DeviceUID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - TenantID: "00000000-0000-4000-0000-000000000000", - Username: "john_doe", - IPAddress: "0.0.0.0", - Device: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - Active: false, - Closed: true, - Authenticated: true, - Recorded: true, - Type: "shell", - Term: "xterm", - Position: models.SessionPosition{Longitude: 45.6789, Latitude: -12.3456}, - }, - { - StartedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "fc2e1493d8b6a4c17bf6a2f7f9e55629e384b2d3a21e0c3d90f6e35b0c946178a", - DeviceUID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - TenantID: "00000000-0000-4000-0000-000000000000", - Username: "john_doe", - IPAddress: "0.0.0.0", - Device: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - Active: false, - Closed: true, - Authenticated: true, - Recorded: false, - Type: "exec", - Term: "", - Position: models.SessionPosition{Longitude: -78.9012, Latitude: 23.4567}, - }, - { - StartedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - UID: "bc3d75821a29cfe70bf7986f9ee5629e384b2d3a21e0c3d90f6e35b0c946178a", - DeviceUID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - TenantID: "00000000-0000-4000-0000-000000000000", - Username: "john_doe", - IPAddress: "0.0.0.0", - Device: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - Active: false, - Closed: true, - Authenticated: true, - Recorded: true, - Type: "shell", - Term: "xterm", - Position: models.SessionPosition{Longitude: -56.7890, Latitude: 34.5678}, - }, - }, - count: 4, - err: nil, - }, - }, - } - - // Due to the non-deterministic order of applying fixtures when dealing with multiple datasets, - // we ensure that both the expected and result arrays are correctly sorted. - sort := func(s []models.Session) { - sort.Slice(s, func(i, j int) bool { - return s[i].UID < s[j].UID - }) - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - s, count, err := s.SessionList(ctx, tc.paginator) - - sort(tc.expected.s) - sort(s) - - assert.Equal(t, tc.expected, Expected{s: s, count: count, err: err}) - }) - } -} - -func TestSessionGet(t *testing.T) { - type Expected struct { - s *models.Session - err error - } - - cases := []struct { - description string - UID models.UID - fixtures []string - expected Expected - }{ - { - description: "fails when session is not found", - UID: models.UID("nonexistent"), - fixtures: []string{ - fixtureNamespaces, - fixtureDevices, - fixtureSessions, - fixtureActiveSessions, - }, - expected: Expected{ - s: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when session is found", - UID: models.UID("a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68"), - fixtures: []string{ - fixtureNamespaces, - fixtureDevices, - fixtureSessions, - fixtureActiveSessions, - }, - expected: Expected{ - s: &models.Session{ - StartedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - UID: "a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68", - DeviceUID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - TenantID: "00000000-0000-4000-0000-000000000000", - Username: "john_doe", - IPAddress: "0.0.0.0", - Device: &models.Device{ - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - StatusUpdatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastSeen: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - UID: "2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c", - Name: "device-3", - Identity: &models.DeviceIdentity{MAC: "mac-3"}, - Info: nil, - PublicKey: "", - TenantID: "00000000-0000-4000-0000-000000000000", - Online: false, - Namespace: "namespace-1", - Status: "accepted", - RemoteAddr: "", - Position: nil, - Tags: []string{"tag-1"}, - Acceptable: false, - }, - Active: true, - Closed: true, - Authenticated: true, - Recorded: false, - Type: "shell", - Term: "xterm", - Position: models.SessionPosition{Longitude: 0, Latitude: 0}, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - s, err := s.SessionGet(ctx, tc.UID) - assert.Equal(t, tc.expected, Expected{s: s, err: err}) - }) - } -} - -func TestSessionCreate(t *testing.T) { - cases := []struct { - description string - fixtures []string - session models.Session - expected error - }{ - { - description: "", - fixtures: []string{fixtureDevices, fixtureNamespaces}, - session: models.Session{ - Username: "username", - UID: "uid", - TenantID: "00000000-0000-4000-0000-000000000000", - DeviceUID: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - IPAddress: "0.0.0.0", - Authenticated: true, - }, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - session, err := s.SessionCreate(ctx, tc.session) - assert.Equal(t, tc.expected, err) - assert.NotEmpty(t, session) - }) - } -} - -func TestSessionUpdateDeviceUID(t *testing.T) { - cases := []struct { - description string - oldUID models.UID - newUID models.UID - fixtures []string - expected error - }{ - { - description: "fails when device is not found", - oldUID: models.UID("nonexistent"), - newUID: models.UID("uid"), - fixtures: []string{fixtureSessions}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when device is found", - oldUID: models.UID("2300230e3ca2f637636b4d025d2235269014865db5204b6d115386cbee89809c"), - newUID: models.UID("uid"), - fixtures: []string{fixtureSessions}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.SessionUpdateDeviceUID(ctx, tc.oldUID, tc.newUID) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestSessionUpdate(t *testing.T) { - cases := []struct { - description string - UID models.UID - authenticate bool - fixtures []string - expected error - }{ - { - description: "fails when session is not found", - UID: models.UID("nonexistent"), - authenticate: false, - fixtures: []string{fixtureSessions}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when session is found", - UID: models.UID("a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68"), - authenticate: false, - fixtures: []string{fixtureSessions}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.SessionUpdate(ctx, tc.UID, &models.Session{}) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestSessionSetRecorded(t *testing.T) { - cases := []struct { - description string - UID models.UID - recorded bool - fixtures []string - expected error - }{ - { - description: "fails when session is not found", - UID: models.UID("nonexistent"), - recorded: false, - fixtures: []string{fixtureSessions}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when session is found", - UID: models.UID("a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68"), - recorded: false, - fixtures: []string{fixtureSessions}, - expected: nil, - }, - } - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - err := s.SessionSetRecorded(ctx, tc.UID, tc.recorded) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestSessionSetLastSeen(t *testing.T) { - cases := []struct { - description string - UID models.UID - fixtures []string - expected error - }{ - { - description: "fails when session is not found", - UID: models.UID("nonexistent"), - fixtures: []string{fixtureSessions}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when session is found", - UID: models.UID("a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68"), - fixtures: []string{fixtureSessions}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.SessionSetLastSeen(ctx, tc.UID) - assert.Equal(t, tc.expected, err) - }) - } -} - -func TestSessionDeleteActives(t *testing.T) { - cases := []struct { - description string - UID models.UID - fixtures []string - expected error - }{ - { - description: "fails when session is not found", - UID: models.UID("nonexistent"), - fixtures: []string{fixtureSessions}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when session is found", - UID: models.UID("a3b0431f5df6a7827945d2e34872a5c781452bc36de42f8b1297fd9ecb012f68"), - fixtures: []string{fixtureSessions}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.SessionDeleteActives(ctx, tc.UID) - assert.Equal(t, tc.expected, err) - }) - } -} diff --git a/api/store/mongo/stats.go b/api/store/mongo/stats.go deleted file mode 100644 index 73335a477bd..00000000000 --- a/api/store/mongo/stats.go +++ /dev/null @@ -1,141 +0,0 @@ -package mongo - -import ( - "context" - "time" - - "github.com/shellhub-io/shellhub/api/pkg/gateway" - "github.com/shellhub-io/shellhub/pkg/models" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func (s *Store) GetStats(ctx context.Context) (*models.Stats, error) { - query := []bson.M{ - {"$group": bson.M{"_id": bson.M{"uid": "$uid"}, "count": bson.M{"$sum": 1}}}, - {"$group": bson.M{"_id": bson.M{"uid": "$uid"}, "count": bson.M{"$sum": 1}}}, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append([]bson.M{{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }}, query...) - } - - query = append([]bson.M{ - { - "$match": bson.M{ - "disconnected_at": nil, - "last_seen": bson.M{"$gt": primitive.NewDateTimeFromTime(time.Now().Add(-2 * time.Minute))}, - "status": "accepted", - }, - }, - }, query...) - - onlineDevices, err := AggregateCount(ctx, s.db.Collection("devices"), query) - if err != nil { - return nil, err - } - - query = []bson.M{ - {"$count": "count"}, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append([]bson.M{{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }}, query...) - } - query = append([]bson.M{{ - "$match": bson.M{ - "status": "accepted", - }, - }}, query...) - - registeredDevices, err := AggregateCount(ctx, s.db.Collection("devices"), query) - if err != nil { - return nil, err - } - - query = []bson.M{ - {"$count": "count"}, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append([]bson.M{{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }}, query...) - } - - query = append([]bson.M{{ - "$match": bson.M{ - "status": "pending", - }, - }}, query...) - - pendingDevices, err := AggregateCount(ctx, s.db.Collection("devices"), query) - if err != nil { - return nil, err - } - - query = []bson.M{ - {"$count": "count"}, - } - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append([]bson.M{{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }}, query...) - } - - query = append([]bson.M{{ - "$match": bson.M{ - "status": "rejected", - }, - }}, query...) - - rejectedDevices, err := AggregateCount(ctx, s.db.Collection("devices"), query) - if err != nil { - return nil, err - } - - query = []bson.M{} - - // Only match for the respective tenant if requested - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append(query, bson.M{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }) - } - - query = append(query, bson.M{ - "$count": "count", - }) - - activeSessions, err := AggregateCount(ctx, s.db.Collection("active_sessions"), query) - if err != nil { - return nil, err - } - - return &models.Stats{ - RegisteredDevices: registeredDevices, - OnlineDevices: onlineDevices, - PendingDevices: pendingDevices, - RejectedDevices: rejectedDevices, - ActiveSessions: activeSessions, - }, nil -} diff --git a/api/store/mongo/stats_test.go b/api/store/mongo/stats_test.go deleted file mode 100644 index 94275370454..00000000000 --- a/api/store/mongo/stats_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package mongo_test - -import ( - "context" - "testing" - - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" -) - -func TestGetStats(t *testing.T) { - type Expected struct { - stats *models.Stats - err error - } - - cases := []struct { - description string - fixtures []string - expected Expected - }{ - { - description: "succeeds", - fixtures: []string{ - fixtureUsers, - fixtureNamespaces, - fixtureSessions, - fixtureActiveSessions, - fixtureDevices, - }, - expected: Expected{ - stats: &models.Stats{ - RegisteredDevices: 3, - OnlineDevices: 0, - ActiveSessions: 1, - PendingDevices: 1, - RejectedDevices: 0, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - stats, err := s.GetStats(ctx) - assert.Equal(t, tc.expected, Expected{stats: stats, err: err}) - }) - } -} diff --git a/api/store/mongo/store.go b/api/store/mongo/store.go deleted file mode 100644 index b7d30e51863..00000000000 --- a/api/store/mongo/store.go +++ /dev/null @@ -1,71 +0,0 @@ -package mongo - -import ( - "context" - "errors" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo/options" - "github.com/shellhub-io/shellhub/pkg/cache" - "go.mongodb.org/mongo-driver/mongo" - mongooptions "go.mongodb.org/mongo-driver/mongo/options" - "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" -) - -var ( - ErrWrongParamsType = errors.New("wrong parameters type") - ErrNamespaceDuplicatedMember = errors.New("this member is already in this namespace") - ErrNamespaceMemberNotFound = errors.New("this member does not exist in this namespace") - ErrUserNotFound = errors.New("user not found") - ErrStoreParseURI = errors.New("fail to parse the Mongo URI") - ErrStoreConnect = errors.New("fail to connect to the database on Mongo URI") - ErrStorePing = errors.New("fail to ping the Mongo database") - ErrStoreApplyMigration = errors.New("fail to apply Mongo migrations") -) - -type queryOptions struct{} - -type Store struct { - db *mongo.Database - options *queryOptions - cache cache.Cache -} - -func (s *Store) GetDB() *mongo.Database { - return s.db -} - -func Connect(ctx context.Context, uri string) (*mongo.Client, *mongo.Database, error) { - client, err := mongo.Connect(ctx, mongooptions.Client().ApplyURI(uri)) - if err != nil { - return nil, nil, errors.Join(ErrStoreConnect, err) - } - - if err := client.Ping(ctx, nil); err != nil { - return nil, nil, errors.Join(ErrStorePing, err) - } - - connStr, err := connstring.ParseAndValidate(uri) - if err != nil { - return nil, nil, errors.Join(ErrStoreParseURI, err) - } - - return client, client.Database(connStr.Database), nil -} - -func NewStore(ctx context.Context, uri string, cache cache.Cache, opts ...options.DatabaseOpt) (store.Store, error) { - _, db, err := Connect(ctx, uri) - if err != nil { - return nil, err - } - - store := &Store{db: db, cache: cache, options: &queryOptions{}} - - for _, opt := range opts { - if err := opt(ctx, store.db); err != nil { - return nil, err - } - } - - return store, nil -} diff --git a/api/store/mongo/store_test.go b/api/store/mongo/store_test.go deleted file mode 100644 index 41f2bc570ea..00000000000 --- a/api/store/mongo/store_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package mongo_test - -import ( - "context" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/shellhub-io/mongotest" - "github.com/shellhub-io/shellhub/api/pkg/dbtest" - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo" - "github.com/shellhub-io/shellhub/pkg/cache" - log "github.com/sirupsen/logrus" - mongodb "go.mongodb.org/mongo-driver/mongo" -) - -var ( - srv = &dbtest.Server{} - db *mongodb.Database - s store.Store -) - -const ( - fixtureAPIKeys = "api-key" // Check "store.mongo.fixtures.api-keys" for fixture info - fixtureDevices = "devices" // Check "store.mongo.fixtures.devices" for fixture info - fixtureSessions = "sessions" // Check "store.mongo.fixtures.sessions" for fixture info - fixtureActiveSessions = "active_sessions" // Check "store.mongo.fixtures.active_sessions" for fixture info - fixtureFirewallRules = "firewall_rules" // Check "store.mongo.fixtures.firewall_rules" for fixture info - fixturePublicKeys = "public_keys" // Check "store.mongo.fixtures.public_keys" for fixture info - fixturePrivateKeys = "private_keys" // Check "store.mongo.fixtures.private_keys" for fixture info - fixtureUsers = "users" // Check "store.mongo.fixtures.users" for fixture iefo - fixtureNamespaces = "namespaces" // Check "store.mongo.fixtures.namespaces" for fixture info - fixtureRecoveryTokens = "recovery_tokens" // Check "store.mongo.fixtures.recovery_tokens" for fixture info -) - -func TestMain(m *testing.M) { - log.Info("Starting store tests") - - ctx := context.Background() - - srv.Container.Database = "test" - _, file, _, _ := runtime.Caller(0) - srv.Fixtures.Root = filepath.Join(filepath.Dir(file), "fixtures") - srv.Fixtures.PreInsertFuncs = []mongotest.PreInsertFunc{ - mongotest.SimpleConvertObjID("users", "_id"), - mongotest.SimpleConvertTime("users", "created_at"), - mongotest.SimpleConvertTime("users", "last_login"), - mongotest.SimpleConvertObjID("public_keys", "_id"), - mongotest.SimpleConvertBytes("public_keys", "data"), - mongotest.SimpleConvertTime("public_keys", "created_at"), - mongotest.SimpleConvertObjID("private_keys", "_id"), - mongotest.SimpleConvertBytes("private_keys", "data"), - mongotest.SimpleConvertTime("private_keys", "created_at"), - mongotest.SimpleConvertObjID("namespaces", "_id"), - mongotest.SimpleConvertTime("namespaces", "created_at"), - mongotest.SimpleConvertObjID("devices", "_id"), - mongotest.SimpleConvertTime("devices", "created_at"), - mongotest.SimpleConvertTime("devices", "last_seen"), - mongotest.SimpleConvertTime("devices", "status_updated_at"), - mongotest.SimpleConvertObjID("firewall_rules", "_id"), - mongotest.SimpleConvertObjID("sessions", "_id"), - mongotest.SimpleConvertTime("sessions", "started_at"), - mongotest.SimpleConvertTime("sessions", "last_seen"), - mongotest.SimpleConvertObjID("active_sessions", "_id"), - mongotest.SimpleConvertTime("active_sessions", "last_seen"), - } - - if err := srv.Up(ctx); err != nil { - log.WithError(err).Error("Failed to UP the mongodb container") - os.Exit(1) - } - - log.Info("Connecting to ", srv.Container.ConnectionString) - - var err error - - s, err = mongo.NewStore(ctx, srv.Container.ConnectionString+"/"+srv.Container.Database, cache.NewNullCache()) - if err != nil { - log.WithError(err).Error("Failed to create the mongodb store") - os.Exit(1) - } - - store := s.(*mongo.Store) - db = store.GetDB() - - code := m.Run() - - log.Info("Stopping store tests") - if err := srv.Down(ctx); err != nil { - log.WithError(err).Error("Failed to DOWN the mongodb container") - os.Exit(1) - } - - os.Exit(code) -} diff --git a/api/store/mongo/system.go b/api/store/mongo/system.go deleted file mode 100644 index 3e67db2a13c..00000000000 --- a/api/store/mongo/system.go +++ /dev/null @@ -1,62 +0,0 @@ -package mongo - -import ( - "context" - "time" - - "github.com/shellhub-io/shellhub/pkg/cache" - "github.com/shellhub-io/shellhub/pkg/models" - log "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - SystemCollection = "system" - SystemCacheTTL = 60 * time.Minute -) - -func (s *Store) SystemGet(ctx context.Context) (*models.System, error) { - if system, err := cache.Get[models.System](ctx, s.cache, SystemCollection); err == nil { - log.WithField("system", system).Warn("using system from cache") - - return system, nil - } - - result := s.db.Collection(SystemCollection).FindOne(ctx, bson.M{}) - if result.Err() != nil { - return nil, FromMongoError(result.Err()) - } - - var system *models.System - if err := result.Decode(&system); err != nil { - return nil, FromMongoError(err) - } - - if err := s.cache.Set(ctx, SystemCollection, system, SystemCacheTTL); err != nil { - log.WithField("system", system).Warn("failed to set the system data on cache") - } - - return system, nil -} - -func (s *Store) SystemSet(ctx context.Context, key string, value any) error { - upsert := true - - _, err := s.db.Collection(SystemCollection).UpdateOne(ctx, bson.M{}, bson.M{ - "$set": bson.M{ - key: value, - }, - }, &options.UpdateOptions{ - Upsert: &upsert, - }) - if err != nil { - return FromMongoError(err) - } - - if err := s.cache.Delete(ctx, SystemCollection); err != nil { - log.WithField(SystemCollection, key).Warn("failed to delete system from cache") - } - - return nil -} diff --git a/api/store/mongo/tags.go b/api/store/mongo/tags.go deleted file mode 100644 index 6ea7146aee2..00000000000 --- a/api/store/mongo/tags.go +++ /dev/null @@ -1,132 +0,0 @@ -package mongo - -import ( - "context" - - "go.mongodb.org/mongo-driver/bson" - mongodriver "go.mongodb.org/mongo-driver/mongo" -) - -func (s *Store) FirewallRuleGetTags(ctx context.Context, tenant string) ([]string, int, error) { - list, err := s.db.Collection("firewall_rules").Distinct(ctx, "filter.tags", bson.M{"tenant_id": tenant}) - - tags := make([]string, len(list)) - for i, item := range list { - tags[i] = item.(string) //nolint:forcetypeassert - } - - return tags, len(tags), FromMongoError(err) -} - -func (s *Store) TagsGet(ctx context.Context, tenant string) ([]string, int, error) { - session, err := s.db.Client().StartSession() - if err != nil { - return nil, 0, err - } - defer session.EndSession(ctx) - - tags, err := session.WithTransaction(ctx, func(sessCtx mongodriver.SessionContext) (interface{}, error) { - deviceTags, _, err := s.DeviceGetTags(sessCtx, tenant) - if err != nil { - return nil, err - } - - keyTags, _, err := s.PublicKeyGetTags(sessCtx, tenant) - if err != nil { - return nil, err - } - - ruleTags, _, err := s.FirewallRuleGetTags(sessCtx, tenant) - if err != nil { - return nil, err - } - - tags := []string{} - tags = append(tags, deviceTags...) - tags = append(tags, keyTags...) - tags = append(tags, ruleTags...) - - return removeDuplicate[string](tags), nil - }) - if err != nil { - return nil, 0, FromMongoError(err) - } - - return tags.([]string), len(tags.([]string)), nil -} - -func (s *Store) FirewallRuleBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (int64, error) { - res, err := s.db.Collection("firewall_rules").UpdateMany(ctx, bson.M{"tenant_id": tenant, "filter.tags": currentTag}, bson.M{"$set": bson.M{"filter.tags.$": newTag}}) - - return res.ModifiedCount, FromMongoError(err) -} - -func (s *Store) TagsRename(ctx context.Context, tenantID string, oldTag string, newTag string) (int64, error) { - session, err := s.db.Client().StartSession() - if err != nil { - return int64(0), FromMongoError(err) - } - defer session.EndSession(ctx) - - count, err := session.WithTransaction(ctx, func(sessCtx mongodriver.SessionContext) (interface{}, error) { - devCount, err := s.DeviceBulkRenameTag(sessCtx, tenantID, oldTag, newTag) - if err != nil { - return int64(0), err - } - - keyCount, err := s.PublicKeyBulkRenameTag(sessCtx, tenantID, oldTag, newTag) - if err != nil { - return int64(0), err - } - - rulCount, err := s.FirewallRuleBulkRenameTag(sessCtx, tenantID, oldTag, newTag) - if err != nil { - return int64(0), err - } - - return devCount + keyCount + rulCount, nil - }) - if err != nil { - return int64(0), FromMongoError(err) - } - - return count.(int64), nil -} - -func (s *Store) FirewallRuleBulkDeleteTag(ctx context.Context, tenant, tag string) (int64, error) { - res, err := s.db.Collection("firewall_rules").UpdateMany(ctx, bson.M{"tenant_id": tenant}, bson.M{"$pull": bson.M{"filter.tags": tag}}) - - return res.ModifiedCount, FromMongoError(err) -} - -func (s *Store) TagsDelete(ctx context.Context, tenantID string, tag string) (int64, error) { - session, err := s.db.Client().StartSession() - if err != nil { - return int64(0), FromMongoError(err) - } - defer session.EndSession(ctx) - - count, err := session.WithTransaction(ctx, func(sessCtx mongodriver.SessionContext) (interface{}, error) { - devCount, err := s.DeviceBulkDeleteTag(sessCtx, tenantID, tag) - if err != nil { - return int64(0), err - } - - keyCount, err := s.PublicKeyBulkDeleteTag(sessCtx, tenantID, tag) - if err != nil { - return int64(0), err - } - - rulCount, err := s.FirewallRuleBulkDeleteTag(sessCtx, tenantID, tag) - if err != nil { - return int64(0), err - } - - return devCount + keyCount + rulCount, nil - }) - if err != nil { - return int64(0), FromMongoError(err) - } - - return count.(int64), nil -} diff --git a/api/store/mongo/tags_test.go b/api/store/mongo/tags_test.go deleted file mode 100644 index be9fd44d5b3..00000000000 --- a/api/store/mongo/tags_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package mongo_test - -import ( - "context" - "sort" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTagsGet(t *testing.T) { - type Expected struct { - tags []string - len int - err error - } - - cases := []struct { - description string - tenant string - fixtures []string - expected Expected - }{ - { - description: "succeeds when tag is found", - tenant: "00000000-0000-4000-0000-000000000000", - fixtures: []string{fixturePublicKeys, fixtureFirewallRules, fixtureDevices}, - expected: Expected{ - tags: []string{"tag-1"}, - len: 1, - err: nil, - }, - }, - } - - // Due to the non-deterministic order of applying fixtures when dealing with multiple datasets, - // we ensure that both the expected and result arrays are correctly sorted. - sort := func(tags []string) { - sort.Slice(tags, func(i, j int) bool { - return tags[i] < tags[j] - }) - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - tags, count, err := s.TagsGet(ctx, tc.tenant) - - sort(tc.expected.tags) - sort(tags) - - assert.Equal(t, tc.expected, Expected{tags: tags, len: count, err: err}) - }) - } -} - -func TestTagsRename(t *testing.T) { - type Expected struct { - count int64 - err error - } - - cases := []struct { - description string - tenant string - oldTag string - newTag string - fixtures []string - expected Expected - }{ - { - description: "succeeds when tag is found", - tenant: "00000000-0000-4000-0000-000000000000", - oldTag: "tag-1", - newTag: "edited-tag", - fixtures: []string{fixturePublicKeys, fixtureFirewallRules, fixtureDevices}, - expected: Expected{ - count: 6, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - count, err := s.TagsRename(ctx, tc.tenant, tc.oldTag, tc.newTag) - assert.Equal(t, tc.expected, Expected{count, err}) - }) - } -} - -func TestTagsDelete(t *testing.T) { - type Expected struct { - count int64 - err error - } - - cases := []struct { - description string - tenant string - tag string - fixtures []string - expected Expected - }{ - { - description: "succeeds when tag is found", - tenant: "00000000-0000-4000-0000-000000000000", - tag: "tag-1", - fixtures: []string{fixturePublicKeys, fixtureFirewallRules, fixtureDevices}, - expected: Expected{ - count: 6, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - count, err := s.TagsDelete(ctx, tc.tenant, tc.tag) - assert.Equal(t, tc.expected, Expected{count, err}) - }) - } -} diff --git a/api/store/mongo/transaction.go b/api/store/mongo/transaction.go deleted file mode 100644 index 6179120bf87..00000000000 --- a/api/store/mongo/transaction.go +++ /dev/null @@ -1,27 +0,0 @@ -package mongo - -import ( - "context" - - "github.com/shellhub-io/shellhub/api/store" - "go.mongodb.org/mongo-driver/mongo" -) - -func (s *Store) WithTransaction(ctx context.Context, cb store.TransactionCb) error { - session, err := s.db.Client().StartSession() - if err != nil { - return store.ErrStartTransactionFailed - } - defer session.EndSession(ctx) - - // The [session.WithTransaction] function expects a callback that returns an [interface{}] and an error. - // To meet this requirement, we need to wrap our cb so that it always returns nil as the [interface{}], - // along with the error from our callback function. - fn := func(ctx mongo.SessionContext) (interface{}, error) { - return nil, cb(ctx) - } - - _, err = session.WithTransaction(ctx, fn) - - return err -} diff --git a/api/store/mongo/transaction_test.go b/api/store/mongo/transaction_test.go deleted file mode 100644 index e9816c91fee..00000000000 --- a/api/store/mongo/transaction_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package mongo_test - -import ( - "context" - "errors" - "testing" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" -) - -func TestWithTransaction(t *testing.T) { - cases := []struct { - description string - callback store.TransactionCb - expected error - }{ - { - description: "should abort changes", - callback: func(ctx context.Context) error { - if _, err := db.Collection("users").InsertOne(ctx, bson.M{"_id": 1, "name": "John Doe"}); err != nil { - return err - } - - return errors.New("error") - }, - expected: errors.New("error"), - }, - { - description: "should commit changes", - callback: func(ctx context.Context) error { - if _, err := db.Collection("users").InsertOne(ctx, bson.M{"_id": 1, "name": "John Doe"}); err != nil { - return err - } - - return nil - }, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - ctx := context.Background() - - tt.Cleanup(func() { - assert.NoError(tt, srv.Reset()) - }) - - if err := s.WithTransaction(ctx, tc.callback); err != nil { - require.Equal(tt, err, tc.expected) - target := make(map[string]interface{}) - require.Error(tt, db.Collection("users").FindOne(ctx, bson.M{"_id": 1}).Decode(&target)) - _, ok := target["name"] - require.Equal(tt, false, ok) - } else { - target := make(map[string]interface{}) - require.NoError(tt, db.Collection("users").FindOne(ctx, bson.M{"_id": 1}).Decode(&target)) - require.Equal(tt, "John Doe", target["name"]) - } - }) - } -} diff --git a/api/store/mongo/user.go b/api/store/mongo/user.go deleted file mode 100644 index 7db6ef36b37..00000000000 --- a/api/store/mongo/user.go +++ /dev/null @@ -1,297 +0,0 @@ -package mongo - -import ( - "context" - "time" - - "github.com/shellhub-io/shellhub/api/pkg/gateway" - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/api/store/mongo/queries" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func (s *Store) UserList(ctx context.Context, paginator query.Paginator, filters query.Filters) ([]models.User, int, error) { - query := []bson.M{} - - if tenant := gateway.TenantFromContext(ctx); tenant != nil { - query = append(query, bson.M{ - "$match": bson.M{ - "tenant_id": tenant.ID, - }, - }) - } - - query = append(query, []bson.M{ - { - "$addFields": bson.M{ - "user_id": bson.M{"$toString": "$_id"}, - }, - }, - { - "$lookup": bson.M{ - "from": "namespaces", - "localField": "user_id", - "foreignField": "owner", - "as": "namespaces", - }, - }, - { - "$addFields": bson.M{ - "namespaces": bson.M{"$size": "$namespaces"}, - }, - }, - }...) - - queryMatch, err := queries.FromFilters(&filters) - if err != nil { - return nil, 0, FromMongoError(err) - } - query = append(query, queryMatch...) - - queryCount := query - queryCount = append(queryCount, bson.M{"$count": "count"}) - count, err := AggregateCount(ctx, s.db.Collection("users"), queryCount) - if err != nil { - return nil, 0, FromMongoError(err) - } - - query = append(query, queries.FromPaginator(&paginator)...) - - users := make([]models.User, 0) - cursor, err := s.db.Collection("users").Aggregate(ctx, query) - if err != nil { - return nil, 0, FromMongoError(err) - } - defer cursor.Close(ctx) - - for cursor.Next(ctx) { - user := new(models.User) - err = cursor.Decode(&user) - if err != nil { - return nil, 0, FromMongoError(err) - } - - users = append(users, *user) - } - - return users, count, FromMongoError(err) -} - -func (s *Store) UserCreate(ctx context.Context, user *models.User) (string, error) { - user.CreatedAt = time.Now() - user.LastLogin = time.Time{} - - r, err := s.db.Collection("users").InsertOne(ctx, user) - if err != nil { - return "", FromMongoError(err) - } - - return r.InsertedID.(primitive.ObjectID).Hex(), nil -} - -func (s *Store) UserCreateInvited(ctx context.Context, email string) (string, error) { - user := structToBson(models.User{CreatedAt: clock.Now(), Status: models.UserStatusInvited, UserData: models.UserData{Email: email}}) - sanitizeBson(user) - - r, err := s.db.Collection("users").InsertOne(ctx, user) - if err != nil { - return "", FromMongoError(err) - } - - return r.InsertedID.(primitive.ObjectID).Hex(), nil -} - -func (s *Store) UserGetByUsername(ctx context.Context, username string) (*models.User, error) { - user := new(models.User) - - if err := s.db.Collection("users").FindOne(ctx, bson.M{"username": username}).Decode(&user); err != nil { - return nil, FromMongoError(err) - } - - return user, nil -} - -func (s *Store) UserGetByEmail(ctx context.Context, email string) (*models.User, error) { - user := new(models.User) - - if err := s.db.Collection("users").FindOne(ctx, bson.M{"email": email}).Decode(&user); err != nil { - return nil, FromMongoError(err) - } - - return user, nil -} - -func (s *Store) UserGetByID(ctx context.Context, id string, ns bool) (*models.User, int, error) { - user := new(models.User) - objID, err := primitive.ObjectIDFromHex(id) - if err != nil { - return nil, 0, err - } - - if err := s.db.Collection("users").FindOne(ctx, bson.M{"_id": objID}).Decode(&user); err != nil { - return nil, 0, FromMongoError(err) - } - - if !ns { - return user, 0, nil - } - - nss := struct { - NamespacesOwned int `bson:"namespacesOwned"` - }{} - - query := []bson.M{ - { - "$match": bson.M{ - "_id": objID, - }, - }, - { - "$addFields": bson.M{ - "_id": bson.M{ - "$toString": "$_id", - }, - }, - }, - { - "$lookup": bson.M{ - "from": "namespaces", - "localField": "_id", - "foreignField": "owner", - "as": "ns", - }, - }, - { - "$addFields": bson.M{ - "namespacesOwned": bson.M{ - "$size": "$ns", - }, - }, - }, - { - "$project": bson.M{ - "namespacesOwned": 1, - "_id": 0, - }, - }, - } - - cursor, err := s.db.Collection("users").Aggregate(ctx, query) - if err != nil { - return nil, 0, FromMongoError(err) - } - - defer cursor.Close(ctx) - - if !cursor.Next(ctx) { - return nil, 0, FromMongoError(err) - } - - if err = cursor.Decode(&nss); err != nil { - return nil, 0, FromMongoError(err) - } - - return user, nss.NamespacesOwned, nil -} - -func (s *Store) UserConflicts(ctx context.Context, target *models.UserConflicts) ([]string, bool, error) { - pipeline := []bson.M{ - { - "$match": bson.M{ - "$or": []bson.M{ - {"email": target.Email}, - {"username": target.Username}, - }, - }, - }, - } - - cursor, err := s.db.Collection("users").Aggregate(ctx, pipeline) - if err != nil { - return nil, false, FromMongoError(err) - } - defer cursor.Close(ctx) - - user := new(models.UserConflicts) - conflicts := make([]string, 0) - for cursor.Next(ctx) { - if err := cursor.Decode(&user); err != nil { - return nil, false, FromMongoError(err) - } - - if user.Username == target.Username { - conflicts = append(conflicts, "username") - } - - if user.Email == target.Email { - conflicts = append(conflicts, "email") - } - } - - return conflicts, len(conflicts) > 0, nil -} - -func (s *Store) UserUpdate(ctx context.Context, id string, changes *models.UserChanges) error { - objID, err := primitive.ObjectIDFromHex(id) - if err != nil { - return FromMongoError(err) - } - - r, err := s.db.Collection("users").UpdateOne(ctx, bson.M{"_id": objID}, bson.M{"$set": changes}) - if err != nil { - return FromMongoError(err) - } - - if r.MatchedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) UserDelete(ctx context.Context, id string) error { - objID, err := primitive.ObjectIDFromHex(id) - if err != nil { - return FromMongoError(err) - } - - user, err := s.db.Collection("users").DeleteOne(ctx, bson.M{"_id": objID}) - if err != nil { - return FromMongoError(err) - } - - if user.DeletedCount < 1 { - return store.ErrNoDocuments - } - - return nil -} - -func (s *Store) UserGetInfo(ctx context.Context, id string) (*models.UserInfo, error) { - cursor, err := s.db.Collection("namespaces").Find(ctx, bson.M{"members": bson.M{"$elemMatch": bson.M{"id": id}}}) - if err != nil { - return nil, FromMongoError(err) - } - defer cursor.Close(ctx) - - userInfo := &models.UserInfo{} - - for cursor.Next(ctx) { - ns := new(models.Namespace) - if err := cursor.Decode(ns); err != nil { - return nil, FromMongoError(err) - } - - if ns.Owner == id { - userInfo.OwnedNamespaces = append(userInfo.OwnedNamespaces, *ns) - } else { - userInfo.AssociatedNamespaces = append(userInfo.AssociatedNamespaces, *ns) - } - } - - return userInfo, nil -} diff --git a/api/store/mongo/user_test.go b/api/store/mongo/user_test.go deleted file mode 100644 index 55d6f5995e5..00000000000 --- a/api/store/mongo/user_test.go +++ /dev/null @@ -1,729 +0,0 @@ -package mongo_test - -import ( - "context" - "sort" - "testing" - "time" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" - "github.com/shellhub-io/shellhub/pkg/clock" - clockmock "github.com/shellhub-io/shellhub/pkg/clock/mocks" - "github.com/shellhub-io/shellhub/pkg/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func TestUserList(t *testing.T) { - type Expected struct { - users []models.User - count int - err error - } - - cases := []struct { - description string - page query.Paginator - filters query.Filters - fixtures []string - expected Expected - }{ - { - description: "succeeds when users are found", - page: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{}, - fixtures: []string{fixtureUsers}, - expected: Expected{ - users: []models.User{ - { - ID: "507f1f77bcf86cd799439011", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "john doe", - Username: "john_doe", - Email: "john.doe@test.com", - }, - MaxNamespaces: 0, - Password: models.UserPassword{ - Hash: "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - }, - }, - { - ID: "608f32a2c7351f001f6475e0", - CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "Jane Smith", - Username: "jane_smith", - Email: "jane.smith@test.com", - }, - MaxNamespaces: 3, - Password: models.UserPassword{ - Hash: "a0b8c29f4c8d57e542f5e81d35ebe801fd27f569f116fe670e8962d798512a1d", - }, - }, - { - ID: "709f45b5e812c1002f3a67e7", - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "Bob Johnson", - Username: "bob_johnson", - Email: "bob.johnson@test.com", - }, - MaxNamespaces: 10, - Password: models.UserPassword{ - Hash: "5f3b3956a1a150b73e6b27e674f27d7aeb01ab1a40c179c3e1aa6026a36655a2", - }, - }, - { - ID: "80fdcea1d7299c002f3a67e8", - CreatedAt: time.Date(2023, 1, 4, 12, 0, 0, 0, time.UTC), - EmailMarketing: false, - Status: models.UserStatusNotConfirmed, - UserData: models.UserData{ - Name: "Alex Rodriguez", - Username: "alex_rodriguez", - Email: "alex.rodriguez@test.com", - }, - MaxNamespaces: 3, - Password: models.UserPassword{ - Hash: "c5093eb98678c7a3324825b84c6b67c1127b93786482ddbbd356e67e29b2763f", - }, - }, - { - ID: "6509e169ae6144b2f56bf288", - CreatedAt: time.Date(2023, 1, 5, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 5, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "Maria Garcia", - Email: "maria.garcia@test.com", - Username: "maria_garcia", - }, - MaxNamespaces: 5, - Password: models.UserPassword{ - Hash: "c2301b2b7e872843b473d2c301e4fb2e6e9f27f2e7a1b6ad44a3b2c97f1670b3", - }, - }, - }, - count: 5, - err: nil, - }, - }, - { - description: "succeeds with filters", - page: query.Paginator{Page: -1, PerPage: -1}, - filters: query.Filters{ - Data: []query.Filter{ - { - Type: "property", - Params: &query.FilterProperty{ - Name: "max_namespaces", - Operator: "gt", - Value: "3", - }, - }, - }, - }, - fixtures: []string{fixtureUsers}, - expected: Expected{ - users: []models.User{ - { - ID: "709f45b5e812c1002f3a67e7", - CreatedAt: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 3, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "Bob Johnson", - Username: "bob_johnson", - Email: "bob.johnson@test.com", - }, - MaxNamespaces: 10, - Password: models.UserPassword{ - Hash: "5f3b3956a1a150b73e6b27e674f27d7aeb01ab1a40c179c3e1aa6026a36655a2", - }, - }, - { - ID: "6509e169ae6144b2f56bf288", - CreatedAt: time.Date(2023, 1, 5, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 5, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "Maria Garcia", - Email: "maria.garcia@test.com", - Username: "maria_garcia", - }, - MaxNamespaces: 5, - Password: models.UserPassword{ - Hash: "c2301b2b7e872843b473d2c301e4fb2e6e9f27f2e7a1b6ad44a3b2c97f1670b3", - }, - }, - }, - count: 2, - err: nil, - }, - }, - } - - // Due to the non-deterministic order of applying fixtures when dealing with multiple datasets, - // we ensure that both the expected and result arrays are correctly sorted. - sort := func(users []models.User) { - sort.Slice(users, func(i, j int) bool { - return users[i].ID < users[j].ID - }) - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - users, count, err := s.UserList(ctx, tc.page, tc.filters) - - sort(tc.expected.users) - sort(users) - - assert.Equal(t, tc.expected, Expected{users: users, count: count, err: err}) - }) - } -} - -func TestUserCreate(t *testing.T) { - cases := []struct { - description string - user *models.User - fixtures []string - expected error - }{ - { - description: "succeeds when data is valid", - user: &models.User{ - UserData: models.UserData{ - Name: "john doe", - Username: "john_doe", - Email: "john.doe@test.com", - }, - Password: models.UserPassword{ - Hash: "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - }, - }, - fixtures: []string{}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - insertedID, err := s.UserCreate(ctx, tc.user) - assert.Equal(t, tc.expected, err) - assert.NotEmpty(t, insertedID) - }) - } -} - -func TestStore_UserCreateInvited(t *testing.T) { - now := time.Now() - - cases := []struct { - description string - email string - fixtures []string - mocks func() - }{ - { - description: "succeeds", - email: "john.doe@test.com", - fixtures: []string{}, - mocks: func() { - mockClock := new(clockmock.Clock) - clock.DefaultBackend = mockClock - mockClock.On("Now").Return(now) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(tt *testing.T) { - ctx := context.Background() - tc.mocks() - - require.NoError(tt, srv.Apply(tc.fixtures...)) - tt.Cleanup(func() { - require.NoError(tt, srv.Reset()) - }) - - insertedID, err := s.UserCreateInvited(ctx, tc.email) - require.NoError(tt, err) - require.NotEmpty(tt, insertedID) - - objID, _ := primitive.ObjectIDFromHex(insertedID) - - tmpUser := make(map[string]interface{}) - require.NoError(tt, db.Collection("users").FindOne(ctx, bson.M{"_id": objID}).Decode(&tmpUser)) - require.Equal( - tt, - map[string]interface{}{ - "_id": objID, - "created_at": primitive.NewDateTimeFromTime(now), - "last_login": primitive.NewDateTimeFromTime(time.Time{}), - "origin": nil, - "external_id": nil, - "status": models.UserStatusInvited.String(), - "max_namespaces": nil, - "name": nil, - "username": nil, - "email": "john.doe@test.com", - "recovery_email": nil, - "email_marketing": nil, - "password": nil, - "preferences": map[string]interface{}{"preferred_namespace": nil, "auth_methods": nil}, - "mfa": map[string]interface{}{"enabled": nil, "secret": nil, "recovery_codes": nil}, - }, - tmpUser, - ) - }) - } -} - -func TestUserGetByUsername(t *testing.T) { - type Expected struct { - user *models.User - err error - } - - cases := []struct { - description string - username string - fixtures []string - expected Expected - }{ - { - description: "fails when user is not found", - username: "nonexistent", - fixtures: []string{fixtureUsers}, - expected: Expected{ - user: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when user is found", - username: "john_doe", - fixtures: []string{fixtureUsers}, - expected: Expected{ - user: &models.User{ - ID: "507f1f77bcf86cd799439011", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "john doe", - Username: "john_doe", - Email: "john.doe@test.com", - }, - MaxNamespaces: 0, - Password: models.UserPassword{ - Hash: "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - }, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user, err := s.UserGetByUsername(ctx, tc.username) - assert.Equal(t, tc.expected, Expected{user: user, err: err}) - }) - } -} - -func TestUserGetByEmail(t *testing.T) { - type Expected struct { - user *models.User - err error - } - - cases := []struct { - description string - email string - fixtures []string - expected Expected - }{ - { - description: "fails when email is not found", - email: "nonexistent", - fixtures: []string{fixtureUsers}, - expected: Expected{ - user: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when email is found", - email: "john.doe@test.com", - fixtures: []string{fixtureUsers}, - expected: Expected{ - user: &models.User{ - ID: "507f1f77bcf86cd799439011", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "john doe", - Username: "john_doe", - Email: "john.doe@test.com", - }, - MaxNamespaces: 0, - Password: models.UserPassword{ - Hash: "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - }, - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user, err := s.UserGetByEmail(ctx, tc.email) - assert.Equal(t, tc.expected, Expected{user: user, err: err}) - }) - } -} - -func TestUserGetByID(t *testing.T) { - type Expected struct { - user *models.User - ns int - err error - } - - cases := []struct { - description string - id string - ns bool - fixtures []string - expected Expected - }{ - { - description: "fails when user is not found", - id: "507f1f77bcf86cd7994390bb", - fixtures: []string{fixtureUsers, fixtureNamespaces}, - expected: Expected{ - user: nil, - ns: 0, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when user is found with ns equal false", - id: "507f1f77bcf86cd799439011", - ns: false, - fixtures: []string{fixtureUsers, fixtureNamespaces}, - expected: Expected{ - user: &models.User{ - ID: "507f1f77bcf86cd799439011", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "john doe", - Username: "john_doe", - Email: "john.doe@test.com", - }, - MaxNamespaces: 0, - Password: models.UserPassword{ - Hash: "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - }, - }, - ns: 0, - err: nil, - }, - }, - { - description: "succeeds when user is found with ns equal true", - id: "507f1f77bcf86cd799439011", - ns: true, - fixtures: []string{fixtureUsers, fixtureNamespaces}, - expected: Expected{ - user: &models.User{ - ID: "507f1f77bcf86cd799439011", - CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - LastLogin: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - EmailMarketing: true, - Status: models.UserStatusConfirmed, - UserData: models.UserData{ - Name: "john doe", - Username: "john_doe", - Email: "john.doe@test.com", - }, - MaxNamespaces: 0, - Password: models.UserPassword{ - Hash: "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - }, - }, - ns: 1, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - user, ns, err := s.UserGetByID(ctx, tc.id, tc.ns) - assert.Equal(t, tc.expected, Expected{user: user, ns: ns, err: err}) - }) - } -} - -func TestUserConflicts(t *testing.T) { - type Expected struct { - conflicts []string - ok bool - err error - } - - cases := []struct { - description string - target *models.UserConflicts - fixtures []string - expected Expected - }{ - { - description: "no conflicts when target is empty", - target: &models.UserConflicts{}, - fixtures: []string{fixtureUsers}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "no conflicts with non existing email", - target: &models.UserConflicts{Email: "other@test.com"}, - fixtures: []string{fixtureUsers}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "no conflicts with non existing username", - target: &models.UserConflicts{Username: "other"}, - fixtures: []string{fixtureUsers}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "no conflicts with non existing username and email", - target: &models.UserConflicts{Email: "other@test.com", Username: "other"}, - fixtures: []string{fixtureUsers}, - expected: Expected{[]string{}, false, nil}, - }, - { - description: "conflict detected with existing email", - target: &models.UserConflicts{Email: "john.doe@test.com"}, - fixtures: []string{fixtureUsers}, - expected: Expected{[]string{"email"}, true, nil}, - }, - { - description: "conflict detected with existing username", - target: &models.UserConflicts{Username: "john_doe"}, - fixtures: []string{fixtureUsers}, - expected: Expected{[]string{"username"}, true, nil}, - }, - { - description: "conflict detected with existing username and email", - target: &models.UserConflicts{Email: "john.doe@test.com", Username: "john_doe"}, - fixtures: []string{fixtureUsers}, - expected: Expected{[]string{"username", "email"}, true, nil}, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - conflicts, ok, err := s.UserConflicts(ctx, tc.target) - require.Equal(t, tc.expected, Expected{conflicts, ok, err}) - }) - } -} - -func TestUserUpdate(t *testing.T) { - type Expected struct { - changes *models.UserChanges - err error - } - - cases := []struct { - description string - id string - changes *models.UserChanges - fixtures []string - expected Expected - }{ - { - description: "fails when user is not found", - id: "000000000000000000000000", - changes: &models.UserChanges{}, - fixtures: []string{fixtureUsers}, - expected: Expected{ - changes: nil, - err: store.ErrNoDocuments, - }, - }, - { - description: "succeeds when updating string values", - id: "507f1f77bcf86cd799439011", - changes: &models.UserChanges{ - Name: "New Value", - Email: "new.value@test.com", - Status: models.UserStatusNotConfirmed, - }, - fixtures: []string{fixtureUsers}, - expected: Expected{ - changes: &models.UserChanges{ - LastLogin: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "New Value", - Email: "new.value@test.com", - Username: "john_doe", - Status: models.UserStatusNotConfirmed, - Password: "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - }, - err: nil, - }, - }, - { - description: "succeeds when updating time values", - id: "507f1f77bcf86cd799439011", - changes: &models.UserChanges{ - LastLogin: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC), - }, - fixtures: []string{fixtureUsers}, - expected: Expected{ - changes: &models.UserChanges{ - LastLogin: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC), - Name: "john doe", - Email: "john.doe@test.com", - Username: "john_doe", - Status: models.UserStatusConfirmed, - Password: "fcf730b6d95236ecd3c9fc2d92d7b6b2bb061514961aec041d6c7a7192f592e4", - }, - err: nil, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - require.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { require.NoError(t, srv.Reset()) }) - - if err := s.UserUpdate(ctx, tc.id, tc.changes); err != nil { - require.Equal(t, tc.expected.err, err) - - return - } - - id, err := primitive.ObjectIDFromHex(tc.id) - require.NoError(t, err) - - user := new(models.User) - require.NoError(t, db.Collection("users").FindOne(ctx, bson.M{"_id": id}).Decode(user)) - - // Ensures that only the expected attributes have been updated. - require.Equal(t, tc.expected.changes.LastLogin, user.LastLogin) - require.Equal(t, tc.expected.changes.Name, user.Name) - require.Equal(t, tc.expected.changes.Email, user.Email) - require.Equal(t, tc.expected.changes.Status, user.Status) - require.Equal(t, tc.expected.changes.Username, user.Username) - require.Equal(t, tc.expected.changes.Password, user.Password.Hash) - }) - } -} - -func TestUserDelete(t *testing.T) { - cases := []struct { - description string - id string - fixtures []string - expected error - }{ - { - description: "fails when user is not found", - id: "000000000000000000000000", - fixtures: []string{fixtureUsers}, - expected: store.ErrNoDocuments, - }, - { - description: "succeeds when user is found", - id: "507f1f77bcf86cd799439011", - fixtures: []string{fixtureUsers}, - expected: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() - - assert.NoError(t, srv.Apply(tc.fixtures...)) - t.Cleanup(func() { - assert.NoError(t, srv.Reset()) - }) - - err := s.UserDelete(ctx, tc.id) - assert.Equal(t, tc.expected, err) - }) - } -} diff --git a/api/store/mongo/utils.go b/api/store/mongo/utils.go deleted file mode 100644 index 83a3e8403b4..00000000000 --- a/api/store/mongo/utils.go +++ /dev/null @@ -1,103 +0,0 @@ -package mongo - -import ( - "context" - "io" - "reflect" - - "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/errors" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" -) - -// AggregateCount takes a pipeline and count the results. -func AggregateCount(ctx context.Context, coll *mongo.Collection, pipeline []bson.M) (int, error) { - resp := struct { - Count int `bson:"count"` - }{} - - cursor, err := coll.Aggregate(ctx, pipeline) - if err != nil { - return 0, err - } - - defer cursor.Close(ctx) - - if !cursor.Next(ctx) { - return 0, nil - } - - if err = cursor.Decode(&resp); err != nil { - return 0, err - } - - return resp.Count, nil -} - -// ErrLayer is an error level. Each error defined at this level, is container to it. -// ErrLayer is the errors' level for mongo's error. -const ErrLayer = "mongo" - -// ErrMongo is the error for any unknown mongo error. -var ErrMongo = errors.New("mongo error", ErrLayer, 1) - -func FromMongoError(err error) error { - switch { - case err == mongo.ErrNoDocuments, err == io.EOF: - return store.ErrNoDocuments - case err == primitive.ErrInvalidHex: - return store.ErrInvalidHex - case mongo.IsDuplicateKeyError(err): - return store.ErrDuplicate - default: - if err == nil { - return nil - } - - return errors.Wrap(ErrMongo, err) - } -} - -// removeDuplicate removes duplicate elements from a slice while maintaining the original order. -func removeDuplicate[T comparable](slice []T) []T { - allKeys := make(map[T]bool) - list := []T{} - for _, item := range slice { - if _, value := allKeys[item]; !value { - allKeys[item] = true - list = append(list, item) - } - } - - return list -} - -// structToBson converts a struct to it's bson representation. -func structToBson[T any](v T) primitive.M { - data, err := bson.Marshal(v) - if err != nil { - panic(err) - } - - doc := make(primitive.M) - if err := bson.Unmarshal(data, &doc); err != nil { - panic(err) - } - - return doc -} - -// sanitizeBson recursively sanitizes a bson, setting zero-value fields to nil -func sanitizeBson(data primitive.M) { - for k, v := range data { - if reflect.TypeOf(v) == reflect.TypeOf(primitive.M{}) { - sanitizeBson(v.(primitive.M)) - } else { - if v != nil && reflect.DeepEqual(v, reflect.Zero(reflect.TypeOf(v)).Interface()) { - data[k] = nil - } - } - } -} From 25d8b30a865103e03b92247bd1ac3d339c85df44 Mon Sep 17 00:00:00 2001 From: Heitor Danilo Date: Mon, 12 May 2025 00:11:39 -0300 Subject: [PATCH 3/3] wip --- api/Dockerfile | 2 - api/go.mod | 56 +-- api/go.sum | 153 ++++---- api/routes/device.go | 79 ++-- api/routes/nsadm.go | 9 + api/routes/sshkeys.go | 23 +- api/server.go | 3 +- api/services/api-key.go | 39 +- api/services/auth.go | 195 ++++------ api/services/device.go | 356 +++++++----------- api/services/device_tags.go | 52 +-- api/services/member.go | 94 +++-- api/services/namespace.go | 112 +++--- api/services/setup.go | 29 +- api/services/sshkeys.go | 64 ++-- api/services/sshkeys_tags.go | 112 ------ api/services/system.go | 11 +- api/services/tags.go | 5 +- api/services/task.go | 5 +- api/services/user.go | 43 ++- api/store/api_key.go | 26 +- api/store/device.go | 48 +-- api/store/device_tags.go | 33 -- api/store/namespace.go | 88 ++--- api/store/pg/api-key.go | 81 +++- api/store/pg/dbtest/dbtest.go | 54 +++ api/store/pg/dbtest/fixtures.go | 12 + api/store/pg/dbtest/fixtures/users.yaml | 18 + api/store/pg/device.go | 235 ++++++------ api/store/pg/entity/api-key.go | 48 +++ api/store/pg/entity/device.go | 110 ++++++ api/store/pg/entity/entity.go | 13 + api/store/pg/entity/membership.go | 44 +++ api/store/pg/entity/namespace.go | 76 ++++ api/store/pg/entity/private-key.go | 35 ++ api/store/pg/entity/public-key.go | 51 +++ api/store/pg/entity/user.go | 106 ++++++ api/store/pg/metrics.go | 94 ++++- .../migrations/001_create_namespaces_table.go | 52 +++ .../pg/migrations/002_create_users_table.go | 73 ++++ .../003_create_memberships_table.go | 59 +++ .../pg/migrations/004_create_devices_table.go | 105 ++++++ .../005_create_private_keys_table.go | 39 ++ .../pg/migrations/006_create_api_keys.go | 47 +++ .../pg/migrations/007_create_public_keys.go | 46 +++ api/store/pg/migrations/migrations.go | 15 + api/store/pg/namespace.go | 130 +++++-- api/store/pg/options/log.go | 39 ++ api/store/pg/options/migrate.go | 61 +++ api/store/pg/options/with-fixtures.go | 31 ++ api/store/pg/pg.go | 19 +- api/store/pg/pg_test.go | 85 +++++ api/store/pg/private-key.go | 22 +- api/store/pg/public-key.go | 81 ++-- api/store/pg/query-options.go | 276 +++++++++++++- api/store/pg/session.go | 24 +- api/store/pg/system.go | 4 +- api/store/pg/tags.go | 6 +- api/store/pg/transaction.go | 2 +- api/store/pg/user.go | 101 +++-- api/store/pg/user_test.go | 355 +++++++++++++++++ api/store/pg/utils.go | 13 + api/store/publickey.go | 17 +- api/store/publickey_tags.go | 35 -- api/store/query-options.go | 16 +- api/store/store.go | 2 - api/store/user.go | 44 +-- cli/go.mod | 22 +- cli/go.sum | 130 ++++++- cli/main.go | 3 +- cli/services/namespaces.go | 36 +- cli/services/users.go | 70 ++-- docker-compose.yml | 2 - pkg/api/requests/namespace.go | 4 +- pkg/api/requests/publickey.go | 8 + pkg/models/device.go | 4 +- pkg/models/member.go | 5 +- pkg/models/namespace.go | 60 +-- pkg/models/privatekey.go | 1 + pkg/models/publickey.go | 2 + pkg/models/user.go | 73 +--- pkg/uuid/uuid.go | 4 +- ssh/session/session.go | 12 +- 83 files changed, 3381 insertions(+), 1468 deletions(-) delete mode 100644 api/store/device_tags.go create mode 100644 api/store/pg/dbtest/dbtest.go create mode 100644 api/store/pg/dbtest/fixtures.go create mode 100644 api/store/pg/dbtest/fixtures/users.yaml create mode 100644 api/store/pg/entity/api-key.go create mode 100644 api/store/pg/entity/device.go create mode 100644 api/store/pg/entity/entity.go create mode 100644 api/store/pg/entity/membership.go create mode 100644 api/store/pg/entity/namespace.go create mode 100644 api/store/pg/entity/private-key.go create mode 100644 api/store/pg/entity/public-key.go create mode 100644 api/store/pg/entity/user.go create mode 100644 api/store/pg/migrations/001_create_namespaces_table.go create mode 100644 api/store/pg/migrations/002_create_users_table.go create mode 100644 api/store/pg/migrations/003_create_memberships_table.go create mode 100644 api/store/pg/migrations/004_create_devices_table.go create mode 100644 api/store/pg/migrations/005_create_private_keys_table.go create mode 100644 api/store/pg/migrations/006_create_api_keys.go create mode 100644 api/store/pg/migrations/007_create_public_keys.go create mode 100644 api/store/pg/migrations/migrations.go create mode 100644 api/store/pg/options/log.go create mode 100644 api/store/pg/options/migrate.go create mode 100644 api/store/pg/options/with-fixtures.go create mode 100644 api/store/pg/pg_test.go create mode 100644 api/store/pg/user_test.go delete mode 100644 api/store/publickey_tags.go diff --git a/api/Dockerfile b/api/Dockerfile index 78842ea36ab..48bb1fa1b3e 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -54,7 +54,6 @@ COPY ./api/entrypoint-dev.sh /entrypoint.sh WORKDIR $GOPATH/src/github.com/shellhub-io/shellhub/api RUN mkdir -p /templates -RUN mkdir -p /migrations COPY ./install.sh /templates/install.sh @@ -68,7 +67,6 @@ RUN apk add curl COPY --from=builder /go/src/github.com/shellhub-io/shellhub/api/api /api RUN mkdir -p /templates -RUN mkdir -p /migrations COPY ./install.sh /templates/install.sh diff --git a/api/go.mod b/api/go.mod index fdf25ab493d..0513cf226b3 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,8 +1,8 @@ module github.com/shellhub-io/shellhub/api -go 1.22.4 +go 1.23.0 -toolchain go1.22.5 +toolchain go1.24.1 require ( github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 @@ -11,25 +11,24 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/jackc/pgx/v5 v5.7.4 github.com/labstack/echo/v4 v4.13.3 - github.com/labstack/gommon v0.4.2 - github.com/pkg/errors v0.9.1 + github.com/oiime/logrusbun v0.1.2-0.20241011112815-4df3a0fb0e11 github.com/shellhub-io/mongotest v0.0.0-20230928124937-e33b07010742 github.com/shellhub-io/shellhub v0.13.4 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.9.1 - github.com/square/mongo-lock v0.0.0-20230808145049-cfcf499f6bf0 github.com/stretchr/testify v1.10.0 + github.com/testcontainers/testcontainers-go v0.37.0 github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.37.0 github.com/uptrace/bun v1.2.11 + github.com/uptrace/bun/dbfixture v1.2.11 github.com/uptrace/bun/dialect/pgdialect v1.2.11 - github.com/xakep666/mongo-migrate v0.3.2 - go.mongodb.org/mongo-driver v1.17.3 - golang.org/x/crypto v0.33.0 - golang.org/x/sync v0.11.0 + golang.org/x/crypto v0.37.0 + golang.org/x/text v0.24.0 ) require ( - dario.cat/mergo v1.0.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/adhocore/gronx v1.8.1 // indirect @@ -40,18 +39,19 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/connesc/cipherio v0.2.1 // indirect - github.com/containerd/containerd v1.7.27 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/docker v28.0.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect + github.com/ebitengine/purego v0.8.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -76,9 +76,10 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/pgzip v1.2.5 // indirect + github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.2.2 // indirect github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mholt/archiver/v4 v4.0.0-alpha.8 // indirect @@ -92,22 +93,21 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/oschwald/geoip2-golang v1.8.0 // indirect github.com/oschwald/maxminddb-golang v1.10.0 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/redis/go-redis/v9 v9.0.3 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sethvargo/go-envconfig v0.9.0 // indirect - github.com/shirou/gopsutil/v3 v3.24.3 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.1 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/testcontainers/testcontainers-go v0.35.0 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect @@ -125,16 +125,22 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect - go.opentelemetry.io/otel v1.26.0 // indirect - go.opentelemetry.io/otel/metric v1.26.0 // indirect - go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.mongodb.org/mongo-driver v1.17.3 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.33.0 // indirect golang.org/x/time v0.8.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/api/go.sum b/api/go.sum index 0c5936cfb19..8ef088ae8fe 100644 --- a/api/go.sum +++ b/api/go.sum @@ -14,8 +14,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= @@ -54,8 +54,6 @@ github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdk github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= -github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= -github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= @@ -74,8 +72,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= +github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -83,6 +81,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -120,10 +120,7 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= -github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= @@ -153,8 +150,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -163,12 +158,11 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -181,8 +175,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -218,8 +212,9 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -230,17 +225,20 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 h1:1KuuSOy4ZNgW0KA2oYIngXVFhQcXxhLqCVK7cBcldkk= github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/goveralls v0.0.9/go.mod h1:FRbM1PS8oVsOe9JtdzAAXM+DsvDMMHcM1C7drGJD8HY= +github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= +github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -255,7 +253,6 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -265,6 +262,8 @@ github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n5 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oiime/logrusbun v0.1.2-0.20241011112815-4df3a0fb0e11 h1:rAqW9sGcM0VsfBwgeBzHk0yebrRwfeSJFy9Egqi0fmM= +github.com/oiime/logrusbun v0.1.2-0.20241011112815-4df3a0fb0e11/go.mod h1:HH9akx9teKgQPX41TYpLLRNxaL8q9R+ltzABnwUHfBM= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= @@ -277,8 +276,8 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs= github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw= github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= @@ -291,7 +290,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -304,8 +302,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= @@ -313,12 +311,9 @@ github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMf github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= github.com/shellhub-io/mongotest v0.0.0-20230928124937-e33b07010742 h1:sIFW1zdZvMTAvpHYOphDoWSh4tiGloK0El2GZni4E+U= github.com/shellhub-io/mongotest v0.0.0-20230928124937-e33b07010742/go.mod h1:6J6yfW5oIvAZ6VjxmV9KyFZyPFVM3B4V3Epbb+1c0oo= -github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= -github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= +github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= @@ -327,8 +322,6 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/square/mongo-lock v0.0.0-20230808145049-cfcf499f6bf0 h1:wnVho7xObpxuF7Lr0146VZtfOLfbkXGcvzfFUw2LXuM= -github.com/square/mongo-lock v0.0.0-20230808145049-cfcf499f6bf0/go.mod h1:bLPJcGVut+NBtZhrqY/jTnfluDrZeuIvf66VjuwU/eU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -343,23 +336,20 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= -github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= +github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= +github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0 h1:i1Kh9fmXgHG9z3uzJv5Arz7pDKVaaNpLrqyd+0xhYMA= github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0/go.mod h1:SD8nVMK1m7b/K2YJqYjYNzfHmZfqHtqNOlI44nfxjdg= +github.com/testcontainers/testcontainers-go/modules/postgres v0.37.0 h1:hsVwFkS6s+79MbKEO+W7A1wNIw1fmkMtF4fg83m6kbc= +github.com/testcontainers/testcontainers-go/modules/postgres v0.37.0/go.mod h1:Qj/eGbRbO/rEYdcRLmN+bEojzatP/+NS1y8ojl2PQsc= github.com/testcontainers/testcontainers-go/modules/redis v0.32.0 h1:HW5Qo9qfLi5iwfS7cbXwG6qe8ybXGePcgGPEmVlVDlo= github.com/testcontainers/testcontainers-go/modules/redis v0.32.0/go.mod h1:5kltdxVKZG0aP1iegeqKz4K8HHyP0wbkW5o84qLyMjY= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q= @@ -371,8 +361,11 @@ github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GH github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/uptrace/bun v0.3.9/go.mod h1:aL6D9vPw8DXaTQTwGrEPtUderBYXx7ShUmPfnxnqscw= github.com/uptrace/bun v1.2.11 h1:l9dTymsdZZAoSZ1+Qo3utms0RffgkDbIv+1UGk8N1wQ= github.com/uptrace/bun v1.2.11/go.mod h1:ww5G8h59UrOnCHmZ8O1I/4Djc7M/Z3E+EWFS2KLB6dQ= +github.com/uptrace/bun/dbfixture v1.2.11 h1:9rKYVDwQCLdXtPkgzMgvDsHKHXelNlXmnpuNHvFz2w0= +github.com/uptrace/bun/dbfixture v1.2.11/go.mod h1:E2H4A4/dx6+xB61qGsjVqV17Pqyb+Gg5QTmswy4hUpk= github.com/uptrace/bun/dialect/pgdialect v1.2.11 h1:n0VKWm1fL1dwJK5TRxYYLaRKRe14BOg2+AQgpvqzG/M= github.com/uptrace/bun/dialect/pgdialect v1.2.11/go.mod h1:NvV1S/zwtwBnW8yhJ3XEKAQEw76SkeH7yUhfrx3W1Eo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -386,17 +379,12 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xakep666/mongo-migrate v0.3.2 h1:qmDtIGiMRIwMvc84fOlsDoP+08S6NWLJDPqa4wPfQ1U= -github.com/xakep666/mongo-migrate v0.3.2/go.mod h1:onPlsF/AvU9UZjlyX3PiC5iAPHYJuejPPPqlOvsCGhM= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -405,29 +393,30 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= -go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38= -go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= -go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= -go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= -go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -438,10 +427,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -494,8 +482,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -507,12 +495,11 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -546,29 +533,25 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= @@ -582,7 +565,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -634,11 +616,10 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/api/routes/device.go b/api/routes/device.go index 24e14bb5d1b..ee3e5fcf389 100644 --- a/api/routes/device.go +++ b/api/routes/device.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/shellhub-io/shellhub/api/pkg/gateway" - "github.com/shellhub-io/shellhub/pkg/api/query" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/models" ) @@ -44,45 +43,45 @@ func (h *Handler) GetDeviceList(c gateway.Context) error { return err } - if c.QueryParam("connector") != "" { - filter := []query.Filter{ - { - Type: query.FilterTypeProperty, - Params: &query.FilterProperty{ - Name: "info.platform", - Operator: "eq", - Value: "connector", - }, - }, - { - Type: query.FilterTypeOperator, - Params: &query.FilterOperator{ - Name: "and", - }, - }, - } - - req.Filters.Data = append(req.Filters.Data, filter...) - } else { - filter := []query.Filter{ - { - Type: query.FilterTypeProperty, - Params: &query.FilterProperty{ - Name: "info.platform", - Operator: "ne", - Value: "connector", - }, - }, - { - Type: query.FilterTypeOperator, - Params: &query.FilterOperator{ - Name: "and", - }, - }, - } - - req.Filters.Data = append(req.Filters.Data, filter...) - } + // if c.QueryParam("connector") != "" { + // filter := []query.Filter{ + // { + // Type: query.FilterTypeProperty, + // Params: &query.FilterProperty{ + // Name: "info.platform", + // Operator: "eq", + // Value: "connector", + // }, + // }, + // { + // Type: query.FilterTypeOperator, + // Params: &query.FilterOperator{ + // Name: "and", + // }, + // }, + // } + // + // req.Filters.Data = append(req.Filters.Data, filter...) + // } else { + // filter := []query.Filter{ + // { + // Type: query.FilterTypeProperty, + // Params: &query.FilterProperty{ + // Name: "info.platform", + // Operator: "ne", + // Value: "connector", + // }, + // }, + // { + // Type: query.FilterTypeOperator, + // Params: &query.FilterOperator{ + // Name: "and", + // }, + // }, + // } + // + // req.Filters.Data = append(req.Filters.Data, filter...) + // } if err := c.Validate(req); err != nil { return err diff --git a/api/routes/nsadm.go b/api/routes/nsadm.go index 79a6cbe3376..bbfdec5b9f8 100644 --- a/api/routes/nsadm.go +++ b/api/routes/nsadm.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/shellhub-io/shellhub/api/pkg/gateway" + "github.com/shellhub-io/shellhub/pkg/api/query" "github.com/shellhub-io/shellhub/pkg/api/requests" ) @@ -35,6 +36,14 @@ func (h *Handler) GetNamespaceList(c gateway.Context) error { } req.Paginator.Normalize() + + if req.Sorter.By == "" { + req.Sorter.By = "created_at" + } + if req.Sorter.Order == "" { + req.Sorter.Order = query.OrderAsc + } + if err := req.Filters.Unmarshal(); err != nil { return err } diff --git a/api/routes/sshkeys.go b/api/routes/sshkeys.go index 5623adb2f3d..570a3b337ec 100644 --- a/api/routes/sshkeys.go +++ b/api/routes/sshkeys.go @@ -7,7 +7,6 @@ import ( "github.com/shellhub-io/shellhub/api/pkg/gateway" "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/models" ) @@ -30,15 +29,27 @@ const ( ) func (h *Handler) GetPublicKeys(c gateway.Context) error { - paginator := query.NewPaginator() - if err := c.Bind(paginator); err != nil { + req := new(requests.PublicKeyList) + + if err := c.Bind(req); err != nil { return err } - // TODO: normalize is not required when request is privileged - paginator.Normalize() + req.Paginator.Normalize() + + if req.Sorter.By == "" { + req.Sorter.By = "created_at" + } + + if req.Sorter.Order == "" { + req.Sorter.Order = "desc" + } + + if err := c.Validate(req); err != nil { + return err + } - list, count, err := h.service.ListPublicKeys(c.Ctx(), *paginator) + list, count, err := h.service.ListPublicKeys(c.Ctx(), req) if err != nil { return err } diff --git a/api/server.go b/api/server.go index d96fbfbeb33..d80d0de087a 100644 --- a/api/server.go +++ b/api/server.go @@ -9,6 +9,7 @@ import ( "github.com/shellhub-io/shellhub/api/routes" "github.com/shellhub-io/shellhub/api/services" "github.com/shellhub-io/shellhub/api/store/pg" + "github.com/shellhub-io/shellhub/api/store/pg/options" "github.com/shellhub-io/shellhub/pkg/api/internalclient" "github.com/shellhub-io/shellhub/pkg/cache" "github.com/shellhub-io/shellhub/pkg/geoip/geolite2" @@ -83,7 +84,7 @@ func (s *Server) Setup(ctx context.Context) error { log.Debug("Redis cache initialized successfully") uri := pg.URI(s.env.PostgresHost, s.env.PostgresPort, s.env.PostgresUser, s.env.PostgresPassword, s.env.PostgresDB) - store, err := pg.New(ctx, uri) + store, err := pg.New(ctx, uri, options.Log("DEBUG", true), options.Migrate()) if err != nil { log. WithError(err). diff --git a/api/services/api-key.go b/api/services/api-key.go index 65a1a720f28..8a8852ab94e 100644 --- a/api/services/api-key.go +++ b/api/services/api-key.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" + "github.com/shellhub-io/shellhub/api/store" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/api/responses" "github.com/shellhub-io/shellhub/pkg/clock" @@ -31,7 +32,7 @@ type APIKeyService interface { } func (s *service) CreateAPIKey(ctx context.Context, req *requests.CreateAPIKey) (*responses.CreateAPIKey, error) { - if _, err := s.store.NamespaceGet(ctx, req.TenantID); err != nil { + if _, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID); err != nil { return nil, NewErrNamespaceNotFound(req.TenantID, err) } @@ -86,35 +87,50 @@ func (s *service) CreateAPIKey(ctx context.Context, req *requests.CreateAPIKey) // As we need to return the plain key in the create service, we temporarily set // the apiKey.ID to the plain key here. - apiKey, _ := s.store.APIKeyGet(ctx, hashedKey) + apiKey, _ := s.store.APIKeyGet(ctx, store.APIKeyIdentID, hashedKey, req.TenantID) apiKey.ID = req.Key return responses.CreateAPIKeyFromModel(apiKey), nil } func (s *service) ListAPIKeys(ctx context.Context, req *requests.ListAPIKey) ([]models.APIKey, int, error) { - return s.store.APIKeyList(ctx, req.TenantID, req.Paginator, req.Sorter) + return s.store.APIKeyList( + ctx, + s.store.Options().InNamespace(req.TenantID), + s.store.Options().Paginate(req.Paginator), + s.store.Options().Order(req.Sorter), + ) } func (s *service) UpdateAPIKey(ctx context.Context, req *requests.UpdateAPIKey) error { - ns, err := s.store.NamespaceGet(ctx, req.TenantID) + ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) if err != nil { return NewErrNamespaceNotFound(req.TenantID, err) } + if conflicts, has, _ := s.store.APIKeyConflicts(ctx, req.TenantID, &models.APIKeyConflicts{Name: req.Name}); has { + return NewErrAPIKeyDuplicated(conflicts) + } + + apiKey, err := s.store.APIKeyGet(ctx, store.APIKeyIdentName, req.CurrentName, req.TenantID) + if err != nil { + return err + } + // If req.Role is not empty, it must be lower than the user's role. if req.Role != "" { if m, ok := ns.FindMember(req.UserID); !ok || !m.Role.HasAuthority(req.Role) { return NewErrRoleInvalid() } + + apiKey.Role = req.Role } - if conflicts, has, _ := s.store.APIKeyConflicts(ctx, req.TenantID, &models.APIKeyConflicts{Name: req.Name}); has { - return NewErrAPIKeyDuplicated(conflicts) + if req.Name != "" { + apiKey.Name = req.Name } - change := &models.APIKeyChanges{Name: req.Name, Role: req.Role} - if err := s.store.APIKeyUpdate(ctx, req.TenantID, req.CurrentName, change); err != nil { + if err := s.store.APIKeySave(ctx, apiKey); err != nil { return NewErrAPIKeyNotFound(req.CurrentName, err) } @@ -122,7 +138,12 @@ func (s *service) UpdateAPIKey(ctx context.Context, req *requests.UpdateAPIKey) } func (s *service) DeleteAPIKey(ctx context.Context, req *requests.DeleteAPIKey) error { - if err := s.store.APIKeyDelete(ctx, req.TenantID, req.Name); err != nil { + apiKey, err := s.store.APIKeyGet(ctx, store.APIKeyIdentName, req.Name, req.TenantID) + if err != nil { + return err + } + + if err := s.store.APIKeyDelete(ctx, apiKey); err != nil { return NewErrAPIKeyNotFound(req.Name, err) } diff --git a/api/services/auth.go b/api/services/auth.go index e68184522e9..ccbd4959cde 100644 --- a/api/services/auth.go +++ b/api/services/auth.go @@ -16,10 +16,12 @@ import ( "time" "github.com/cnf/structhash" + "github.com/shellhub-io/shellhub/api/store" "github.com/shellhub-io/shellhub/pkg/api/authorizer" "github.com/shellhub-io/shellhub/pkg/api/jwttoken" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/clock" + "github.com/shellhub-io/shellhub/pkg/hash" "github.com/shellhub-io/shellhub/pkg/models" "github.com/shellhub-io/shellhub/pkg/uuid" log "github.com/sirupsen/logrus" @@ -66,136 +68,97 @@ type AuthService interface { } func (s *service) AuthDevice(ctx context.Context, req requests.DeviceAuth, remoteAddr string) (*models.DeviceAuthResponse, error) { - if req.Identity == nil { - return nil, NewErrAuthDeviceNoIdentity() + if _, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID); err != nil { + return nil, NewErrNamespaceNotFound(req.TenantID, err) } - identity := &models.DeviceIdentity{ - MAC: req.Identity.MAC, - } - - var hostname string - if req.Hostname != "" { - hostname = req.Hostname - } + if req.Hostname == "" { + if req.Identity == nil || req.Identity.MAC == "" { + return nil, NewErrAuthDeviceNoIdentityAndHostname() + } - if hostname == "" && identity.MAC == "" { - return nil, NewErrAuthDeviceNoIdentityAndHostname() + req.Hostname = strings.ReplaceAll(req.Identity.MAC, ":", "-") } auth := models.DeviceAuth{ - Hostname: hostname, - Identity: identity, + Hostname: req.Hostname, + Identity: &models.DeviceIdentity{MAC: req.Identity.MAC}, PublicKey: req.PublicKey, TenantID: req.TenantID, } - uid := sha256.Sum256(structhash.Dump(auth, 1)) - key := hex.EncodeToString(uid[:]) - - claims := authorizer.DeviceClaims{ - UID: key, - TenantID: req.TenantID, - } - - token, err := jwttoken.EncodeDeviceClaims(claims, s.privKey) + uidSHA := sha256.Sum256(structhash.Dump(auth, 1)) + device, err := s.store.DeviceGet(ctx, store.DeviceIdentUID, hex.EncodeToString(uidSHA[:])) if err != nil { - return nil, NewErrTokenSigned(err) - } - - type Device struct { - Name string - Namespace string - } - - var value *Device - - if err := s.cache.Get(ctx, strings.Join([]string{"auth_device", key}, "/"), &value); err == nil && value != nil { - return &models.DeviceAuthResponse{ - UID: key, - Token: token, - Name: value.Name, - Namespace: value.Namespace, - }, nil - } - var info *models.DeviceInfo - if req.Info != nil { - info = &models.DeviceInfo{ - ID: req.Info.ID, - PrettyName: req.Info.PrettyName, - Version: req.Info.Version, - Arch: req.Info.Arch, - Platform: req.Info.Platform, + if err != store.ErrNoDocuments { + return nil, err } - } - - position, err := s.locator.GetPosition(net.ParseIP(remoteAddr)) - if err != nil { - return nil, err - } - device := models.Device{ - UID: key, - Identity: identity, - Info: info, - PublicKey: req.PublicKey, - TenantID: req.TenantID, - LastSeen: clock.Now(), - RemoteAddr: remoteAddr, - Position: &models.DevicePosition{ - Longitude: position.Longitude, - Latitude: position.Latitude, - }, - } + position, err := s.locator.GetPosition(net.ParseIP(remoteAddr)) + if err != nil { + return nil, err + } - // The order here is critical as we don't want to register devices if the tenant id is invalid - namespace, err := s.store.NamespaceGet(ctx, device.TenantID) - if err != nil { - return nil, NewErrNamespaceNotFound(device.TenantID, err) - } + device = &models.Device{ + UID: hex.EncodeToString(uidSHA[:]), + TenantID: req.TenantID, + LastSeen: clock.Now(), + DisconnectedAt: &time.Time{}, + Status: models.DeviceStatusPending, + Name: req.Hostname, + Identity: &models.DeviceIdentity{MAC: req.Identity.MAC}, + PublicKey: req.PublicKey, + Position: &models.DevicePosition{ + Longitude: position.Longitude, + Latitude: position.Latitude, + }, + Info: nil, + } - hostname = strings.ToLower(hostname) + if req.Info != nil { + device.Info = &models.DeviceInfo{ + ID: req.Info.ID, + PrettyName: req.Info.PrettyName, + Version: req.Info.Version, + Arch: req.Info.Arch, + Platform: req.Info.Platform, + } + } - if err := s.store.DeviceCreate(ctx, device, hostname); err != nil { - return nil, NewErrDeviceCreate(device, err) - } + if _, err := s.store.DeviceCreate(ctx, device); err != nil { + return nil, NewErrDeviceCreate(*device, err) + } + } else { + device.DisconnectedAt = nil + device.LastSeen = clock.Now() - for _, uid := range req.Sessions { - if err := s.store.SessionSetLastSeen(ctx, models.UID(uid)); err != nil { - continue + if err := s.store.DeviceSave(ctx, device); err != nil { + log.WithError(err).Error("failed to updated device to online") } } - dev, err := s.store.DeviceGetByUID(ctx, models.UID(device.UID), device.TenantID) + claims := authorizer.DeviceClaims{UID: device.UID, TenantID: device.TenantID} + token, err := jwttoken.EncodeDeviceClaims(claims, s.privKey) if err != nil { - return nil, NewErrDeviceNotFound(models.UID(device.UID), err) - } - if err := s.cache.Set(ctx, strings.Join([]string{"auth_device", key}, "/"), &Device{Name: dev.Name, Namespace: namespace.Name}, time.Second*30); err != nil { - return nil, err + return nil, NewErrTokenSigned(err) } - return &models.DeviceAuthResponse{ - UID: key, - Token: token, - Name: dev.Name, - Namespace: namespace.Name, - }, nil + return &models.DeviceAuthResponse{UID: device.UID, Token: token, Name: device.Name, Namespace: "dev"}, nil } func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser, sourceIP string) (*models.UserAuthResponse, int64, string, error) { - if s, err := s.store.SystemGet(ctx); err != nil || !s.Authentication.Local.Enabled { - return nil, 0, "", NewErrAuthMethodNotAllowed(models.UserAuthMethodLocal.String()) - } - - var err error - var user *models.User + // if s, err := s.store.SystemGet(ctx); err != nil || !s.Authentication.Local.Enabled { + // return nil, 0, "", NewErrAuthMethodNotAllowed(models.UserAuthMethodLocal.String()) + // } + var ident store.UserIdent if req.Identifier.IsEmail() { - user, err = s.store.UserGetByEmail(ctx, strings.ToLower(string(req.Identifier))) + ident = store.UserIdentEmail } else { - user, err = s.store.UserGetByUsername(ctx, strings.ToLower(string(req.Identifier))) + ident = store.UserIdentUsername } + user, err := s.store.UserGet(ctx, ident, strings.ToLower(string(req.Identifier))) if err != nil { return nil, 0, "", NewErrAuthUnathorized(nil) } @@ -227,7 +190,7 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser return nil, lockout, "", NewErrAuthUnathorized(nil) } - if !user.Password.Compare(req.Password) { + if !hash.CompareWith(req.Password, user.PasswordDigest) { lockout, _, err := s.cache.StoreLoginAttempt(ctx, sourceIP, user.ID) if err != nil { log.WithError(err). @@ -264,7 +227,7 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser role := "" // Populate the tenant and role when the user is associated with a namespace. If the member status is pending, we // ignore the namespace. - if ns, _ := s.store.NamespaceGetPreferred(ctx, user.ID); ns != nil && ns.TenantID != "" { + if ns, _ := s.store.UserPreferredNamespace(ctx, store.UserIdentID, user.ID); ns != nil && ns.TenantID != "" { if m, _ := ns.FindMember(user.ID); m.Status != models.MemberStatusPending { tenantID = ns.TenantID role = m.Role.String() @@ -285,15 +248,15 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser } // Updates last_login and the hash algorithm to bcrypt if still using SHA256 - changes := &models.UserChanges{LastLogin: clock.Now(), PreferredNamespace: &tenantID} - if !strings.HasPrefix(user.Password.Hash, "$") { - if neo, _ := models.HashUserPassword(req.Password); neo.Hash != "" { - changes.Password = neo.Hash - } + user.LastLogin = clock.Now() + user.Preferences.PreferredNamespace = tenantID + if !strings.HasPrefix(user.PasswordDigest, "$") { + pwdDigest, _ := hash.Do(req.Password) + user.PasswordDigest = pwdDigest } // TODO: evaluate make this update in a go routine. - if err := s.store.UserUpdate(ctx, user.ID, changes); err != nil { + if err := s.store.UserSave(ctx, user); err != nil { return nil, 0, "", NewErrUserUpdate(user, err) } @@ -310,7 +273,7 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser User: user.Username, Name: user.Name, Email: user.Email, - RecoveryEmail: user.RecoveryEmail, + RecoveryEmail: user.Preferences.RecoveryEmail, MFA: user.MFA.Enabled, Tenant: tenantID, Role: role, @@ -322,7 +285,7 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser } func (s *service) CreateUserToken(ctx context.Context, req *requests.CreateUserToken) (*models.UserAuthResponse, error) { - user, _, err := s.store.UserGetByID(ctx, req.UserID, false) + user, err := s.store.UserGet(ctx, store.UserIdentID, req.UserID) if err != nil { return nil, NewErrUserNotFound(req.UserID, err) } @@ -333,7 +296,8 @@ func (s *service) CreateUserToken(ctx context.Context, req *requests.CreateUserT switch req.TenantID { case "": // A user may not have a preferred namespace. In such cases, we create a token without it. - namespace, err := s.store.NamespaceGetPreferred(ctx, user.ID) + + namespace, err := s.store.UserPreferredNamespace(ctx, store.UserIdentID, user.ID) if err != nil { break } @@ -348,7 +312,7 @@ func (s *service) CreateUserToken(ctx context.Context, req *requests.CreateUserT role = member.Role.String() } default: - namespace, err := s.store.NamespaceGet(ctx, req.TenantID) + namespace, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) if err != nil { return nil, NewErrNamespaceNotFound(req.TenantID, err) } @@ -366,7 +330,8 @@ func (s *service) CreateUserToken(ctx context.Context, req *requests.CreateUserT role = member.Role.String() if user.Preferences.PreferredNamespace != namespace.TenantID { - _ = s.store.UserUpdate(ctx, user.ID, &models.UserChanges{PreferredNamespace: &tenantID}) + user.Preferences.PreferredNamespace = namespace.TenantID + _ = s.store.UserSave(ctx, user) } } @@ -394,7 +359,7 @@ func (s *service) CreateUserToken(ctx context.Context, req *requests.CreateUserT User: user.Username, Name: user.Name, Email: user.Email, - RecoveryEmail: user.RecoveryEmail, + RecoveryEmail: user.Preferences.RecoveryEmail, MFA: user.MFA.Enabled, Tenant: tenantID, Role: role, @@ -414,7 +379,7 @@ func (s *service) AuthAPIKey(ctx context.Context, key string) (*models.APIKey, e hashedKey := hex.EncodeToString(keySum[:]) var err error - if apiKey, err = s.store.APIKeyGet(ctx, hashedKey); err != nil { + if apiKey, err = s.store.APIKeyGet(ctx, store.APIKeyIdentID, hashedKey, ""); err != nil { return nil, NewErrAPIKeyNotFound("", err) } } @@ -458,7 +423,7 @@ func (s *service) AuthPublicKey(ctx context.Context, req requests.PublicKeyAuth) } func (s *service) GetUserRole(ctx context.Context, tenantID, userID string) (string, error) { - ns, err := s.store.NamespaceGet(ctx, tenantID) + ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenantID) if err != nil { return "", err } diff --git a/api/services/device.go b/api/services/device.go index d9dcd2dbace..d6e09f21661 100644 --- a/api/services/device.go +++ b/api/services/device.go @@ -2,14 +2,11 @@ package services import ( "context" - "errors" "strings" - "time" "github.com/shellhub-io/shellhub/api/store" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/envs" "github.com/shellhub-io/shellhub/pkg/models" ) @@ -28,53 +25,22 @@ type DeviceService interface { } func (s *service) ListDevices(ctx context.Context, req *requests.DeviceList) ([]models.Device, int, error) { - if req.DeviceStatus == models.DeviceStatusRemoved { - // TODO: unique DeviceList - removed, count, err := s.store.DeviceRemovedList(ctx, req.TenantID, req.Paginator, req.Filters, req.Sorter) - if err != nil { - return nil, 0, err - } - - devices := make([]models.Device, 0, len(removed)) - for _, device := range removed { - devices = append(devices, *device.Device) - } - - return devices, count, nil + opts := []store.QueryOption{ + s.store.Options().InNamespace(req.TenantID), + s.store.Options().Filter(req.Filters), + s.store.Options().Paginate(req.Paginator), + s.store.Options().Order(req.Sorter), } - if req.TenantID != "" { - ns, err := s.store.NamespaceGet(ctx, req.TenantID, s.store.Options().CountAcceptedDevices()) - if err != nil { - return nil, 0, NewErrNamespaceNotFound(req.TenantID, err) - } - - if ns.HasMaxDevices() { - switch { - case envs.IsCloud(): - removed, err := s.store.DeviceRemovedCount(ctx, ns.TenantID) - if err != nil { - return nil, 0, NewErrDeviceRemovedCount(err) - } - - if ns.HasLimitDevicesReached(removed) { - return s.store.DeviceList(ctx, req.DeviceStatus, req.Paginator, req.Filters, req.Sorter, store.DeviceAcceptableFromRemoved) - } - case envs.IsEnterprise(): - fallthrough - case envs.IsCommunity(): - if ns.HasMaxDevicesReached() { - return s.store.DeviceList(ctx, req.DeviceStatus, req.Paginator, req.Filters, req.Sorter, store.DeviceAcceptableAsFalse) - } - } - } + if req.DeviceStatus != "" { + opts = append(opts, s.store.Options().WithStatus(string(req.DeviceStatus))) } - return s.store.DeviceList(ctx, req.DeviceStatus, req.Paginator, req.Filters, req.Sorter, store.DeviceAcceptableIfNotAccepted) + return s.store.DeviceList(ctx, opts...) } func (s *service) GetDevice(ctx context.Context, uid models.UID) (*models.Device, error) { - device, err := s.store.DeviceGet(ctx, uid) + device, err := s.store.DeviceGet(ctx, store.DeviceIdentUID, string(uid)) if err != nil { return nil, NewErrDeviceNotFound(uid, err) } @@ -91,69 +57,30 @@ func (s *service) GetDevice(ctx context.Context, uid models.UID) (*models.Device // NewErrNamespaceNotFound(tenant, err), if the usage cannot be reported, ErrReport or if the store function that // delete the device fails. func (s *service) DeleteDevice(ctx context.Context, uid models.UID, tenant string) error { - device, err := s.store.DeviceGetByUID(ctx, uid, tenant) + device, err := s.store.DeviceGet(ctx, store.DeviceIdentUID, string(uid)) if err != nil { return NewErrDeviceNotFound(uid, err) } - ns, err := s.store.NamespaceGet(ctx, tenant) - if err != nil { - return NewErrNamespaceNotFound(tenant, err) - } - - // If the namespace has a limit of devices, we change the device's slot status to removed. - // This way, we can keep track of the number of devices that were removed from the namespace and void the device - // switching. - if envs.IsCloud() && envs.HasBilling() && !ns.Billing.IsActive() { - if err := s.store.DeviceRemovedInsert(ctx, tenant, device); err != nil { - return NewErrDeviceRemovedInsert(err) - } - } - - return s.store.DeviceDelete(ctx, uid) + // ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenant) + // if err != nil { + // return NewErrNamespaceNotFound(tenant, err) + // } + // + // // If the namespace has a limit of devices, we change the device's slot status to removed. + // // This way, we can keep track of the number of devices that were removed from the namespace and void the device + // // switching. + // if envs.IsCloud() && envs.HasBilling() && !ns.Billing.IsActive() { + // if err := s.store.DeviceRemovedInsert(ctx, tenant, device); err != nil { + // return NewErrDeviceRemovedInsert(err) + // } + // } + + return s.store.DeviceDelete(ctx, device) } func (s *service) RenameDevice(ctx context.Context, uid models.UID, name, tenant string) error { - device, err := s.store.DeviceGetByUID(ctx, uid, tenant) - if err != nil { - return NewErrDeviceNotFound(uid, err) - } - - updatedDevice := &models.Device{ - UID: device.UID, - Name: strings.ToLower(name), - Identity: device.Identity, - Info: device.Info, - PublicKey: device.PublicKey, - TenantID: device.TenantID, - LastSeen: device.LastSeen, - Online: device.Online, - Namespace: device.Namespace, - Status: device.Status, - CreatedAt: time.Time{}, - RemoteAddr: "", - Position: &models.DevicePosition{}, - Tags: []string{}, - } - - if ok, err := s.validator.Struct(updatedDevice); !ok || err != nil { - return NewErrDeviceInvalid(nil, err) - } - - if device.Name == updatedDevice.Name { - return nil - } - - otherDevice, err := s.store.DeviceGetByName(ctx, updatedDevice.Name, tenant, models.DeviceStatusAccepted) - if err != nil && err != store.ErrNoDocuments { - return NewErrDeviceNotFound(models.UID(updatedDevice.UID), err) - } - - if otherDevice != nil { - return NewErrDeviceDuplicated(otherDevice.Name, err) - } - - return s.store.DeviceRename(ctx, uid, name) + return nil } // LookupDevice looks for a device in a namespace. @@ -161,8 +88,8 @@ func (s *service) RenameDevice(ctx context.Context, uid models.UID, name, tenant // It receives a context, used to "control" the request flow and, the namespace name from a models.Namespace and a // device name from models.Device. func (s *service) LookupDevice(ctx context.Context, namespace, name string) (*models.Device, error) { - device, err := s.store.DeviceLookup(ctx, namespace, name) - if err != nil || device == nil { + device, err := s.store.DeviceGet(ctx, store.DeviceIdentName, name) + if err != nil { return nil, NewErrDeviceLookupNotFound(namespace, name, err) } @@ -170,26 +97,20 @@ func (s *service) LookupDevice(ctx context.Context, namespace, name string) (*mo } func (s *service) OfflineDevice(ctx context.Context, uid models.UID) error { - now := clock.Now() - if err := s.store.DeviceUpdate(ctx, "", string(uid), &models.DeviceChanges{DisconnectedAt: &now}); err != nil { - if errors.Is(err, store.ErrNoDocuments) { - return NewErrDeviceNotFound(uid, err) - } - - return err + device, err := s.store.DeviceGet(ctx, store.DeviceIdentUID, string(uid)) + if err != nil { + return NewErrDeviceNotFound(uid, err) } - return nil + now := clock.Now() + device.DisconnectedAt = &now + + return s.store.DeviceSave(ctx, device) } // UpdateDeviceStatus updates the device status. func (s *service) UpdateDeviceStatus(ctx context.Context, tenant string, uid models.UID, status models.DeviceStatus) error { - namespace, err := s.store.NamespaceGet(ctx, tenant, s.store.Options().CountAcceptedDevices()) - if err != nil { - return NewErrNamespaceNotFound(tenant, err) - } - - device, err := s.store.DeviceGetByUID(ctx, uid, tenant) + device, err := s.store.DeviceGet(ctx, store.DeviceIdentUID, string(uid)) if err != nil { return NewErrDeviceNotFound(uid, err) } @@ -198,102 +119,113 @@ func (s *service) UpdateDeviceStatus(ctx context.Context, tenant string, uid mod return NewErrDeviceStatusAccepted(nil) } - // NOTICE: when the device is intended to be rejected or in pending status, we don't check for duplications as it - // is not going to be considered for connections. - if status == models.DeviceStatusPending || status == models.DeviceStatusRejected { - return s.store.DeviceUpdateStatus(ctx, uid, status) - } - - // NOTICE: when the intended status is not accepted, we return an error because these status are not allowed - // to be set by the user. - if status != models.DeviceStatusAccepted { - return NewErrDeviceStatusInvalid(string(status), nil) - } - - // NOTICE: when there is an already accepted device with the same MAC address, we need to update the device UID - // transfer the sessions and delete the old device. - sameMacDev, err := s.store.DeviceGetByMac(ctx, device.Identity.MAC, device.TenantID, models.DeviceStatusAccepted) - if err != nil && err != store.ErrNoDocuments { - return NewErrDeviceNotFound(models.UID(device.UID), err) - } - - // TODO: move this logic to store's transactions. - if sameMacDev != nil && sameMacDev.UID != device.UID { - if sameName, err := s.store.DeviceGetByName(ctx, device.Name, device.TenantID, models.DeviceStatusAccepted); sameName != nil && sameName.Identity.MAC != device.Identity.MAC { - return NewErrDeviceDuplicated(device.Name, err) - } - - if err := s.store.SessionUpdateDeviceUID(ctx, models.UID(sameMacDev.UID), models.UID(device.UID)); err != nil && err != store.ErrNoDocuments { - return err - } - - if err := s.store.DeviceRename(ctx, models.UID(device.UID), sameMacDev.Name); err != nil { - return err - } - - if err := s.store.DeviceDelete(ctx, models.UID(sameMacDev.UID)); err != nil { - return err - } - - return s.store.DeviceUpdateStatus(ctx, uid, status) - } - - if sameName, err := s.store.DeviceGetByName(ctx, device.Name, device.TenantID, models.DeviceStatusAccepted); sameName != nil { - return NewErrDeviceDuplicated(device.Name, err) - } - - if status != models.DeviceStatusAccepted { - return s.store.DeviceUpdateStatus(ctx, uid, status) - } - - switch { - case envs.IsCommunity(), envs.IsEnterprise(): - if namespace.HasMaxDevices() && namespace.HasMaxDevicesReached() { - return NewErrDeviceMaxDevicesReached(namespace.MaxDevices) - } - case envs.IsCloud(): - if namespace.Billing.IsActive() { - if err := s.BillingReport(s.client, namespace.TenantID, ReportDeviceAccept); err != nil { - return NewErrBillingReportNamespaceDelete(err) - } - } else { - // TODO: this strategy that stores the removed devices in the database can be simplified. - removed, err := s.store.DeviceRemovedGet(ctx, tenant, uid) - if err != nil && err != store.ErrNoDocuments { - return NewErrDeviceRemovedGet(err) - } - - if removed != nil { - if err := s.store.DeviceRemovedDelete(ctx, tenant, uid); err != nil { - return NewErrDeviceRemovedDelete(err) - } - } else { - count, err := s.store.DeviceRemovedCount(ctx, tenant) - if err != nil { - return NewErrDeviceRemovedCount(err) - } - - if namespace.HasMaxDevices() && namespace.HasLimitDevicesReached(count) { - return NewErrDeviceRemovedFull(namespace.MaxDevices, nil) - } - } + device.Status = status - ok, err := s.BillingEvaluate(s.client, namespace.TenantID) - if err != nil { - return NewErrBillingEvaluate(err) - } + return s.store.DeviceSave(ctx, device) - if !ok { - return ErrDeviceLimit - } - } - } - - return s.store.DeviceUpdateStatus(ctx, uid, status) + // NOTICE: when the device is intended to be rejected or in pending status, we don't check for duplications as it + // is not going to be considered for connections. + // if status == models.DeviceStatusPending || status == models.DeviceStatusRejected { + // device.Status = status + // + // return s.store.DeviceSave(ctx, device) + // } + // + // namespace, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenant) + // if err != nil { + // return NewErrNamespaceNotFound(tenant, err) + // } + // + // // NOTICE: when the intended status is not accepted, we return an error because these status are not allowed + // // to be set by the user. + // if status != models.DeviceStatusAccepted { + // return NewErrDeviceStatusInvalid(string(status), nil) + // } + // + // // NOTICE: when there is an already accepted device with the same MAC address, we need to update the device UID + // // transfer the sessions and delete the old device. + // sameMacDev, err := s.store.DeviceGetByMac(ctx, device.Identity.MAC, device.TenantID, models.DeviceStatusAccepted) + // if err != nil && err != store.ErrNoDocuments { + // return NewErrDeviceNotFound(models.UID(device.UID), err) + // } + // + // // TODO: move this logic to store's transactions. + // if sameMacDev != nil && sameMacDev.UID != device.UID { + // if sameName, err := s.store.DeviceGetByName(ctx, device.Name, device.TenantID, models.DeviceStatusAccepted); sameName != nil && sameName.Identity.MAC != device.Identity.MAC { + // return NewErrDeviceDuplicated(device.Name, err) + // } + // + // if err := s.store.SessionUpdateDeviceUID(ctx, models.UID(sameMacDev.UID), models.UID(device.UID)); err != nil && err != store.ErrNoDocuments { + // return err + // } + // + // if err := s.store.DeviceRename(ctx, models.UID(device.UID), sameMacDev.Name); err != nil { + // return err + // } + // + // if err := s.store.DeviceDelete(ctx, models.UID(sameMacDev.UID)); err != nil { + // return err + // } + // + // return s.store.DeviceUpdateStatus(ctx, uid, status) + // } + // + // if sameName, err := s.store.DeviceGetByName(ctx, device.Name, device.TenantID, models.DeviceStatusAccepted); sameName != nil { + // return NewErrDeviceDuplicated(device.Name, err) + // } + // + // if status != models.DeviceStatusAccepted { + // return s.store.DeviceUpdateStatus(ctx, uid, status) + // } + // + // switch { + // case envs.IsCommunity(), envs.IsEnterprise(): + // if namespace.HasMaxDevices() && namespace.HasMaxDevicesReached() { + // return NewErrDeviceMaxDevicesReached(namespace.MaxDevices) + // } + // case envs.IsCloud(): + // if namespace.Billing.IsActive() { + // if err := s.BillingReport(s.client, namespace.TenantID, ReportDeviceAccept); err != nil { + // return NewErrBillingReportNamespaceDelete(err) + // } + // } else { + // // TODO: this strategy that stores the removed devices in the database can be simplified. + // removed, err := s.store.DeviceRemovedGet(ctx, tenant, uid) + // if err != nil && err != store.ErrNoDocuments { + // return NewErrDeviceRemovedGet(err) + // } + // + // if removed != nil { + // if err := s.store.DeviceRemovedDelete(ctx, tenant, uid); err != nil { + // return NewErrDeviceRemovedDelete(err) + // } + // } else { + // count, err := s.store.DeviceRemovedCount(ctx, tenant) + // if err != nil { + // return NewErrDeviceRemovedCount(err) + // } + // + // if namespace.HasMaxDevices() && namespace.HasLimitDevicesReached(count) { + // return NewErrDeviceRemovedFull(namespace.MaxDevices, nil) + // } + // } + // + // ok, err := s.BillingEvaluate(s.client, namespace.TenantID) + // if err != nil { + // return NewErrBillingEvaluate(err) + // } + // + // if !ok { + // return ErrDeviceLimit + // } + // } + // } + // + // return s.store.DeviceUpdateStatus(ctx, uid, status) } func (s *service) UpdateDevice(ctx context.Context, req *requests.DeviceUpdate) error { - device, err := s.store.DeviceGetByUID(ctx, models.UID(req.UID), req.TenantID) + device, err := s.store.DeviceGet(ctx, store.DeviceIdentUID, string(req.UID)) if err != nil { return NewErrDeviceNotFound(models.UID(req.UID), err) } @@ -304,11 +236,13 @@ func (s *service) UpdateDevice(ctx context.Context, req *requests.DeviceUpdate) return NewErrDeviceDuplicated(req.Name, err) } - // We pass DisconnectedAt because we don't want to update it to nil - changes := &models.DeviceChanges{DisconnectedAt: device.DisconnectedAt} - if req.Name != "" && strings.ToLower(req.Name) != device.Name { - changes.Name = strings.ToLower(req.Name) + if req.Name != "" { + device.Name = strings.ToLower(req.Name) } - return s.store.DeviceUpdate(ctx, req.TenantID, req.UID, changes) + if err := s.store.DeviceSave(ctx, device); err != nil { + return err + } + + return nil } diff --git a/api/services/device_tags.go b/api/services/device_tags.go index b3a7ad4dee5..4c45d236386 100644 --- a/api/services/device_tags.go +++ b/api/services/device_tags.go @@ -23,20 +23,7 @@ const DeviceMaxTags = 3 // If the device already has the maximum number of tags, a NewErrTagLimit error will be returned. // A unknown error will be returned if the tag is not created. func (s *service) CreateDeviceTag(ctx context.Context, uid models.UID, tag string) error { - device, err := s.store.DeviceGet(ctx, uid) - if err != nil || device == nil { - return NewErrDeviceNotFound(uid, err) - } - - if len(device.Tags) == DeviceMaxTags { - return NewErrTagLimit(DeviceMaxTags, nil) - } - - if contains(device.Tags, tag) { - return NewErrTagDuplicated(tag, nil) - } - - return s.store.DevicePushTag(ctx, uid, tag) + return nil } // RemoveDeviceTag removes a tag from a device. UID is the device's UID and tag is the tag's name. @@ -45,16 +32,7 @@ func (s *service) CreateDeviceTag(ctx context.Context, uid models.UID, tag strin // If the tag does not exist, a NewErrTagNotFound error will be returned. // A unknown error will be returned if the tag is not removed. func (s *service) RemoveDeviceTag(ctx context.Context, uid models.UID, tag string) error { - device, err := s.store.DeviceGet(ctx, uid) - if err != nil || device == nil { - return NewErrDeviceNotFound(uid, err) - } - - if !contains(device.Tags, tag) { - return NewErrTagNotFound(tag, nil) - } - - return s.store.DevicePullTag(ctx, uid, tag) + return nil } // UpdateDeviceTag updates a device's tags. UID is the device's UID and tags is the new tags. @@ -63,31 +41,5 @@ func (s *service) RemoveDeviceTag(ctx context.Context, uid models.UID, tag strin // If tags' list contains a duplicated one, it is removed and the device's tag will be updated. // If the device does not exist, a NewErrDeviceNotFound error will be returned. func (s *service) UpdateDeviceTag(ctx context.Context, uid models.UID, tags []string) error { - if len(tags) > DeviceMaxTags { - return NewErrTagLimit(DeviceMaxTags, nil) - } - - if _, err := s.store.DeviceGet(ctx, uid); err != nil { - return NewErrDeviceNotFound(uid, err) - } - - // TODO: remove this conversion function in favor of a external package. - set := func(list []string) []string { - s := make(map[string]bool) - l := make([]string, 0) - for _, o := range list { - if _, ok := s[o]; !ok { - s[o] = true - l = append(l, o) - } - } - - return l - }(tags) - - if _, _, err := s.store.DeviceSetTags(ctx, uid, set); err != nil { - return err - } - return nil } diff --git a/api/services/member.go b/api/services/member.go index 8059fa38040..3060d0ffddf 100644 --- a/api/services/member.go +++ b/api/services/member.go @@ -2,7 +2,6 @@ package services import ( "context" - "errors" "strings" "time" @@ -48,12 +47,12 @@ type MemberService interface { } func (s *service) AddNamespaceMember(ctx context.Context, req *requests.NamespaceAddMember) (*models.Namespace, error) { - namespace, err := s.store.NamespaceGet(ctx, req.TenantID) + namespace, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) if err != nil || namespace == nil { return nil, NewErrNamespaceNotFound(req.TenantID, err) } - user, _, err := s.store.UserGetByID(ctx, req.UserID, false) + user, err := s.store.UserGet(ctx, store.UserIdentID, req.UserID) if err != nil || user == nil { return nil, NewErrUserNotFound(req.UserID, err) } @@ -71,17 +70,18 @@ func (s *service) AddNamespaceMember(ctx context.Context, req *requests.Namespac // In cloud instances, if the target user does not exist, we need to create a new user // with the specified email. We use the inserted ID to identify the user once they complete // the registration and accepts the invitation. - passiveUser, err := s.store.UserGetByEmail(ctx, strings.ToLower(req.MemberEmail)) + passiveUser, err := s.store.UserGet(ctx, store.UserIdentEmail, strings.ToLower(req.MemberEmail)) if err != nil { - if !envs.IsCloud() || !errors.Is(err, store.ErrNoDocuments) { - return nil, NewErrUserNotFound(req.MemberEmail, err) - } - - passiveUser = &models.User{} - passiveUser.ID, err = s.store.UserCreateInvited(ctx, strings.ToLower(req.MemberEmail)) - if err != nil { - return nil, err - } + return nil, NewErrUserNotFound(req.MemberEmail, err) + // if !envs.IsCloud() || !errors.Is(err, store.ErrNoDocuments) { + // return nil, NewErrUserNotFound(req.MemberEmail, err) + // } + + // passiveUser = &models.User{} + // passiveUser.ID, err = s.store.UserCreateInvited(ctx, strings.ToLower(req.MemberEmail)) + // if err != nil { + // return nil, err + // } } // In cloud instances, if a member exists and their status is pending and the expiration date is reached, @@ -100,12 +100,12 @@ func (s *service) AddNamespaceMember(ctx context.Context, req *requests.Namespac return nil, err } } else { - if err := s.store.WithTransaction(ctx, s.addMember(passiveUser.ID, req)); err != nil { + if err := s.addMember(passiveUser.ID, req)(ctx); err != nil { return nil, err } } - return s.store.NamespaceGet(ctx, req.TenantID, s.store.Options().CountAcceptedDevices(), s.store.Options().EnrichMembersData()) + return s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) } // addMember returns a transaction callback that adds a member and sends an invite if the instance is cloud. @@ -126,7 +126,7 @@ func (s *service) addMember(memberID string, req *requests.NamespaceAddMember) s member.ExpiresAt = time.Time{} } - if err := s.store.NamespaceAddMember(ctx, req.TenantID, member); err != nil { + if err := s.store.NamespaceCreateMemberships(ctx, req.TenantID, *member); err != nil { return err } @@ -144,24 +144,24 @@ func (s *service) addMember(memberID string, req *requests.NamespaceAddMember) s // specified ID. func (s *service) resendMemberInvite(memberID string, req *requests.NamespaceAddMember) store.TransactionCb { return func(ctx context.Context) error { - expiresAt := clock.Now().Add(7 * (24 * time.Hour)) - changes := &models.MemberChanges{ExpiresAt: &expiresAt, Role: req.MemberRole} + // expiresAt := clock.Now().Add(7 * (24 * time.Hour)) + // changes := &models.MemberChanges{ExpiresAt: &expiresAt, Role: req.MemberRole} - if err := s.store.NamespaceUpdateMember(ctx, req.TenantID, memberID, changes); err != nil { - return err - } + // if err := s.store.NamespaceUpdateMember(ctx, req.TenantID, memberID, changes); err != nil { + // return err + // } return s.client.InviteMember(ctx, req.TenantID, memberID, req.FowardedHost) } } func (s *service) UpdateNamespaceMember(ctx context.Context, req *requests.NamespaceUpdateMember) error { - namespace, err := s.store.NamespaceGet(ctx, req.TenantID) + namespace, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) if err != nil { return NewErrNamespaceNotFound(req.TenantID, err) } - user, _, err := s.store.UserGetByID(ctx, req.UserID, false) + user, err := s.store.UserGet(ctx, store.UserIdentID, req.UserID) if err != nil { return NewErrUserNotFound(req.UserID, err) } @@ -171,19 +171,20 @@ func (s *service) UpdateNamespaceMember(ctx context.Context, req *requests.Names return NewErrNamespaceMemberNotFound(user.ID, err) } - if _, ok := namespace.FindMember(req.MemberID); !ok { + member, ok := namespace.FindMember(req.MemberID) + if !ok { return NewErrNamespaceMemberNotFound(req.MemberID, err) } - changes := &models.MemberChanges{Role: req.MemberRole} - - if changes.Role != authorizer.RoleInvalid { + if req.MemberRole != authorizer.RoleInvalid { if !active.Role.HasAuthority(req.MemberRole) { return NewErrRoleInvalid() } + + member.Role = req.MemberRole } - if err := s.store.NamespaceUpdateMember(ctx, req.TenantID, req.MemberID, changes); err != nil { + if err := s.store.NamespaceSaveMembership(ctx, req.TenantID, member); err != nil { return err } @@ -193,12 +194,12 @@ func (s *service) UpdateNamespaceMember(ctx context.Context, req *requests.Names } func (s *service) RemoveNamespaceMember(ctx context.Context, req *requests.NamespaceRemoveMember) (*models.Namespace, error) { - namespace, err := s.store.NamespaceGet(ctx, req.TenantID) + namespace, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) if err != nil { return nil, NewErrNamespaceNotFound(req.TenantID, err) } - user, _, err := s.store.UserGetByID(ctx, req.UserID, false) + user, err := s.store.UserGet(ctx, store.UserIdentID, req.UserID) if err != nil { return nil, NewErrUserNotFound(req.UserID, err) } @@ -217,7 +218,7 @@ func (s *service) RemoveNamespaceMember(ctx context.Context, req *requests.Names return nil, NewErrRoleInvalid() } - if err := s.removeMember(ctx, namespace, req.MemberID); err != nil { //nolint:revive + if err := s.store.NamespaceDeleteMembership(ctx, req.TenantID, passive); err != nil { return nil, err } @@ -228,20 +229,26 @@ func (s *service) RemoveNamespaceMember(ctx context.Context, req *requests.Names Error("failed to uncache the token") } - return s.store.NamespaceGet(ctx, req.TenantID, s.store.Options().CountAcceptedDevices(), s.store.Options().EnrichMembersData()) + return s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) } func (s *service) LeaveNamespace(ctx context.Context, req *requests.LeaveNamespace) (*models.UserAuthResponse, error) { - ns, err := s.store.NamespaceGet(ctx, req.TenantID) + ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) if err != nil { return nil, NewErrNamespaceNotFound(req.TenantID, err) } - if m, ok := ns.FindMember(req.UserID); !ok || m.Role == authorizer.RoleOwner { + user, err := s.store.UserGet(ctx, store.UserIdentID, req.UserID) + if err != nil || user == nil { + return nil, NewErrUserNotFound(req.UserID, err) + } + + member, ok := ns.FindMember(user.ID) + if !ok || member.Role == authorizer.RoleOwner { return nil, NewErrAuthForbidden() } - if err := s.removeMember(ctx, ns, req.UserID); err != nil { //nolint:revive + if err := s.store.NamespaceDeleteMembership(ctx, req.TenantID, member); err != nil { return nil, err } @@ -251,8 +258,8 @@ func (s *service) LeaveNamespace(ctx context.Context, req *requests.LeaveNamespa return nil, nil } - emptyString := "" // just to be used as a pointer - if err := s.store.UserUpdate(ctx, req.UserID, &models.UserChanges{PreferredNamespace: &emptyString}); err != nil { + user.Preferences.PreferredNamespace = "" + if err := s.store.UserSave(ctx, user); err != nil { log.WithError(err). WithField("tenant_id", req.TenantID). WithField("user_id", req.UserID). @@ -269,16 +276,3 @@ func (s *service) LeaveNamespace(ctx context.Context, req *requests.LeaveNamespa // TODO: make this method a util function return s.CreateUserToken(ctx, &requests.CreateUserToken{UserID: req.UserID}) } - -func (s *service) removeMember(ctx context.Context, ns *models.Namespace, userID string) error { - if err := s.store.NamespaceRemoveMember(ctx, ns.TenantID, userID); err != nil { - switch { - case errors.Is(err, store.ErrNoDocuments): - return NewErrNamespaceNotFound(ns.TenantID, err) - default: - return err - } - } - - return nil -} diff --git a/api/services/namespace.go b/api/services/namespace.go index 95de4fe6b4e..6f7d6ba9f95 100644 --- a/api/services/namespace.go +++ b/api/services/namespace.go @@ -2,13 +2,11 @@ package services import ( "context" - "errors" "strings" "github.com/shellhub-io/shellhub/api/store" "github.com/shellhub-io/shellhub/pkg/api/authorizer" "github.com/shellhub-io/shellhub/pkg/api/requests" - "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/envs" "github.com/shellhub-io/shellhub/pkg/models" "github.com/shellhub-io/shellhub/pkg/uuid" @@ -25,7 +23,7 @@ type NamespaceService interface { // CreateNamespace creates a new namespace. func (s *service) CreateNamespace(ctx context.Context, req *requests.NamespaceCreate) (*models.Namespace, error) { - user, _, err := s.store.UserGetByID(ctx, req.UserID, false) + user, err := s.store.UserGet(ctx, store.UserIdentID, req.UserID) if err != nil || user == nil { return nil, NewErrUserNotFound(req.UserID, err) } @@ -44,61 +42,63 @@ func (s *service) CreateNamespace(ctx context.Context, req *requests.NamespaceCr } } - if dup, err := s.store.NamespaceGetByName(ctx, strings.ToLower(req.Name)); dup != nil || (err != nil && err != store.ErrNoDocuments) { + req.Name = strings.ToLower(req.Name) + + if _, has, err := s.store.NamespaceConflicts(ctx, &models.NamespaceConflicts{Name: req.Name}); err != nil || has { return nil, NewErrNamespaceDuplicated(err) } + if req.TenantID == "" { + req.TenantID = uuid.Generate() + } + ns := &models.Namespace{ - Name: strings.ToLower(req.Name), - Owner: user.ID, + TenantID: req.TenantID, + Type: models.TypeTeam, + Name: strings.ToLower(req.Name), + Settings: &models.NamespaceSettings{}, Members: []models.Member{ { - ID: user.ID, - Role: authorizer.RoleOwner, - Status: models.MemberStatusAccepted, - AddedAt: clock.Now(), + ID: user.ID, + Status: models.MemberStatusAccepted, + Role: authorizer.RoleOwner, }, }, - Settings: &models.NamespaceSettings{ - SessionRecord: true, - ConnectionAnnouncement: "", - }, - TenantID: req.TenantID, - Type: models.NewDefaultType(), - } - - if envs.IsCommunity() { - ns.Settings.ConnectionAnnouncement = models.DefaultAnnouncementMessage - } - - if models.IsTypeTeam(req.Type) { - ns.Type = models.TypeTeam - } else if models.IsTypePersonal(req.Type) { - ns.Type = models.TypePersonal - } - - if req.TenantID == "" { - ns.TenantID = uuid.Generate() } // Set limits according to ShellHub instance type if envs.IsCloud() { // cloud free plan is limited only by the max of devices ns.MaxDevices = 3 + ns.Settings.ConnectionAnnouncement = "" + ns.Settings.SessionRecord = true } else { // we don't set limits on enterprise and community instances ns.MaxDevices = -1 + ns.Settings.ConnectionAnnouncement = models.DefaultAnnouncementMessage + ns.Settings.SessionRecord = envs.IsEnterprise() } + // TODO: transactions if _, err := s.store.NamespaceCreate(ctx, ns); err != nil { return nil, NewErrNamespaceCreateStore(err) } + if err := s.store.NamespaceCreateMemberships(ctx, ns.TenantID, ns.Members...); err != nil { + return nil, err + } + return ns, nil } func (s *service) ListNamespaces(ctx context.Context, req *requests.NamespaceList) ([]models.Namespace, int, error) { - namespaces, count, err := s.store.NamespaceList(ctx, req.Paginator, req.Filters, s.store.Options().CountAcceptedDevices(), s.store.Options().EnrichMembersData()) + namespaces, count, err := s.store.NamespaceList( + ctx, + s.store.Options().WithMember(req.UserID), + s.store.Options().Paginate(req.Paginator), + s.store.Options().Order(req.Sorter), + s.store.Options().Filter(req.Filters), + ) if err != nil { return nil, 0, NewErrNamespaceList(err) } @@ -112,7 +112,7 @@ func (s *service) ListNamespaces(ctx context.Context, req *requests.NamespaceLis // // GetNamespace returns a models.Namespace and an error. When error is not nil, the models.Namespace is nil. func (s *service) GetNamespace(ctx context.Context, tenantID string) (*models.Namespace, error) { - namespace, err := s.store.NamespaceGet(ctx, tenantID, s.store.Options().CountAcceptedDevices(), s.store.Options().EnrichMembersData()) + namespace, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenantID) if err != nil || namespace == nil { return nil, NewErrNamespaceNotFound(tenantID, err) } @@ -127,7 +127,7 @@ func (s *service) GetNamespace(ctx context.Context, tenantID string) (*models.Na // When cloud and billing is enabled, it will try to delete the namespace's billing information from the billing // service if it exists. func (s *service) DeleteNamespace(ctx context.Context, tenantID string) error { - ns, err := s.store.NamespaceGet(ctx, tenantID, s.store.Options().CountAcceptedDevices()) + ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenantID) if err != nil { return NewErrNamespaceNotFound(tenantID, err) } @@ -142,26 +142,32 @@ func (s *service) DeleteNamespace(ctx context.Context, tenantID string) error { } } - return s.store.NamespaceDelete(ctx, tenantID) + return s.store.NamespaceDelete(ctx, ns) } func (s *service) EditNamespace(ctx context.Context, req *requests.NamespaceEdit) (*models.Namespace, error) { - changes := &models.NamespaceChanges{ - Name: strings.ToLower(req.Name), - SessionRecord: req.Settings.SessionRecord, - ConnectionAnnouncement: req.Settings.ConnectionAnnouncement, + ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, req.TenantID) + if err != nil { + return nil, NewErrNamespaceNotFound(req.TenantID, err) } - if err := s.store.NamespaceEdit(ctx, req.Tenant, changes); err != nil { - switch { - case errors.Is(err, store.ErrNoDocuments): - return nil, NewErrNamespaceNotFound(req.Tenant, err) - default: - return nil, err - } + if req.Name != "" { + ns.Name = strings.ToLower(req.Name) + } + + if req.Settings.SessionRecord != nil { + ns.Settings.SessionRecord = *req.Settings.SessionRecord } - return s.store.NamespaceGet(ctx, req.Tenant, s.store.Options().CountAcceptedDevices(), s.store.Options().EnrichMembersData()) + if req.Settings.ConnectionAnnouncement != nil { + ns.Settings.ConnectionAnnouncement = *req.Settings.ConnectionAnnouncement + } + + if err := s.store.NamespaceSave(ctx, ns); err != nil { + return nil, err + } + + return ns, nil } // EditSessionRecordStatus defines if the sessions will be recorded. @@ -169,7 +175,8 @@ func (s *service) EditNamespace(ctx context.Context, req *requests.NamespaceEdit // It receives a context, used to "control" the request flow, a boolean to define if the sessions will be recorded and // the tenant ID from models.Namespace. func (s *service) EditSessionRecordStatus(ctx context.Context, sessionRecord bool, tenantID string) error { - return s.store.NamespaceSetSessionRecord(ctx, sessionRecord, tenantID) + // return s.store.NamespaceSetSessionRecord(ctx, sessionRecord, tenantID) + return nil } // GetSessionRecord gets the session record data. @@ -179,9 +186,10 @@ func (s *service) EditSessionRecordStatus(ctx context.Context, sessionRecord boo // GetSessionRecord returns a boolean indicating the session record status and an error. When error is not nil, // the boolean is false. func (s *service) GetSessionRecord(ctx context.Context, tenantID string) (bool, error) { - if _, err := s.store.NamespaceGet(ctx, tenantID); err != nil { - return false, NewErrNamespaceNotFound(tenantID, err) - } - - return s.store.NamespaceGetSessionRecord(ctx, tenantID) + // if _, err := s.store.NamespaceGet(ctx, store.NamespaceIdentID, tenantID); err != nil { + // return false, NewErrNamespaceNotFound(tenantID, err) + // } + // + // return s.store.NamespaceGetSessionRecord(ctx, tenantID) + return false, nil } diff --git a/api/services/setup.go b/api/services/setup.go index 14bf289a0c3..34cdb65121a 100644 --- a/api/services/setup.go +++ b/api/services/setup.go @@ -14,6 +14,7 @@ import ( "github.com/shellhub-io/shellhub/pkg/api/authorizer" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/clock" + "github.com/shellhub-io/shellhub/pkg/hash" "github.com/shellhub-io/shellhub/pkg/models" "github.com/shellhub-io/shellhub/pkg/uuid" ) @@ -30,36 +31,28 @@ func (s *service) Setup(ctx context.Context, req requests.Setup) error { return NewErrSetupForbidden(err) } - data := models.UserData{ - Name: req.Name, - Email: req.Email, - Username: req.Username, - RecoveryEmail: "", - } - - if ok, err := s.validator.Struct(data); !ok || err != nil { - return NewErrUserInvalid(nil, err) - } - - password, err := models.HashUserPassword(req.Password) + pwdDigest, err := hash.Do(req.Password) if err != nil { return NewErrUserPasswordInvalid(err) } - if ok, err := s.validator.Struct(password); !ok || err != nil { + if ok, err := s.validator.Struct(pwdDigest); !ok || err != nil { return NewErrUserPasswordInvalid(err) } user := &models.User{ - Origin: models.UserOriginLocal, - UserData: data, - Password: password, + Origin: models.UserOriginLocal, + Name: req.Name, + Email: req.Email, + Username: req.Username, + PasswordDigest: pwdDigest, // NOTE: user's created from the setup screen doesn't need to be confirmed. Status: models.UserStatusConfirmed, CreatedAt: clock.Now(), MaxNamespaces: -1, Preferences: models.UserPreferences{ - AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + RecoveryEmail: "", }, } @@ -90,7 +83,7 @@ func (s *service) Setup(ctx context.Context, req requests.Setup) error { } if _, err = s.store.NamespaceCreate(ctx, namespace); err != nil { - if err := s.store.UserDelete(ctx, insertedID); err != nil { + if err := s.store.NamespaceDelete(ctx, namespace); err != nil { return NewErrUserDelete(err) } diff --git a/api/services/sshkeys.go b/api/services/sshkeys.go index 0f7ba89b065..3127c9aab5d 100644 --- a/api/services/sshkeys.go +++ b/api/services/sshkeys.go @@ -9,7 +9,6 @@ import ( "regexp" "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/api/responses" "github.com/shellhub-io/shellhub/pkg/clock" @@ -20,7 +19,7 @@ import ( type SSHKeysService interface { EvaluateKeyFilter(ctx context.Context, key *models.PublicKey, dev models.Device) (bool, error) EvaluateKeyUsername(ctx context.Context, key *models.PublicKey, username string) (bool, error) - ListPublicKeys(ctx context.Context, paginator query.Paginator) ([]models.PublicKey, int, error) + ListPublicKeys(ctx context.Context, req *requests.PublicKeyList) ([]models.PublicKey, int, error) GetPublicKey(ctx context.Context, fingerprint, tenant string) (*models.PublicKey, error) CreatePublicKey(ctx context.Context, req requests.PublicKeyCreate, tenant string) (*responses.PublicKeyCreate, error) UpdatePublicKey(ctx context.Context, fingerprint, tenant string, key requests.PublicKeyUpdate) (*models.PublicKey, error) @@ -67,11 +66,11 @@ func (s *service) EvaluateKeyUsername(_ context.Context, key *models.PublicKey, } func (s *service) GetPublicKey(ctx context.Context, fingerprint, tenant string) (*models.PublicKey, error) { - if _, err := s.store.NamespaceGet(ctx, tenant); err != nil { + if _, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenant); err != nil { return nil, NewErrNamespaceNotFound(tenant, err) } - return s.store.PublicKeyGet(ctx, fingerprint, tenant) + return s.store.PublicKeyGet(ctx, store.PublicKeyIdentFingerprint, fingerprint, tenant) } func (s *service) CreatePublicKey(ctx context.Context, req requests.PublicKeyCreate, tenant string) (*responses.PublicKeyCreate, error) { @@ -97,7 +96,7 @@ func (s *service) CreatePublicKey(ctx context.Context, req requests.PublicKeyCre req.Fingerprint = ssh.FingerprintLegacyMD5(pubKey) - returnedKey, err := s.store.PublicKeyGet(ctx, req.Fingerprint, tenant) + returnedKey, err := s.store.PublicKeyGet(ctx, store.PublicKeyIdentFingerprint, req.Fingerprint, tenant) if err != nil && err != store.ErrNoDocuments { return nil, NewErrPublicKeyNotFound(req.Fingerprint, err) } @@ -121,8 +120,7 @@ func (s *service) CreatePublicKey(ctx context.Context, req requests.PublicKeyCre }, } - err = s.store.PublicKeyCreate(ctx, &model) - if err != nil { + if _, err := s.store.PublicKeyCreate(ctx, &model); err != nil { return nil, err } @@ -136,14 +134,36 @@ func (s *service) CreatePublicKey(ctx context.Context, req requests.PublicKeyCre }, nil } -func (s *service) ListPublicKeys(ctx context.Context, paginator query.Paginator) ([]models.PublicKey, int, error) { - return s.store.PublicKeyList(ctx, paginator) +func (s *service) ListPublicKeys(ctx context.Context, req *requests.PublicKeyList) ([]models.PublicKey, int, error) { + return s.store.PublicKeyList( + ctx, + s.store.Options().InNamespace(req.TenantID), + s.store.Options().Paginate(req.Paginator), + s.store.Options().Order(req.Sorter), + ) } func (s *service) UpdatePublicKey(ctx context.Context, fingerprint, tenant string, key requests.PublicKeyUpdate) (*models.PublicKey, error) { - // Checks if public key filter type is Tags. If it is, checks if there are, at least, one tag on the public key - // filter and if the all tags exist on database. - if key.Filter.Tags != nil { + publicKey, err := s.store.PublicKeyGet(ctx, store.PublicKeyIdentFingerprint, fingerprint, tenant) + if err != nil { + return nil, NewErrPublicKeyNotFound(fingerprint, err) + } + + if key.Name != "" { + publicKey.Name = key.Name + } + + if key.Username != "" { + publicKey.Username = key.Username + } + + if key.Filter.Hostname != "" { + publicKey.Filter.Hostname = key.Filter.Hostname + } + + if len(key.Filter.Tags) > 0 { + // Checks if public key filter type is Tags. If it is, checks if there are, at least, one tag on the public key + // filter and if the all tags exist on database. tags, _, err := s.store.TagsGet(ctx, tenant) if err != nil { return nil, NewErrTagEmpty(tenant, err) @@ -156,30 +176,24 @@ func (s *service) UpdatePublicKey(ctx context.Context, fingerprint, tenant strin } } - model := models.PublicKeyUpdate{ - PublicKeyFields: models.PublicKeyFields{ - Name: key.Name, - Username: key.Username, - Filter: models.PublicKeyFilter{ - Hostname: key.Filter.Hostname, - Tags: key.Filter.Tags, - }, - }, + if err := s.store.PublicKeySave(ctx, publicKey); err != nil { + return nil, err } - return s.store.PublicKeyUpdate(ctx, fingerprint, tenant, &model) + return publicKey, nil } func (s *service) DeletePublicKey(ctx context.Context, fingerprint, tenant string) error { - if _, err := s.store.NamespaceGet(ctx, tenant); err != nil { + if _, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenant); err != nil { return NewErrNamespaceNotFound(tenant, err) } - if _, err := s.store.PublicKeyGet(ctx, fingerprint, tenant); err != nil { + publicKey, err := s.store.PublicKeyGet(ctx, store.PublicKeyIdentFingerprint, fingerprint, tenant) + if err != nil { return NewErrPublicKeyNotFound(fingerprint, err) } - return s.store.PublicKeyDelete(ctx, fingerprint, tenant) + return s.store.PublicKeyDelete(ctx, publicKey) } func (s *service) CreatePrivateKey(ctx context.Context) (*models.PrivateKey, error) { diff --git a/api/services/sshkeys_tags.go b/api/services/sshkeys_tags.go index 2c37b617e71..442855ec142 100644 --- a/api/services/sshkeys_tags.go +++ b/api/services/sshkeys_tags.go @@ -2,8 +2,6 @@ package services import ( "context" - - "github.com/shellhub-io/shellhub/api/store" ) type SSHKeysTagsService interface { @@ -16,74 +14,11 @@ type SSHKeysTagsService interface { // // It checks if the models.Namespace and models.PublicKey exists and try to perform the addition action. func (s *service) AddPublicKeyTag(ctx context.Context, tenant, fingerprint, tag string) error { - if _, err := s.store.NamespaceGet(ctx, tenant); err != nil { - return NewErrNamespaceNotFound(tenant, err) - } - - // Checks if the public key exists. - key, err := s.store.PublicKeyGet(ctx, fingerprint, tenant) - if err != nil || key == nil { - return NewErrPublicKeyNotFound(fingerprint, err) - } - - if key.Filter.Hostname != "" { - return NewErrPublicKeyFilter(nil) - } - - if len(key.Filter.Tags) == DeviceMaxTags { - return NewErrTagLimit(DeviceMaxTags, nil) - } - - tags, _, err := s.store.TagsGet(ctx, tenant) - if err != nil { - return NewErrTagEmpty(tenant, err) - } - - if !contains(tags, tag) { - return NewErrTagNotFound(tag, nil) - } - - // Trys to add a public key. - err = s.store.PublicKeyPushTag(ctx, tenant, fingerprint, tag) - if err != nil { - switch err { - case store.ErrNoDocuments: - return ErrDuplicateTagName - default: - return err - } - } - return nil } // RemovePublicKeyTag trys to remove a tag from the models.PublicKey, when its filter is from Tags type. func (s *service) RemovePublicKeyTag(ctx context.Context, tenant, fingerprint, tag string) error { - if _, err := s.store.NamespaceGet(ctx, tenant); err != nil { - return NewErrNamespaceNotFound(tenant, nil) - } - - // Checks if the public key exists. - key, err := s.store.PublicKeyGet(ctx, fingerprint, tenant) - if err != nil || key == nil { - return NewErrPublicKeyNotFound(fingerprint, err) - } - - if key.Filter.Hostname != "" { - return NewErrPublicKeyFilter(nil) - } - - // Checks if the tag already exists in the device. - if !contains(key.Filter.Tags, tag) { - return NewErrTagNotFound(tag, nil) - } - - // Trys to remove a public key. - err = s.store.PublicKeyPullTag(ctx, tenant, fingerprint, tag) - if err != nil { - return err - } - return nil } @@ -91,52 +26,5 @@ func (s *service) RemovePublicKeyTag(ctx context.Context, tenant, fingerprint, t // // It checks if the models.Namespace and models.PublicKey exists and try to perform the update action. func (s *service) UpdatePublicKeyTags(ctx context.Context, tenant, fingerprint string, tags []string) error { - if len(tags) > DeviceMaxTags { - return NewErrTagLimit(DeviceMaxTags, nil) - } - - set := func(list []string) []string { - state := make(map[string]bool) - helper := make([]string, 0) - for _, item := range list { - if _, ok := state[item]; !ok { - state[item] = true - helper = append(helper, item) - } - } - - return helper - } - - tags = set(tags) - - if _, err := s.store.NamespaceGet(ctx, tenant); err != nil { - return NewErrNamespaceNotFound(tenant, nil) - } - - key, err := s.store.PublicKeyGet(ctx, fingerprint, tenant) - if err != nil || key == nil { - return NewErrPublicKeyNotFound(fingerprint, err) - } - - if key.Filter.Hostname != "" { - return NewErrPublicKeyNotFound(fingerprint, nil) - } - - allTags, _, err := s.store.TagsGet(ctx, tenant) - if err != nil { - return NewErrTagEmpty(tenant, err) - } - - for _, tag := range tags { - if !contains(allTags, tag) { - return NewErrTagNotFound(tag, nil) - } - } - - if _, _, err := s.store.PublicKeySetTags(ctx, tenant, fingerprint, tags); err != nil { - return err - } - return nil } diff --git a/api/services/system.go b/api/services/system.go index fad8496de8b..deb35a373da 100644 --- a/api/services/system.go +++ b/api/services/system.go @@ -19,24 +19,19 @@ type SystemService interface { } func (s *service) GetSystemInfo(ctx context.Context, req *requests.GetSystemInfo) (*responses.SystemInfo, error) { - system, err := s.store.SystemGet(ctx) - if err != nil { - return nil, err - } - apiHost := strings.Split(req.Host, ":")[0] sshPort := envs.DefaultBackend.Get("SHELLHUB_SSH_PORT") resp := &responses.SystemInfo{ Version: envs.DefaultBackend.Get("SHELLHUB_VERSION"), - Setup: system.Setup, + Setup: true, Endpoints: &responses.SystemEndpointsInfo{ API: apiHost, SSH: fmt.Sprintf("%s:%s", apiHost, sshPort), }, Authentication: &responses.SystemAuthenticationInfo{ - Local: system.Authentication.Local.Enabled, - SAML: system.Authentication.SAML.Enabled, + Local: true, + SAML: false, }, } diff --git a/api/services/tags.go b/api/services/tags.go index d43ddafcaf8..4821addca77 100644 --- a/api/services/tags.go +++ b/api/services/tags.go @@ -3,6 +3,7 @@ package services import ( "context" + "github.com/shellhub-io/shellhub/api/store" "github.com/shellhub-io/shellhub/pkg/models" ) @@ -13,7 +14,7 @@ type TagsService interface { } func (s *service) GetTags(ctx context.Context, tenant string) ([]string, int, error) { - namespace, err := s.store.NamespaceGet(ctx, tenant) + namespace, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenant) if err != nil || namespace == nil { return nil, 0, NewErrNamespaceNotFound(tenant, err) } @@ -49,7 +50,7 @@ func (s *service) DeleteTag(ctx context.Context, tenant string, tag string) erro return NewErrTagInvalid(tag, err) } - namespace, err := s.store.NamespaceGet(ctx, tenant) + namespace, err := s.store.NamespaceGet(ctx, store.NamespaceIdentTenantID, tenant) if err != nil || namespace == nil { return NewErrNamespaceNotFound(tenant, err) } diff --git a/api/services/task.go b/api/services/task.go index 28eb7400564..125323655b1 100644 --- a/api/services/task.go +++ b/api/services/task.go @@ -5,9 +5,8 @@ import ( "bytes" "context" "slices" + "time" - "github.com/shellhub-io/shellhub/pkg/clock" - "github.com/shellhub-io/shellhub/pkg/models" "github.com/shellhub-io/shellhub/pkg/worker" log "github.com/sirupsen/logrus" ) @@ -39,7 +38,7 @@ func (s *service) DevicesHeartbeat() worker.TaskHandler { slices.Sort(uids) uids = slices.Compact(uids) - mCount, err := s.store.DeviceBulkUpdate(ctx, uids, &models.DeviceChanges{LastSeen: clock.Now(), DisconnectedAt: nil}) + mCount, err := s.store.DeviceUpdateSeenAt(ctx, uids, time.Now()) if err != nil { log.WithField("task", TaskDevicesHeartbeat.String()). WithError(err). diff --git a/api/services/user.go b/api/services/user.go index 3ecf8f3c085..e7935253087 100644 --- a/api/services/user.go +++ b/api/services/user.go @@ -4,8 +4,12 @@ import ( "context" "strings" + "github.com/shellhub-io/shellhub/api/store" "github.com/shellhub-io/shellhub/pkg/api/requests" + "github.com/shellhub-io/shellhub/pkg/hash" "github.com/shellhub-io/shellhub/pkg/models" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) type UserService interface { @@ -22,7 +26,7 @@ type UserService interface { } func (s *service) UpdateUser(ctx context.Context, req *requests.UpdateUser) ([]string, error) { - user, _, err := s.store.UserGetByID(ctx, req.UserID, false) + user, err := s.store.UserGet(ctx, store.UserIdentID, req.UserID) if err != nil { return []string{}, NewErrUserNotFound(req.UserID, nil) } @@ -37,24 +41,33 @@ func (s *service) UpdateUser(ctx context.Context, req *requests.UpdateUser) ([]s return conflicts, NewErrUserDuplicated(conflicts, nil) } - changes := &models.UserChanges{ - Name: req.Name, - Username: strings.ToLower(req.Username), - Email: strings.ToLower(req.Email), - RecoveryEmail: strings.ToLower(req.RecoveryEmail), + if req.Name != "" { + user.Name = cases.Title(language.AmericanEnglish).String(strings.ToLower(req.Name)) + } + + if req.Username != "" { + user.Username = strings.ToLower(req.Username) + } + + if req.Email != "" { + user.Email = strings.ToLower(req.Email) + } + + if req.RecoveryEmail != "" { + user.Preferences.RecoveryEmail = strings.ToLower(req.RecoveryEmail) } if req.Password != "" { // TODO: test - if !user.Password.Compare(req.CurrentPassword) { + if !hash.CompareWith(req.CurrentPassword, user.PasswordDigest) { return []string{}, NewErrUserPasswordNotMatch(nil) } - neo, _ := models.HashUserPassword(req.Password) - changes.Password = neo.Hash + pwdDigest, _ := hash.Do(req.Password) + user.PasswordDigest = pwdDigest } - if err := s.store.UserUpdate(ctx, req.UserID, changes); err != nil { + if err := s.store.UserSave(ctx, user); err != nil { return []string{}, NewErrUserUpdate(user, err) } @@ -65,21 +78,23 @@ func (s *service) UpdateUser(ctx context.Context, req *requests.UpdateUser) ([]s // // Deprecated, use [Service.UpdateUser] instead. func (s *service) UpdatePasswordUser(ctx context.Context, id, currentPassword, newPassword string) error { - user, _, err := s.store.UserGetByID(ctx, id, false) + user, err := s.store.UserGet(ctx, store.UserIdentID, id) if user == nil { return NewErrUserNotFound(id, err) } - if !user.Password.Compare(currentPassword) { + if !hash.CompareWith(currentPassword, user.PasswordDigest) { return NewErrUserPasswordNotMatch(nil) } - neo, err := models.HashUserPassword(newPassword) + pwdDigest, err := hash.Do(newPassword) if err != nil { return NewErrUserPasswordInvalid(err) } - if err := s.store.UserUpdate(ctx, id, &models.UserChanges{Password: neo.Hash}); err != nil { + user.PasswordDigest = pwdDigest + + if err := s.store.UserSave(ctx, user); err != nil { return NewErrUserUpdate(user, err) } diff --git a/api/store/api_key.go b/api/store/api_key.go index 1cb88f79e00..edd0450a313 100644 --- a/api/store/api_key.go +++ b/api/store/api_key.go @@ -3,20 +3,20 @@ package store import ( "context" - "github.com/shellhub-io/shellhub/pkg/api/query" "github.com/shellhub-io/shellhub/pkg/models" ) +type APIKeyIdent string + +const ( + APIKeyIdentID APIKeyIdent = "key_digest" + APIKeyIdentName APIKeyIdent = "name" +) + type APIKeyStore interface { // APIKeyCreate creates an API key with the provided data. Returns the inserted ID and an error if any. APIKeyCreate(ctx context.Context, APIKey *models.APIKey) (insertedID string, err error) - // APIKeyGet retrieves an API key based on its ID. Returns the API key and an error if any. - APIKeyGet(ctx context.Context, id string) (apiKey *models.APIKey, err error) - - // APIKeyGetByName retrieves an API key based on its name and tenant ID. Returns the API key and an error if any. - APIKeyGetByName(ctx context.Context, tenantID string, name string) (apiKey *models.APIKey, err error) - // APIKeyConflicts reports whether the target contains conflicting attributes with the database. Pass zero values for // attributes you do not wish to match on. It returns an array of conflicting attribute fields and an error, if any. // @@ -25,13 +25,11 @@ type APIKeyStore interface { // APIKeyList retrieves a list of API keys for the specified tenant using the given paginator and sorter values. // Returns the list of API keys, the total count of matched documents, and an error if any. - APIKeyList(ctx context.Context, tenantID string, paginator query.Paginator, sorter query.Sorter) (apiKeys []models.APIKey, count int, err error) + APIKeyList(ctx context.Context, opts ...QueryOption) (apiKeys []models.APIKey, count int, err error) + + APIKeyGet(ctx context.Context, ident APIKeyIdent, val string, tenantID string) (*models.APIKey, error) - // APIKeyUpdate updates an API key with the specified name and tenant ID using the given changes. - // Any zero values in the changes (e.g., empty strings) will be ignored during the update. - // Returns an error if any. - APIKeyUpdate(ctx context.Context, tenantID, name string, changes *models.APIKeyChanges) (err error) + APIKeySave(ctx context.Context, apiKey *models.APIKey) error - // APIKeyDelete deletes an API key with the specified name and tenant ID. Returns an error if any. - APIKeyDelete(ctx context.Context, tenantID, name string) (err error) + APIKeyDelete(ctx context.Context, apiKey *models.APIKey) error } diff --git a/api/store/device.go b/api/store/device.go index 4306a59ace3..1fe7a0c1560 100644 --- a/api/store/device.go +++ b/api/store/device.go @@ -2,8 +2,8 @@ package store import ( "context" + "time" - "github.com/shellhub-io/shellhub/pkg/api/query" "github.com/shellhub-io/shellhub/pkg/models" ) @@ -19,39 +19,19 @@ const ( DeviceAcceptableAsFalse ) -type DeviceStore interface { - DeviceList(ctx context.Context, status models.DeviceStatus, pagination query.Paginator, filters query.Filters, sorter query.Sorter, acceptable DeviceAcceptable) ([]models.Device, int, error) - DeviceGet(ctx context.Context, uid models.UID) (*models.Device, error) - - // DeviceConflicts reports whether the target contains conflicting attributes with the database. Pass zero values for - // attributes you do not wish to match on. For example, the following call checks for conflicts based on email only: - // - // ctx := context.Background() - // conflicts, has, err := store.DeviceConflicts(ctx, &models.DeviceConflicts{Name: "mydevice"}) - // - // It returns an array of conflicting attribute fields and an error, if any. - DeviceConflicts(ctx context.Context, target *models.DeviceConflicts) (conflicts []string, has bool, err error) +type DeviceIdent string - // DeviceUpdate updates a device with the specified UID that belongs to the specified namespace. It returns [ErrNoDocuments] if none device is found. - DeviceUpdate(ctx context.Context, tenant, uid string, changes *models.DeviceChanges) error - // DeviceBulkdUpdate updates a list of devices. Different than [DeviceStore.DeviceUpdate], it does not differentiate namespaces. - // It returns the number of modified devices and an error if any. - DeviceBulkUpdate(ctx context.Context, uids []string, changes *models.DeviceChanges) (modifiedCount int64, err error) +const ( + DeviceIdentUID DeviceIdent = "id" + DeviceIdentName DeviceIdent = "name" +) - DeviceDelete(ctx context.Context, uid models.UID) error - DeviceCreate(ctx context.Context, d models.Device, hostname string) error - DeviceRename(ctx context.Context, uid models.UID, hostname string) error - DeviceLookup(ctx context.Context, namespace, hostname string) (*models.Device, error) - DeviceUpdateStatus(ctx context.Context, uid models.UID, status models.DeviceStatus) error - DeviceGetByMac(ctx context.Context, mac string, tenantID string, status models.DeviceStatus) (*models.Device, error) - DeviceGetByName(ctx context.Context, name string, tenantID string, status models.DeviceStatus) (*models.Device, error) - DeviceGetByUID(ctx context.Context, uid models.UID, tenantID string) (*models.Device, error) - DeviceSetPosition(ctx context.Context, uid models.UID, position models.DevicePosition) error - DeviceListByUsage(ctx context.Context, tenantID string) ([]models.UID, error) - DeviceChooser(ctx context.Context, tenantID string, chosen []string) error - DeviceRemovedCount(ctx context.Context, tenant string) (int64, error) - DeviceRemovedGet(ctx context.Context, tenant string, uid models.UID) (*models.DeviceRemoved, error) - DeviceRemovedInsert(ctx context.Context, tenant string, device *models.Device) error - DeviceRemovedDelete(ctx context.Context, tenant string, uid models.UID) error - DeviceRemovedList(ctx context.Context, tenant string, pagination query.Paginator, filters query.Filters, sorter query.Sorter) ([]models.DeviceRemoved, int, error) +type DeviceStore interface { + DeviceCreate(ctx context.Context, device *models.Device) (string, error) + DeviceConflicts(ctx context.Context, target *models.DeviceConflicts) ([]string, bool, error) + DeviceList(ctx context.Context, opts ...QueryOption) ([]models.Device, int, error) + DeviceGet(ctx context.Context, ident DeviceIdent, val string) (*models.Device, error) + DeviceSave(ctx context.Context, device *models.Device) error + DeviceUpdateSeenAt(ctx context.Context, ids []string, to time.Time) (int64, error) + DeviceDelete(ctx context.Context, device *models.Device) error } diff --git a/api/store/device_tags.go b/api/store/device_tags.go deleted file mode 100644 index 479a8483118..00000000000 --- a/api/store/device_tags.go +++ /dev/null @@ -1,33 +0,0 @@ -package store - -import ( - "context" - - "github.com/shellhub-io/shellhub/pkg/models" -) - -type DeviceTagsStore interface { - // DevicePushTag adds a new tag to the list of tags for a device with the specified UID. - // Returns an error if any issues occur during the tag addition or ErrNoDocuments when matching documents are found. - DevicePushTag(ctx context.Context, uid models.UID, tag string) error - - // DevicePullTag removes a tag from the list of tags for a device with the specified UID. - // Returns an error if any issues occur during the tag removal or ErrNoDocuments when matching documents are found. - DevicePullTag(ctx context.Context, uid models.UID, tag string) error - - // DeviceSetTags sets the tags for a device with the specified UID. - // It returns the number of matching documents, the number of modified documents, and any encountered errors. - DeviceSetTags(ctx context.Context, uid models.UID, tags []string) (matchedCount int64, updatedCount int64, err error) - - // DeviceBulkRenameTag replaces all occurrences of the old tag with the new tag for all devices belonging to the specified tenant. - // Returns the number of documents updated and an error if any issues occur during the tag renaming. - DeviceBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (updatedCount int64, err error) - - // DeviceBulkDeleteTag removes a tag from all devices belonging to the specified tenant. - // Returns the number of documents updated and an error if any issues occur during the tag deletion. - DeviceBulkDeleteTag(ctx context.Context, tenant, tag string) (deletedCount int64, err error) - - // DeviceGetTags retrieves all tags associated with the tenant. - // Returns the tags, the number of tags, and an error if any issues occur. - DeviceGetTags(ctx context.Context, tenant string) (tag []string, n int, err error) -} diff --git a/api/store/namespace.go b/api/store/namespace.go index f96dcee339b..bb90eb35ba5 100644 --- a/api/store/namespace.go +++ b/api/store/namespace.go @@ -3,57 +3,47 @@ package store import ( "context" - "github.com/shellhub-io/shellhub/pkg/api/query" "github.com/shellhub-io/shellhub/pkg/models" ) +type NamespaceIdent string + +const ( + NamespaceIdentTenantID NamespaceIdent = "id" + NamespaceIdentName NamespaceIdent = "name" +) + type NamespaceStore interface { - // NamespaceList retrieves a list of namespaces based on the provided filters and pagination settings. - // If the user ID is available in the context, it will only match namespaces that the user is a member - // of and does not have a pending membership status. A list of options can be passed to inject - // additional data into each namespace in the list. - // - // It returns the list of namespaces, the total count of matching documents (ignoring pagination), and - // an error if any. - NamespaceList(ctx context.Context, paginator query.Paginator, filters query.Filters, opts ...NamespaceQueryOption) ([]models.Namespace, int, error) - - // NamespaceGet retrieves a namespace identified by the given tenantID. A list of options can be - // passed to inject additional data into the namespace. - // - // It returns the namespace or an error if any. - NamespaceGet(ctx context.Context, tenantID string, opts ...NamespaceQueryOption) (*models.Namespace, error) - - // NamespaceGetByName retrieves a namespace by its name, similar to [Store.NamespaceGet], but matches by name instead - // of tenantID. - NamespaceGetByName(ctx context.Context, name string, opts ...NamespaceQueryOption) (*models.Namespace, error) - - // NamespaceGetPreferred retrieves the user's preferred namespace. If the user has no preferred namespace it returns - // the first namespace where the user is a member (typically the first one the user was added to). A list of options - // can be passed via `opts` to inject additional data into the namespace. - // - // It returns the namespace or an error if any. - NamespaceGetPreferred(ctx context.Context, userID string, opts ...NamespaceQueryOption) (*models.Namespace, error) - - NamespaceCreate(ctx context.Context, namespace *models.Namespace) (*models.Namespace, error) - - // NamespaceEdit updates a namespace with the specified tenant. - // It returns an error, if any, or store.ErrNoDocuments if the namespace does not exist. - NamespaceEdit(ctx context.Context, tenant string, changes *models.NamespaceChanges) error - - NamespaceUpdate(ctx context.Context, tenantID string, namespace *models.Namespace) error - NamespaceDelete(ctx context.Context, tenantID string) error - - // NamespaceAddMember adds a new member to the namespace with the specified tenantID. - // It returns an error if any. - NamespaceAddMember(ctx context.Context, tenantID string, member *models.Member) error - // NamespaceUpdateMember updates a member with the specified memberID in the namespace with the specified tenantID with - // the changes. It returns an error if any. - NamespaceUpdateMember(ctx context.Context, tenantID string, memberID string, changes *models.MemberChanges) error - // NamespaceRemoveMember removes a member with the specified memberID in the namespace with the specified tenantID. - // If the namespace's tenant ID is the member's preffered tenant ID, it will set the value to an empty string. - // It returns an error if any. - NamespaceRemoveMember(ctx context.Context, tenantID string, memberID string) error - - NamespaceSetSessionRecord(ctx context.Context, sessionRecord bool, tenantID string) error - NamespaceGetSessionRecord(ctx context.Context, tenantID string) (bool, error) + // NamespaceCreate creates a new namespace with the provided data. + // It returns the inserted ID or an error, if any. + NamespaceCreate(ctx context.Context, namespace *models.Namespace) (string, error) + + // NamespaceCreateMemberships creates membership associations between users and a namespace. + // It returns an error, if any. + NamespaceCreateMemberships(ctx context.Context, tenantID string, memberships ...models.Member) error + + // NamespaceConflicts reports whether the target contains conflicting attributes with the database. + // Pass zero values for attributes you do not wish to match on. + // It returns an array of conflicting attribute fields and an error, if any. + NamespaceConflicts(ctx context.Context, target *models.NamespaceConflicts) (conflicts []string, has bool, err error) + + // NamespaceList retrieves a list of namespaces based on the provided query options. + // It returns a list of namespaces, the total count, and an error if any. + NamespaceList(ctx context.Context, opts ...QueryOption) ([]models.Namespace, int, error) + + // NamespaceGet retrieves a namespace based on the provided NamespaceIdent. + // It returns an error if no record was found. + NamespaceGet(ctx context.Context, ident NamespaceIdent, val string, opts ...QueryOption) (*models.Namespace, error) + + // NamespaceSave updates the namespace. + // It returns an error, if any. + NamespaceSave(ctx context.Context, namespace *models.Namespace) (err error) + + NamespaceSaveMembership(ctx context.Context, tenantID string, member *models.Member) (err error) + + // NamespaceDelete deletes the namespace. + // It returns an error, if any. + NamespaceDelete(ctx context.Context, namespace *models.Namespace) (err error) + + NamespaceDeleteMembership(ctx context.Context, tenantID string, member *models.Member) (err error) } diff --git a/api/store/pg/api-key.go b/api/store/pg/api-key.go index 61a22977e49..8e39c66167f 100644 --- a/api/store/pg/api-key.go +++ b/api/store/pg/api-key.go @@ -3,36 +3,81 @@ package pg import ( "context" - "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/api/store/pg/entity" + "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" ) -func (pg *pg) APIKeyCreate(ctx context.Context, APIKey *models.APIKey) (string, error) { - return "", nil -} +func (pg *Pg) APIKeyCreate(ctx context.Context, apiKey *models.APIKey) (string, error) { + apiKey.CreatedAt = clock.Now() + apiKey.UpdatedAt = clock.Now() + + if _, err := pg.driver.NewInsert().Model(entity.APIKeyFromModel(apiKey)).Exec(ctx); err != nil { + return "", fromSqlError(err) + } -func (pg *pg) APIKeyConflicts(ctx context.Context, tenantID string, target *models.APIKeyConflicts) (conflicts []string, has bool, err error) { - return nil, false, nil + return apiKey.ID, nil } -func (pg *pg) APIKeyList(ctx context.Context, tenantID string, paginator query.Paginator, sorter query.Sorter) (apiKeys []models.APIKey, count int, err error) { - return nil, 0, nil +func (pg *Pg) APIKeyConflicts(ctx context.Context, tenantID string, target *models.APIKeyConflicts) ([]string, bool, error) { + apiKeys := make([]map[string]any, 0) + if err := pg.driver.NewSelect().Model((*entity.Namespace)(nil)).Column("name").Where("name = ?", target.Name).Scan(ctx, &apiKeys); err != nil { + return nil, false, fromSqlError(err) + } + + conflicts := make([]string, 0) + for _, apiKey := range apiKeys { + if apiKey["name"] == target.Name { + conflicts = append(conflicts, "name") + } + } + + return conflicts, len(conflicts) > 0, nil } -func (pg *pg) APIKeyGet(ctx context.Context, id string) (apiKey *models.APIKey, err error) { - // TODO: unify get methods - return nil, nil +func (pg *Pg) APIKeyList(ctx context.Context, opts ...store.QueryOption) ([]models.APIKey, int, error) { + entities := make([]entity.APIKey, 0) + + query := pg.driver.NewSelect().Model(&entities) + if err := applyOptions(ctx, query, opts...); err != nil { + return nil, 0, fromSqlError(err) + } + + count, err := query.ScanAndCount(ctx) + if err != nil { + return nil, 0, fromSqlError(err) + } + + apiKeys := make([]models.APIKey, len(entities)) + for i, e := range entities { + apiKeys[i] = *entity.APIKeyToModel(&e) + } + + return apiKeys, count, nil } -func (pg *pg) APIKeyGetByName(ctx context.Context, tenantID string, name string) (apiKey *models.APIKey, err error) { - // TODO: unify get methods - return nil, nil +func (pg *Pg) APIKeyGet(ctx context.Context, ident store.APIKeyIdent, val string, tenantID string) (*models.APIKey, error) { + a := new(entity.APIKey) + if err := pg.driver.NewSelect().Model(a).Where("? = ?", bun.Ident(ident), val).Scan(ctx); err != nil { + return nil, fromSqlError(err) + } + + return entity.APIKeyToModel(a), nil } -func (pg *pg) APIKeyUpdate(ctx context.Context, tenantID, name string, changes *models.APIKeyChanges) (err error) { - return nil +func (pg *Pg) APIKeySave(ctx context.Context, apiKey *models.APIKey) error { + a := entity.APIKeyFromModel(apiKey) + a.UpdatedAt = clock.Now() + _, err := pg.driver.NewUpdate().Model(a).WherePK().Exec(ctx) + + return fromSqlError(err) } -func (pg *pg) APIKeyDelete(ctx context.Context, tenantID, name string) (err error) { - return nil +func (pg *Pg) APIKeyDelete(ctx context.Context, apiKey *models.APIKey) error { + a := entity.APIKeyFromModel(apiKey) + _, err := pg.driver.NewDelete().Model(a).WherePK().Exec(ctx) + + return fromSqlError(err) } diff --git a/api/store/pg/dbtest/dbtest.go b/api/store/pg/dbtest/dbtest.go new file mode 100644 index 00000000000..6397ba2fb7d --- /dev/null +++ b/api/store/pg/dbtest/dbtest.go @@ -0,0 +1,54 @@ +package dbtest + +import ( + "context" + "io" + "log" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" +) + +// Server represents a Postgres test server instance. +type Server struct { + container *postgres.PostgresContainer +} + +// Up starts a new Postgres container. Use [Server.ConnectionString] to access the connection string. +func (srv *Server) Up(ctx context.Context, verbose bool) error { + if !verbose { + testcontainers.Logger = log.New(io.Discard, "", 0) + } + + opts := []testcontainers.ContainerCustomizer{ + postgres.WithDatabase("test"), + postgres.WithUsername("admin"), + postgres.WithPassword("admin"), + testcontainers.WithWaitStrategy(wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5 * time.Second)), + } + + container, err := postgres.Run(ctx, "postgres:17", opts...) + if err != nil { + return err + } + + srv.container = container + + return nil +} + +// Down gracefully terminates the Postgres container. +func (srv *Server) Down(ctx context.Context) error { + return srv.container.Terminate(ctx) +} + +func (srv *Server) ConnectionString(ctx context.Context) (string, error) { + cIP, err := srv.container.ContainerIP(ctx) + if err != nil { + return "", err + } + + return "postgres://admin:admin@" + cIP + ":5432/test", nil +} diff --git a/api/store/pg/dbtest/fixtures.go b/api/store/pg/dbtest/fixtures.go new file mode 100644 index 00000000000..801d04b2141 --- /dev/null +++ b/api/store/pg/dbtest/fixtures.go @@ -0,0 +1,12 @@ +package dbtest + +import ( + "path/filepath" + "runtime" +) + +func FixturesPath() string { + _, file, _, _ := runtime.Caller(0) + + return filepath.Join(filepath.Dir(file), "fixtures") +} diff --git a/api/store/pg/dbtest/fixtures/users.yaml b/api/store/pg/dbtest/fixtures/users.yaml new file mode 100644 index 00000000000..c69ff3bc5b6 --- /dev/null +++ b/api/store/pg/dbtest/fixtures/users.yaml @@ -0,0 +1,18 @@ +- model: User + rows: + - id: 0195cefa-aa01-7efb-8098-c9c173056250 + created_at: 2025-01-15T10:30:00+00:00 + updated_at: 2025-01-15T10:30:00+00:00 + last_login: null + status: confirmed + origin: local + external_id: "" + name: Jonh Doe + username: john_doe + email: john.doe@test.com + security_email: jane.smith@test.com + password_digest: "$2y$12$VVm2ETx7AvaGlfMYqNYK9uzU2M45YZ70YnT..O.s1o2zdE1pekhq6" + auth_methods: [ local ] + namespace_ownership_limit: -1 + email_marketing: true + preferred_namespace_id: null diff --git a/api/store/pg/device.go b/api/store/pg/device.go index 6c6cb5bbdf3..a6586449aee 100644 --- a/api/store/pg/device.go +++ b/api/store/pg/device.go @@ -2,117 +2,136 @@ package pg import ( "context" //nolint:gosec + "time" "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/api/store/pg/entity" + "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" ) -func (pg *pg) DeviceList(ctx context.Context, status models.DeviceStatus, paginator query.Paginator, filters query.Filters, sorter query.Sorter, acceptable store.DeviceAcceptable) ([]models.Device, int, error) { - return nil, 0, nil -} - -func (pg *pg) DeviceGet(ctx context.Context, uid models.UID) (*models.Device, error) { - return nil, nil -} - -func (pg *pg) DeviceDelete(ctx context.Context, uid models.UID) error { - return nil -} - -func (pg *pg) DeviceCreate(ctx context.Context, d models.Device, hostname string) error { - return nil -} - -func (pg *pg) DeviceRename(ctx context.Context, uid models.UID, hostname string) error { - return nil -} - -func (pg *pg) DeviceLookup(ctx context.Context, namespace, hostname string) (*models.Device, error) { - return nil, nil -} - -// DeviceUpdateStatus updates the status of a specific device in the devices collection -func (pg *pg) DeviceUpdateStatus(ctx context.Context, uid models.UID, status models.DeviceStatus) error { - return nil -} - -func (pg *pg) DeviceListByUsage(ctx context.Context, tenant string) ([]models.UID, error) { - return nil, nil -} - -func (pg *pg) DeviceGetByMac(ctx context.Context, mac string, tenantID string, status models.DeviceStatus) (*models.Device, error) { - return nil, nil -} - -func (pg *pg) DeviceGetByName(ctx context.Context, name string, tenantID string, status models.DeviceStatus) (*models.Device, error) { - return nil, nil -} - -func (pg *pg) DeviceGetByUID(ctx context.Context, uid models.UID, tenantID string) (*models.Device, error) { - return nil, nil -} - -func (pg *pg) DeviceSetPosition(ctx context.Context, uid models.UID, position models.DevicePosition) error { - return nil -} - -func (pg *pg) DeviceChooser(ctx context.Context, tenantID string, chosen []string) error { - return nil -} - -func (pg *pg) DeviceConflicts(ctx context.Context, target *models.DeviceConflicts) ([]string, bool, error) { - return nil, false, nil -} - -func (pg *pg) DeviceUpdate(ctx context.Context, tenantID, uid string, changepg *models.DeviceChanges) error { - return nil -} - -func (pg *pg) DeviceBulkUpdate(ctx context.Context, uids []string, changepg *models.DeviceChanges) (int64, error) { - return int64(0), nil -} - -func (pg *pg) DeviceRemovedCount(ctx context.Context, tenant string) (int64, error) { - return int64(0), nil -} - -func (pg *pg) DeviceRemovedGet(ctx context.Context, tenant string, uid models.UID) (*models.DeviceRemoved, error) { - return nil, nil -} - -func (pg *pg) DeviceRemovedInsert(ctx context.Context, tenant string, device *models.Device) error { //nolint:revive - return nil -} - -func (pg *pg) DeviceRemovedDelete(ctx context.Context, tenant string, uid models.UID) error { - return nil -} - -func (pg *pg) DeviceRemovedList(ctx context.Context, tenant string, paginator query.Paginator, filters query.Filters, sorter query.Sorter) ([]models.DeviceRemoved, int, error) { - return nil, 0, nil -} - -func (pg *pg) DevicePushTag(ctx context.Context, uid models.UID, tag string) error { - return nil -} - -func (pg *pg) DevicePullTag(ctx context.Context, uid models.UID, tag string) error { - return nil -} - -func (pg *pg) DeviceSetTags(ctx context.Context, uid models.UID, tags []string) (int64, int64, error) { - return int64(0), int64(0), nil -} - -func (pg *pg) DeviceBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (int64, error) { - return int64(0), nil -} - -func (pg *pg) DeviceBulkDeleteTag(ctx context.Context, tenant, tag string) (int64, error) { - return int64(0), nil -} - -func (pg *pg) DeviceGetTags(ctx context.Context, tenant string) ([]string, int, error) { - return nil, 0, nil +func (pg *Pg) DeviceCreate(ctx context.Context, device *models.Device) (string, error) { + device.CreatedAt = clock.Now() + device.UpdatedAt = clock.Now() + + e := entity.DeviceFromModel(device) + if _, err := pg.driver.NewInsert().Model(e).Exec(ctx); err != nil { + return "", fromSqlError(err) + } + + return e.ID, nil +} + +func (pg *Pg) DeviceConflicts(ctx context.Context, target *models.DeviceConflicts) ([]string, bool, error) { + devices := make([]map[string]any, 0) + if err := pg.driver.NewSelect().Model((*entity.Device)(nil)).Column("name").Where("name = ?", target.Name).Scan(ctx, &devices); err != nil { + return nil, false, fromSqlError(err) + } + + conflicts := make([]string, 0) + for _, device := range devices { + if device["name"] == target.Name { + conflicts = append(conflicts, "name") + } + } + + return conflicts, len(conflicts) > 0, nil +} + +func (pg *Pg) DeviceList(ctx context.Context, opts ...store.QueryOption) ([]models.Device, int, error) { + entities := make([]entity.Device, 0) + + query := pg.driver. + NewSelect(). + Model(&entities). + Column("device.*"). + Relation("Namespace"). + ColumnExpr(` + CASE + WHEN "device"."disconnected_at" IS NULL AND "device"."seen_at" > ? + THEN true + ELSE false + END AS "online"`, + time.Now().Add(-2*time.Minute), + ). + ColumnExpr(` + CASE + WHEN "device"."status" <> 'accepted' + THEN true + ELSE false + END AS "acceptable"`, + ) + + if err := applyOptions(ctx, query, opts...); err != nil { + return nil, 0, fromSqlError(err) + } + + count, err := query.ScanAndCount(ctx) + if err != nil { + return nil, 0, fromSqlError(err) + } + + devices := make([]models.Device, len(entities)) + for i, e := range entities { + devices[i] = *entity.DeviceToModel(&e) + } + + return devices, count, nil +} + +func (pg *Pg) DeviceGet(ctx context.Context, ident store.DeviceIdent, val string) (*models.Device, error) { + d := new(entity.Device) + + query := pg.driver. + NewSelect(). + Model(d). + Where("? = ?", bun.Ident("device."+ident), val). + Column("device.*"). + Relation("Namespace"). + ColumnExpr(` + CASE + WHEN "device"."disconnected_at" IS NULL AND "device"."seen_at" > ? + THEN true + ELSE false + END AS "online"`, + time.Now().Add(-2*time.Minute), + ) + + if err := query.Scan(ctx); err != nil { + return nil, fromSqlError(err) + } + + return entity.DeviceToModel(d), nil +} + +func (pg *Pg) DeviceSave(ctx context.Context, device *models.Device) error { + d := entity.DeviceFromModel(device) + d.UpdatedAt = clock.Now() + _, err := pg.driver.NewUpdate().Model(d).WherePK().Exec(ctx) + + return fromSqlError(err) +} + +func (pg *Pg) DeviceUpdateSeenAt(ctx context.Context, ids []string, to time.Time) (int64, error) { + r, err := pg.driver.NewUpdate(). + Model((*entity.Device)(nil)). + Set("seen_at = ?", to). + Set("disconnected_at = NULL"). + TableExpr("(SELECT unnest(?::varchar[]) as id) as _data", pgdialect.Array(ids)). + Where("device.id = _data.id"). + Exec(ctx) + if err != nil { + return 0, fromSqlError(err) + } + + return r.RowsAffected() +} + +func (pg *Pg) DeviceDelete(ctx context.Context, device *models.Device) error { + d := entity.DeviceFromModel(device) + _, err := pg.driver.NewDelete().Model(d).WherePK().Exec(ctx) + + return fromSqlError(err) } diff --git a/api/store/pg/entity/api-key.go b/api/store/pg/entity/api-key.go new file mode 100644 index 00000000000..5ff3f1677bf --- /dev/null +++ b/api/store/pg/entity/api-key.go @@ -0,0 +1,48 @@ +package entity + +import ( + "time" + + "github.com/shellhub-io/shellhub/pkg/api/authorizer" + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" +) + +type APIKey struct { + bun.BaseModel `bun:"table:api_keys"` + + KeyDigest string `bun:"key_digest,pk"` + NamespaceID string `bun:"namespace_id,pk"` + Name string `bun:"name"` + Role string `bun:"role"` + UserID string `bun:"user_id"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` + ExpiresIn int64 `bun:"expires_in,nullzero"` +} + +func APIKeyFromModel(model *models.APIKey) *APIKey { + return &APIKey{ + Name: model.Name, + NamespaceID: model.TenantID, + KeyDigest: model.ID, + Role: model.Role.String(), + UserID: model.CreatedBy, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + ExpiresIn: model.ExpiresIn, + } +} + +func APIKeyToModel(entity *APIKey) *models.APIKey { + return &models.APIKey{ + ID: entity.KeyDigest, + Name: entity.Name, + TenantID: entity.NamespaceID, + Role: authorizer.Role(entity.Role), + CreatedBy: entity.UserID, + CreatedAt: entity.CreatedAt, + UpdatedAt: entity.UpdatedAt, + ExpiresIn: entity.ExpiresIn, + } +} diff --git a/api/store/pg/entity/device.go b/api/store/pg/entity/device.go new file mode 100644 index 00000000000..762d03ecc14 --- /dev/null +++ b/api/store/pg/entity/device.go @@ -0,0 +1,110 @@ +package entity + +import ( + "time" + + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" +) + +type Device struct { + bun.BaseModel `bun:"table:devices"` + + ID string `bun:"id,pk"` + NamespaceID string `bun:"namespace_id,pk,type:uuid"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` + SeenAt time.Time `bun:"seen_at"` + DisconnectedAt time.Time `bun:"disconnected_at,nullzero"` + Online bool `bun:",scanonly"` + Acceptable bool `bun:",scanonly"` + Status string `bun:"status"` + Name string `bun:"name"` + MAC string `bun:"mac"` + PublicKey string `bun:"public_key"` + Identifier string `bun:"identifier"` + PrettyName string `bun:"pretty_name"` + Version string `bun:"version"` + Arch string `bun:"arch"` + Platform string `bun:"platform"` + Longitude float64 `bun:"longitude,type:numeric"` + Latitude float64 `bun:"latitude,type:numeric"` + + Namespace *Namespace `bun:"rel:belongs-to,join:namespace_id=id"` +} + +func DeviceFromModel(model *models.Device) *Device { + device := &Device{ + ID: model.UID, + NamespaceID: model.TenantID, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + SeenAt: model.LastSeen, + Status: string(model.Status), + Name: model.Name, + PublicKey: model.PublicKey, + } + + if model.DisconnectedAt != nil { + device.DisconnectedAt = *model.DisconnectedAt + } + + if model.Identity != nil { + device.MAC = model.Identity.MAC + } + + if model.Position != nil { + device.Longitude = model.Position.Longitude + device.Latitude = model.Position.Latitude + } + + if model.Info != nil { + device.Identifier = model.Info.ID + device.PrettyName = model.Info.PrettyName + device.Version = model.Info.Version + device.Arch = model.Info.Arch + device.Platform = model.Info.Platform + } + + return device +} + +func DeviceToModel(entity *Device) *models.Device { + device := &models.Device{ + UID: entity.ID, + TenantID: entity.NamespaceID, + CreatedAt: entity.CreatedAt, + UpdatedAt: entity.UpdatedAt, + LastSeen: entity.SeenAt, + Status: models.DeviceStatus(entity.Status), + Name: entity.Name, + PublicKey: entity.PublicKey, + Online: entity.Online, + Acceptable: entity.Acceptable, + Namespace: entity.Namespace.Name, + DisconnectedAt: nil, + RemoteAddr: "", + Tags: []string{}, + Position: &models.DevicePosition{ + Longitude: entity.Longitude, + Latitude: entity.Latitude, + }, + Info: &models.DeviceInfo{ + ID: entity.Identifier, + PrettyName: entity.PrettyName, + Version: entity.Version, + Arch: entity.Arch, + Platform: entity.Platform, + }, + Identity: &models.DeviceIdentity{ + MAC: entity.MAC, + }, + } + + if !entity.DisconnectedAt.IsZero() { + disconnectedAt := entity.DisconnectedAt + device.DisconnectedAt = &disconnectedAt + } + + return device +} diff --git a/api/store/pg/entity/entity.go b/api/store/pg/entity/entity.go new file mode 100644 index 00000000000..29bf1650549 --- /dev/null +++ b/api/store/pg/entity/entity.go @@ -0,0 +1,13 @@ +package entity + +func Entities() []any { + return []any{ + (*User)(nil), + (*Namespace)(nil), + (*Membership)(nil), + (*Device)(nil), + (*APIKey)(nil), + (*PublicKey)(nil), + (*PrivateKey)(nil), + } +} diff --git a/api/store/pg/entity/membership.go b/api/store/pg/entity/membership.go new file mode 100644 index 00000000000..a76f3c4b143 --- /dev/null +++ b/api/store/pg/entity/membership.go @@ -0,0 +1,44 @@ +package entity + +import ( + "time" + + "github.com/shellhub-io/shellhub/pkg/api/authorizer" + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" +) + +type Membership struct { + bun.BaseModel `bun:"table:memberships"` + + UserID string `bun:"user_id,pk,type:uuid"` + NamespaceID string `bun:"namespace_id,pk,type:uuid"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` + Status string `bun:"status"` + Role string `bun:"role"` + + User *User `bun:"rel:belongs-to,join:user_id=id"` + Namespace *Namespace `bun:"rel:belongs-to,join:namespace_id=id"` +} + +func MembershipFromModel(member *models.Member, namespaceID string) *Membership { + return &Membership{ + UserID: member.ID, + NamespaceID: namespaceID, + CreatedAt: member.AddedAt, + UpdatedAt: member.UpdatedAt, + Status: string(member.Status), + Role: string(member.Role), + } +} + +func MembershipToModel(entity *Membership) *models.Member { + return &models.Member{ + ID: entity.UserID, + AddedAt: entity.CreatedAt, + Role: authorizer.Role(entity.Role), + Status: models.MemberStatus(entity.Status), + Email: entity.User.Email, + } +} diff --git a/api/store/pg/entity/namespace.go b/api/store/pg/entity/namespace.go new file mode 100644 index 00000000000..aada82d62f0 --- /dev/null +++ b/api/store/pg/entity/namespace.go @@ -0,0 +1,76 @@ +package entity + +import ( + "time" + + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" +) + +type Namespace struct { + bun.BaseModel `bun:"table:namespaces"` + + ID string `bun:"id,pk,type:uuid"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` + Type string `bun:"scope"` + Name string `bun:"name"` + Memberships []Membership `json:"members" bun:"rel:has-many,join:id=namespace_id"` + Settings NamespaceSettings `bun:"embed:"` +} + +type NamespaceSettings struct { + MaxDevices int `bun:"max_devices"` + SessionRecord bool `bun:"record_sessions"` + ConnectionAnnouncement string `bun:"connection_announcement,type:text"` +} + +func NamespaceFromModel(model *models.Namespace) *Namespace { + namespace := &Namespace{ + ID: model.TenantID, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + Type: string(model.Type), + Name: model.Name, + Settings: NamespaceSettings{ + MaxDevices: model.MaxDevices, + SessionRecord: model.Settings.SessionRecord, + ConnectionAnnouncement: model.Settings.ConnectionAnnouncement, + }, + } + + namespace.Memberships = make([]Membership, len(model.Members)) + for i, member := range model.Members { + namespace.Memberships[i] = Membership{ + UserID: member.ID, + NamespaceID: model.TenantID, + CreatedAt: member.AddedAt, + UpdatedAt: member.UpdatedAt, + Status: string(member.Status), + Role: string(member.Role), + } + } + + return namespace +} + +func NamespaceToModel(entity *Namespace) *models.Namespace { + namespace := &models.Namespace{ + TenantID: entity.ID, + Name: entity.Name, + CreatedAt: entity.CreatedAt, + Type: models.Type(entity.Type), + MaxDevices: entity.Settings.MaxDevices, + Settings: &models.NamespaceSettings{ + SessionRecord: entity.Settings.SessionRecord, + ConnectionAnnouncement: entity.Settings.ConnectionAnnouncement, + }, + } + + namespace.Members = make([]models.Member, len(entity.Memberships)) + for i, membership := range entity.Memberships { + namespace.Members[i] = *MembershipToModel(&membership) + } + + return namespace +} diff --git a/api/store/pg/entity/private-key.go b/api/store/pg/entity/private-key.go new file mode 100644 index 00000000000..24ea1b25f31 --- /dev/null +++ b/api/store/pg/entity/private-key.go @@ -0,0 +1,35 @@ +package entity + +import ( + "time" + + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" +) + +type PrivateKey struct { + bun.BaseModel `bun:"table:private_keys"` + + Fingerprint string `bun:"fingerprint,pk"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` + Data []byte `bun:"data,type:bytea"` +} + +func PrivateKeyFromModel(model *models.PrivateKey) *PrivateKey { + return &PrivateKey{ + Fingerprint: model.Fingerprint, + Data: model.Data, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + } +} + +func PrivateKeyToModel(entity *PrivateKey) *models.PrivateKey { + return &models.PrivateKey{ + Fingerprint: entity.Fingerprint, + Data: entity.Data, + CreatedAt: entity.CreatedAt, + UpdatedAt: entity.UpdatedAt, + } +} diff --git a/api/store/pg/entity/public-key.go b/api/store/pg/entity/public-key.go new file mode 100644 index 00000000000..6ffff72a2e2 --- /dev/null +++ b/api/store/pg/entity/public-key.go @@ -0,0 +1,51 @@ +package entity + +import ( + "time" + + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" +) + +type PublicKey struct { + bun.BaseModel `bun:"table:public_keys"` + + ID string `bun:"id,pk"` + Fingerprint string `bun:"fingerprint"` + NamespaceID string `bun:"namespace_id"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` + Name string `bun:"name"` + Data []byte `bun:"data,type:bytea"` +} + +func PublicKeyFromModel(model *models.PublicKey) *PublicKey { + return &PublicKey{ + ID: model.ID, + NamespaceID: model.TenantID, + Fingerprint: model.Fingerprint, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + Name: model.PublicKeyFields.Name, + Data: model.Data, + } +} + +func PublicKeyToModel(entity *PublicKey) *models.PublicKey { + return &models.PublicKey{ + ID: entity.ID, + TenantID: entity.NamespaceID, + Fingerprint: entity.Fingerprint, + Data: entity.Data, + CreatedAt: entity.CreatedAt, + UpdatedAt: entity.UpdatedAt, + PublicKeyFields: models.PublicKeyFields{ + Name: entity.Name, + Username: "", + Filter: models.PublicKeyFilter{ + Hostname: "", + Tags: []string{}, + }, + }, + } +} diff --git a/api/store/pg/entity/user.go b/api/store/pg/entity/user.go new file mode 100644 index 00000000000..08a887f692e --- /dev/null +++ b/api/store/pg/entity/user.go @@ -0,0 +1,106 @@ +package entity + +import ( + "time" + + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/uptrace/bun" +) + +type User struct { + bun.BaseModel `bun:"table:users"` + + ID string `bun:"id,pk,type:uuid"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` + LastLogin time.Time `bun:"last_login,nullzero"` + Origin string `bun:"origin"` + ExternalID string `bun:"external_id,nullzero"` + Status string `bun:"status"` + Name string `bun:"name"` + Username string `bun:"username"` + Email string `bun:"email"` + PasswordDigest string `bun:"password_digest"` + Preferences UserPreferences `bun:"embed:"` + MFA UserMFA `bun:"-"` +} + +type UserPreferences struct { + PreferredNamespace string `bun:"preferred_namespace_id,nullzero"` + AuthMethods []string `bun:"auth_methods,array"` + SecurityEmail string `bun:"security_email,nullzero"` + MaxNamespaces int `bun:"namespace_ownership_limit"` + EmailMarketing bool `bun:"email_marketing"` +} + +type UserMFA struct { + Enabled bool `bun:"enabled"` + Secret string `bun:"secret,nullzero"` + RecoveryCodes []string `bun:"recovery_codes,nullzero,array"` +} + +func UserFromModel(model *models.User) *User { + authMethods := make([]string, len(model.Preferences.AuthMethods)) + for i, method := range model.Preferences.AuthMethods { + authMethods[i] = method.String() + } + + return &User{ + ID: model.ID, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + LastLogin: model.LastLogin, + Origin: model.Origin.String(), + ExternalID: model.ExternalID, + Status: model.Status.String(), + Name: model.Name, + Username: model.Username, + Email: model.Email, + PasswordDigest: model.PasswordDigest, + Preferences: UserPreferences{ + PreferredNamespace: model.Preferences.PreferredNamespace, + AuthMethods: authMethods, + SecurityEmail: model.Preferences.RecoveryEmail, + MaxNamespaces: model.MaxNamespaces, + EmailMarketing: model.EmailMarketing, + }, + MFA: UserMFA{ + Enabled: model.MFA.Enabled, + Secret: model.MFA.Secret, + RecoveryCodes: model.MFA.RecoveryCodes, + }, + } +} + +func UserToModel(entity *User) *models.User { + authMethods := make([]models.UserAuthMethod, len(entity.Preferences.AuthMethods)) + for i, method := range entity.Preferences.AuthMethods { + authMethods[i] = models.UserAuthMethod(method) + } + + return &models.User{ + ID: entity.ID, + Origin: models.UserOrigin(entity.Origin), + ExternalID: entity.ExternalID, + Status: models.UserStatus(entity.Status), + MaxNamespaces: entity.Preferences.MaxNamespaces, + CreatedAt: entity.CreatedAt, + UpdatedAt: entity.UpdatedAt, + LastLogin: entity.LastLogin, + EmailMarketing: entity.Preferences.EmailMarketing, + Name: entity.Name, + Username: entity.Username, + Email: entity.Email, + PasswordDigest: entity.PasswordDigest, + MFA: models.UserMFA{ + Enabled: entity.MFA.Enabled, + Secret: entity.MFA.Secret, + RecoveryCodes: entity.MFA.RecoveryCodes, + }, + Preferences: models.UserPreferences{ + PreferredNamespace: entity.Preferences.PreferredNamespace, + AuthMethods: authMethods, + RecoveryEmail: entity.Preferences.SecurityEmail, + }, + } +} diff --git a/api/store/pg/metrics.go b/api/store/pg/metrics.go index 5474c4662ee..bc3b3e04533 100644 --- a/api/store/pg/metrics.go +++ b/api/store/pg/metrics.go @@ -2,10 +2,100 @@ package pg import ( "context" + "time" + "github.com/shellhub-io/shellhub/api/pkg/gateway" + "github.com/shellhub-io/shellhub/api/store/pg/entity" "github.com/shellhub-io/shellhub/pkg/models" ) -func (pg *pg) GetStats(ctx context.Context) (*models.Stats, error) { - return nil, nil +func (pg *Pg) GetStats(ctx context.Context) (*models.Stats, error) { + var tenantID string + if tenant := gateway.TenantFromContext(ctx); tenant != nil { + tenantID = tenant.ID + } + + onlineDevices := 0 + query := pg.driver.NewSelect(). + Model((*entity.Device)(nil)). + Where("disconnected_at IS NULL"). + Where("seen_at > ?", time.Now().Add(-2*time.Minute)). + Where("status = ?", "accepted") + + if tenantID != "" { + query = query.Where("namespace_id = (SELECT id FROM namespaces WHERE id = ?)", tenantID) + } + + count, err := query.Count(ctx) + if err != nil { + return nil, fromSqlError(err) + } + onlineDevices = count + + registeredDevices := 0 + query = pg.driver.NewSelect(). + Model((*entity.Device)(nil)). + Where("status = ?", "accepted") + + if tenantID != "" { + query = query.Where("namespace_id = (SELECT id FROM namespaces WHERE id = ?)", tenantID) + } + + count, err = query.Count(ctx) + if err != nil { + return nil, fromSqlError(err) + } + registeredDevices = count + + pendingDevices := 0 + query = pg.driver.NewSelect(). + Model((*entity.Device)(nil)). + Where("status = ?", "pending") + + if tenantID != "" { + query = query.Where("namespace_id = (SELECT id FROM namespaces WHERE id = ?)", tenantID) + } + + count, err = query.Count(ctx) + if err != nil { + return nil, fromSqlError(err) + } + pendingDevices = count + + rejectedDevices := 0 + query = pg.driver.NewSelect(). + Model((*entity.Device)(nil)). + Where("status = ?", "rejected") + + if tenantID != "" { + query = query.Where("namespace_id = (SELECT id FROM namespaces WHERE id = ?)", tenantID) + } + + count, err = query.Count(ctx) + if err != nil { + return nil, fromSqlError(err) + } + rejectedDevices = count + + // activeSessions := 0 + // query = pg.driver.NewSelect(). + // Model((*entity.ActiveSession)(nil)) + // + // if tenantID != "" { + // query = query.Where("tenant_id = ?", tenantID) + // } + // + // count, err = query.Count(ctx) + // if err != nil { + // return nil, fromSqlError(err) + // } + // activeSessions = count + + return &models.Stats{ + RegisteredDevices: registeredDevices, + OnlineDevices: onlineDevices, + PendingDevices: pendingDevices, + RejectedDevices: rejectedDevices, + ActiveSessions: 0, + }, nil } diff --git a/api/store/pg/migrations/001_create_namespaces_table.go b/api/store/pg/migrations/001_create_namespaces_table.go new file mode 100644 index 00000000000..a1e3d094cc2 --- /dev/null +++ b/api/store/pg/migrations/001_create_namespaces_table.go @@ -0,0 +1,52 @@ +package migrations + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(migration001Up, migration001Down) +} + +func migration001Up(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TYPE IF EXISTS namespace_scope; + CREATE TYPE namespace_scope AS ENUM ('personal', 'team'); + `) + if err != nil { + return err + } + + table := &struct { + bun.BaseModel `bun:"table:namespaces"` + ID string `bun:"id,type:uuid,pk"` + CreatedAt time.Time `bun:"created_at,type:timestamptz,notnull"` + UpdatedAt time.Time `bun:"updated_at,type:timestamptz,notnull"` + Scope string `bun:"scope,type:namespace_scope,notnull"` + Name string `bun:"name,type:varchar(64),notnull"` + MaxDevices int `bun:"max_devices,type:integer,notnull"` + RecordSessions bool `bun:"record_sessions,notnull"` + ConnectionAnnouncement string `bun:"connection_announcement,type:text,nullzero"` + }{} + + if _, err := db.NewCreateTable().Model(table).IfNotExists().Exec(ctx); err != nil { + log.WithError(err).Error("failed to apply migration 003") + + return err + } + + return nil +} + +func migration001Down(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TABLE IF EXISTS namespaces; + DROP TYPE IF EXISTS namespace_scope; + `) + + return err +} diff --git a/api/store/pg/migrations/002_create_users_table.go b/api/store/pg/migrations/002_create_users_table.go new file mode 100644 index 00000000000..25997de1a40 --- /dev/null +++ b/api/store/pg/migrations/002_create_users_table.go @@ -0,0 +1,73 @@ +package migrations + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(migration002Up, migration002Down) +} + +func migration002Up(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TYPE IF EXISTS user_origin; + CREATE TYPE user_origin AS ENUM ('local', 'saml'); + + DROP TYPE IF EXISTS user_status; + CREATE TYPE user_status AS ENUM ('invited', 'pending', 'confirmed'); + + DROP TYPE IF EXISTS user_auth_method; + CREATE TYPE user_auth_method AS ENUM ('local', 'saml'); + `) + if err != nil { + return err + } + + table := &struct { + bun.BaseModel `bun:"table:users"` + ID string `bun:"id,type:uuid,pk"` + CreatedAt time.Time `bun:"created_at,type:timestamptz,notnull"` + UpdatedAt time.Time `bun:"updated_at,type:timestamptz,notnull"` + LastLogin time.Time `bun:"last_login,type:timestamptz,nullzero"` + Origin string `bun:"origin,type:user_origin,notnull"` + ExternalID string `bun:"external_id,type:varchar,nullzero"` + Status string `bun:"status,type:user_status,notnull"` + Name string `bun:"name,type:varchar(64),notnull"` + Username string `bun:"username,type:varchar(32),notnull,unique"` + Email string `bun:"email,type:varchar(320),notnull,unique"` + SecurityEmail string `bun:"security_email,type:varchar(320),nullzero"` + PasswordDigest string `bun:"password_digest,type:char(72),notnull"` + AuthMethods []string `bun:"auth_methods,type:user_auth_method[],array,notnull"` + NamespaceOwnershipLimit int `bun:"namespace_ownership_limit,type:integer,notnull"` + EmailMarketing bool `bun:"email_marketing,notnull,default:false"` + PreferredNamespaceID string `bun:"preferred_namespace_id,type:uuid,nullzero"` + }{} + + if _, err := db. + NewCreateTable(). + Model(table). + IfNotExists(). + ForeignKey(`("preferred_namespace_id") REFERENCES namespaces("id") ON DELETE SET NULL`). + Exec(ctx); err != nil { + log.WithError(err).Error("failed to apply migration 002") + + return err + } + + return nil +} + +func migration002Down(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TABLE IF EXISTS users; + DROP TYPE IF EXISTS user_origin; + DROP TYPE IF EXISTS user_status; + DROP TYPE IF EXISTS user_auth_method; + `) + + return err +} diff --git a/api/store/pg/migrations/003_create_memberships_table.go b/api/store/pg/migrations/003_create_memberships_table.go new file mode 100644 index 00000000000..64572bfb191 --- /dev/null +++ b/api/store/pg/migrations/003_create_memberships_table.go @@ -0,0 +1,59 @@ +package migrations + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(migration003Up, migration003Down) +} + +func migration003Up(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TYPE IF EXISTS membership_status; + CREATE TYPE membership_status AS ENUM ('pending', 'accepted'); + + DROP TYPE IF EXISTS membership_role; + CREATE TYPE membership_role AS ENUM ('owner', 'administrator', 'operator', 'observer'); + `) + if err != nil { + return err + } + + table := &struct { + bun.BaseModel `bun:"table:memberships"` + UserID string `bun:"user_id,type:uuid,notnull,pk"` + NamespaceID string `bun:"namespace_id,type:uuid,notnull,pk"` + CreatedAt time.Time `bun:"created_at,type:timestamptz,notnull"` + UpdatedAt time.Time `bun:"updated_at,type:timestamptz,notnull"` + Status string `bun:"status,type:membership_status,notnull"` + Role string `bun:"role,type:membership_role,notnull"` + }{} + + if _, err := db.NewCreateTable(). + Model(table). + IfNotExists(). + ForeignKey(`("user_id") REFERENCES users("id") ON DELETE CASCADE`). + ForeignKey(`("namespace_id") REFERENCES namespaces("id") ON DELETE CASCADE`). + Exec(ctx); err != nil { + log.WithError(err).Error("failed to apply migration 003") + + return err + } + + return nil +} + +func migration003Down(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TABLE IF EXISTS memberships; + DROP TYPE IF EXISTS membership_status; + DROP TYPE IF EXISTS membership_role; + `) + + return err +} diff --git a/api/store/pg/migrations/004_create_devices_table.go b/api/store/pg/migrations/004_create_devices_table.go new file mode 100644 index 00000000000..62b337e9856 --- /dev/null +++ b/api/store/pg/migrations/004_create_devices_table.go @@ -0,0 +1,105 @@ +package migrations + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(migration004Up, migration004Down) +} + +func migration004Up(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TYPE IF EXISTS device_status; + CREATE TYPE device_status AS ENUM ('accepted', 'pending', 'rejected', 'removed', 'unused'); + `) + if err != nil { + return err + } + + deviceTable := &struct { + bun.BaseModel `bun:"table:devices"` + ID string `bun:"id,type:varchar,pk"` + NamespaceID string `bun:"namespace_id,type:uuid,notnull"` + CreatedAt time.Time `bun:"created_at,type:timestamptz,notnull"` + UpdatedAt time.Time `bun:"updated_at,type:timestamptz,notnull"` + SeenAt time.Time `bun:"seen_at,type:timestamptz,notnull"` + DisconnectedAt time.Time `bun:"disconnected_at,type:timestamptz,nullzero"` + Status string `bun:"status,type:device_status,notnull"` + Name string `bun:"name,type:varchar(64),notnull"` + Mac string `bun:"mac,type:varchar(17),notnull"` + PublicKey string `bun:"public_key,type:text,notnull"` + Identifier string `bun:"identifier,type:varchar,nullzero"` + PrettyName string `bun:"pretty_name,type:varchar(64),nullzero"` + Version string `bun:"version,type:varchar(32),nullzero"` + Arch string `bun:"arch,type:varchar(16),nullzero"` + Platform string `bun:"platform,type:varchar(32),nullzero"` + Latitude float64 `bun:"latitude,type:numeric,nullzero"` + Longitude float64 `bun:"longitude,type:numeric,nullzero"` + }{} + + _, err = db.NewCreateTable(). + Model(deviceTable). + IfNotExists(). + ForeignKey(`("namespace_id") REFERENCES namespaces("id")`). + Exec(ctx) + if err != nil { + log.WithError(err).Error("failed to apply migration 004") + + return err + } + + _, err = db.NewCreateIndex(). + Model((*struct { + bun.BaseModel `bun:"table:devices"` + })(nil)). + Index("devices_namespace_id"). + Column("namespace_id"). + Exec(ctx) + if err != nil { + log.WithError(err).Error("failed to apply migration 004") + + return err + } + + _, err = db.NewCreateIndex(). + Model((*struct { + bun.BaseModel `bun:"table:devices"` + })(nil)). + Index("devices_seen_at"). + Column("seen_at"). + Exec(ctx) + if err != nil { + log.WithError(err).Error("failed to apply migration 004") + + return err + } + + _, err = db.NewCreateIndex(). + Model((*struct { + bun.BaseModel `bun:"table:devices"` + })(nil)). + Index("devices_disconnected_at"). + Column("disconnected_at"). + Exec(ctx) + if err != nil { + log.WithError(err).Error("failed to apply migration 004") + + return err + } + + return nil +} + +func migration004Down(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TABLE IF EXISTS devices; + DROP TYPE IF EXISTS device_status; + `) + + return err +} diff --git a/api/store/pg/migrations/005_create_private_keys_table.go b/api/store/pg/migrations/005_create_private_keys_table.go new file mode 100644 index 00000000000..75d34ee22a1 --- /dev/null +++ b/api/store/pg/migrations/005_create_private_keys_table.go @@ -0,0 +1,39 @@ +package migrations + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(migration005Up, migration005Down) +} + +func migration005Up(ctx context.Context, db *bun.DB) error { + table := &struct { + bun.BaseModel `bun:"table:private_keys"` + Fingerprint string `bun:"fingerprint,type:varchar,pk"` + CreatedAt time.Time `bun:"created_at,type:timestamptz,notnull"` + UpdatedAt time.Time `bun:"updated_at,type:timestamptz,notnull"` + Data []byte `bun:"data,type:bytea,nullzero"` + }{} + + if _, err := db.NewCreateTable().Model(table).IfNotExists().Exec(ctx); err != nil { + log.WithError(err).Error("failed to apply migration 005") + + return err + } + + return nil +} + +func migration005Down(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TABLE IF EXISTS private_keys; + `) + + return err +} diff --git a/api/store/pg/migrations/006_create_api_keys.go b/api/store/pg/migrations/006_create_api_keys.go new file mode 100644 index 00000000000..5b8326b8193 --- /dev/null +++ b/api/store/pg/migrations/006_create_api_keys.go @@ -0,0 +1,47 @@ +package migrations + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(migration006Up, migration006Down) +} + +func migration006Up(ctx context.Context, db *bun.DB) error { + table := &struct { + bun.BaseModel `bun:"table:api_keys"` + KeyDigest string `bun:"key_digest,type:char(64),notnull,pk"` + NamespaceID string `bun:"namespace_id,type:uuid,notnull,pk"` + CreatedAt time.Time `bun:"created_at,type:timestamptz,notnull"` + UpdatedAt time.Time `bun:"updated_at,type:timestamptz,notnull"` + ExpiresIn int64 `bun:"expires_in,type:bigint,nullzero"` + Name string `bun:"name,type:varchar,notnull,unique"` + Role string `bun:"role,type:membership_role,notnull"` + UserID string `bun:"user_id,type:uuid,notnull"` + }{} + + if _, err := db.NewCreateTable(). + Model(table). + IfNotExists(). + ForeignKey(`("namespace_id") REFERENCES namespaces("id") ON DELETE CASCADE`). + Exec(ctx); err != nil { + log.WithError(err).Error("failed to apply migration 006") + + return err + } + + return nil +} + +func migration006Down(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TABLE IF EXISTS api_keys; + `) + + return err +} diff --git a/api/store/pg/migrations/007_create_public_keys.go b/api/store/pg/migrations/007_create_public_keys.go new file mode 100644 index 00000000000..c88bd9a4e5e --- /dev/null +++ b/api/store/pg/migrations/007_create_public_keys.go @@ -0,0 +1,46 @@ +package migrations + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(migration007Up, migration007Down) +} + +func migration007Up(ctx context.Context, db *bun.DB) error { + table := &struct { + bun.BaseModel `bun:"table:public_keys"` + ID string `bun:"id,type:uuid,pk"` + Fingerprint string `bun:"fingerprint,type:varchar,notnull"` + NamespaceID string `bun:"namespace_id,type:uuid,notnull"` + CreatedAt time.Time `bun:"created_at,type:timestamptz,notnull"` + UpdatedAt time.Time `bun:"updated_at,type:timestamptz,notnull"` + Name string `bun:"name,type:varchar,notnull"` + Data []byte `bun:"data,type:bytea,nullzero"` + }{} + + if _, err := db.NewCreateTable(). + Model(table). + IfNotExists(). + ForeignKey(`("namespace_id") REFERENCES namespaces("id") ON DELETE CASCADE`). + Exec(ctx); err != nil { + log.WithError(err).Error("failed to apply migration 007") + + return err + } + + return nil +} + +func migration007Down(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` + DROP TABLE IF EXISTS public_keys; + `) + + return err +} diff --git a/api/store/pg/migrations/migrations.go b/api/store/pg/migrations/migrations.go new file mode 100644 index 00000000000..9162d7f92c7 --- /dev/null +++ b/api/store/pg/migrations/migrations.go @@ -0,0 +1,15 @@ +package migrations + +import ( + "github.com/uptrace/bun/migrate" +) + +var migrations = migrate.NewMigrations() + +func FetchMigrations() (*migrate.Migrations, error) { + if err := migrations.DiscoverCaller(); err != nil { + return nil, err + } + + return migrations, nil +} diff --git a/api/store/pg/namespace.go b/api/store/pg/namespace.go index 7b88763a640..87d30662be8 100644 --- a/api/store/pg/namespace.go +++ b/api/store/pg/namespace.go @@ -4,65 +4,121 @@ import ( "context" "github.com/shellhub-io/shellhub/api/store" - "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/api/store/pg/entity" + "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/models" + "github.com/shellhub-io/shellhub/pkg/uuid" + "github.com/uptrace/bun" ) -func (pg *pg) NamespaceCreate(ctx context.Context, namespace *models.Namespace) (*models.Namespace, error) { - return nil, nil -} +func (pg *Pg) NamespaceCreate(ctx context.Context, namespace *models.Namespace) (string, error) { + if namespace.TenantID == "" { + namespace.TenantID = uuid.Generate() + } -func (pg *pg) NamespaceList(ctx context.Context, paginator query.Paginator, filters query.Filters, opts ...store.NamespaceQueryOption) ([]models.Namespace, int, error) { - return nil, 0, nil -} + namespace.CreatedAt = clock.Now() + namespace.UpdatedAt = clock.Now() -func (pg *pg) NamespaceGet(ctx context.Context, tenantID string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { - return nil, nil -} + if _, err := pg.driver.NewInsert().Model(entity.NamespaceFromModel(namespace)).Exec(ctx); err != nil { + return "", fromSqlError(err) + } -func (pg *pg) NamespaceGetByName(ctx context.Context, name string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { - // TODO: unify get methods - return nil, nil + return namespace.TenantID, nil } -func (pg *pg) NamespaceGetPreferred(ctx context.Context, userID string, opts ...store.NamespaceQueryOption) (*models.Namespace, error) { - // TODO: unify get methods - return nil, nil -} +func (pg *Pg) NamespaceCreateMemberships(ctx context.Context, tenantID string, memberships ...models.Member) error { + entities := make([]entity.Membership, len(memberships)) + for i, m := range memberships { + m.AddedAt = clock.Now() + m.UpdatedAt = clock.Now() + entities[i] = *entity.MembershipFromModel(&m, tenantID) + } + + if _, err := pg.driver.NewInsert().Model(&entities).Exec(ctx); err != nil { + return fromSqlError(err) + } -func (pg *pg) NamespaceEdit(ctx context.Context, tenant string, changes *models.NamespaceChanges) error { - // TODO: unify update methods return nil } -func (pg *pg) NamespaceUpdate(ctx context.Context, tenantID string, namespace *models.Namespace) error { - // TODO: unify update methods - return nil +func (pg *Pg) NamespaceConflicts(ctx context.Context, target *models.NamespaceConflicts) ([]string, bool, error) { + namespaces := make([]map[string]any, 0) + if err := pg.driver.NewSelect().Model((*entity.Namespace)(nil)).Column("name").Where("name = ?", target.Name).Scan(ctx, &namespaces); err != nil { + return nil, false, fromSqlError(err) + } + + conflicts := make([]string, 0) + for _, user := range namespaces { + if user["name"] == target.Name { + conflicts = append(conflicts, "name") + } + } + + return conflicts, len(conflicts) > 0, nil } -func (pg *pg) NamespaceDelete(ctx context.Context, tenantID string) error { - return nil +func (pg *Pg) NamespaceList(ctx context.Context, opts ...store.QueryOption) ([]models.Namespace, int, error) { + entities := make([]entity.Namespace, 0) + query := pg.driver.NewSelect().Model(&entities).Relation("Memberships.User") + if err := applyOptions(ctx, query, opts...); err != nil { + return nil, 0, fromSqlError(err) + } + + count, err := query.ScanAndCount(ctx) + if err != nil { + return nil, 0, fromSqlError(err) + } + + namespaces := make([]models.Namespace, len(entities)) + for i, e := range entities { + namespaces[i] = *entity.NamespaceToModel(&e) + } + + return namespaces, count, nil } -// TODO: members must be an association N:N between users and namespaces now -func (pg *pg) NamespaceAddMember(ctx context.Context, tenantID string, member *models.Member) error { - return nil +func (pg *Pg) NamespaceGet(ctx context.Context, ident store.NamespaceIdent, val string, opts ...store.QueryOption) (*models.Namespace, error) { + ns := new(entity.Namespace) + + query := pg.driver.NewSelect().Model(ns).Relation("Memberships.User").Where("? = ?", bun.Ident(ident), val) + if err := applyOptions(ctx, query, opts...); err != nil { + return nil, fromSqlError(err) + } + + if err := query.Scan(ctx); err != nil { + return nil, fromSqlError(err) + } + + return entity.NamespaceToModel(ns), nil } -func (pg *pg) NamespaceUpdateMember(ctx context.Context, tenantID string, memberID string, changes *models.MemberChanges) error { - return nil +func (pg *Pg) NamespaceSave(ctx context.Context, namespace *models.Namespace) error { + n := entity.NamespaceFromModel(namespace) + n.UpdatedAt = clock.Now() + + _, err := pg.driver.NewUpdate().Model(n).WherePK().Exec(ctx) + + return fromSqlError(err) } -func (pg *pg) NamespaceRemoveMember(ctx context.Context, tenantID string, memberID string) error { - return nil +func (pg *Pg) NamespaceSaveMembership(ctx context.Context, tenantID string, member *models.Member) error { + e := entity.MembershipFromModel(member, tenantID) + e.UpdatedAt = clock.Now() + _, err := pg.driver.NewUpdate().Model(e).WherePK().Exec(ctx) + + return fromSqlError(err) } -func (pg *pg) NamespaceSetSessionRecord(ctx context.Context, sessionRecord bool, tenantID string) error { - // TODO: these methods are not used anymore - return nil +func (pg *Pg) NamespaceDelete(ctx context.Context, namespace *models.Namespace) error { + n := entity.NamespaceFromModel(namespace) + _, err := pg.driver.NewDelete().Model(n).WherePK().Exec(ctx) + + return fromSqlError(err) } -func (pg *pg) NamespaceGetSessionRecord(ctx context.Context, tenantID string) (bool, error) { - // TODO: these methods are not used anymore - return false, nil +func (pg *Pg) NamespaceDeleteMembership(ctx context.Context, tenantID string, member *models.Member) error { + e := entity.MembershipFromModel(member, tenantID) + _, err := pg.driver.NewDelete().Model(e).WherePK().Exec(ctx) + + return fromSqlError(err) } diff --git a/api/store/pg/options/log.go b/api/store/pg/options/log.go new file mode 100644 index 00000000000..f2601b32b7c --- /dev/null +++ b/api/store/pg/options/log.go @@ -0,0 +1,39 @@ +package options + +import ( + "context" + "os" + + "github.com/oiime/logrusbun" + "github.com/sirupsen/logrus" + "github.com/uptrace/bun" +) + +func Log(level string, verbose bool) Option { + return func(ctx context.Context, db *bun.DB) error { + level, err := logrus.ParseLevel(level) + if err != nil { + return err + } + + logger := &logrus.Logger{ + Out: os.Stderr, + Formatter: new(logrus.TextFormatter), + Hooks: make(logrus.LevelHooks), + Level: level, + } + + db.AddQueryHook(logrusbun.NewQueryHook( + logrusbun.WithEnabled(true), + logrusbun.WithVerbose(verbose), + logrusbun.WithQueryHookOptions(logrusbun.QueryHookOptions{ + Logger: logger, + QueryLevel: logrus.DebugLevel, + ErrorLevel: logrus.ErrorLevel, + SlowLevel: logrus.WarnLevel, + }), + )) + + return nil + } +} diff --git a/api/store/pg/options/migrate.go b/api/store/pg/options/migrate.go new file mode 100644 index 00000000000..e6270a3cf61 --- /dev/null +++ b/api/store/pg/options/migrate.go @@ -0,0 +1,61 @@ +package options + +import ( + "context" + + "github.com/shellhub-io/shellhub/api/store/pg/migrations" + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" +) + +func Migrate() Option { + return func(ctx context.Context, db *bun.DB) error { + log.Info("starting database migration") + + migrations, err := migrations.FetchMigrations() + if err != nil { + log.WithError(err).Error("failed to fetch migrations") + + return err + } + + migrator := migrate.NewMigrator(db, migrations) + if err := migrator.Init(context.Background()); err != nil { + log.WithError(err).Error("failed to start migrations tables") + + return err + } + + if err := migrator.Lock(ctx); err != nil { + log.WithError(err).Error("failed to acquire migration lock") + + return err + } + + defer func() { + if err := migrator.Unlock(ctx); err != nil { + log.WithError(err).Error("failed to release migration lock") + } else { + log.Debug("migration lock released successfully") + } + }() + + group, err := migrator.Migrate(ctx) + if err != nil { + log.WithError(err).Error("migration failed") + + return err + } + + if group.IsZero() { + log.Info("no new migrations to run (database is up to date)") + + return nil + } + + log.Info("migration completed successfully") + + return nil + } +} diff --git a/api/store/pg/options/with-fixtures.go b/api/store/pg/options/with-fixtures.go new file mode 100644 index 00000000000..07c139f91ba --- /dev/null +++ b/api/store/pg/options/with-fixtures.go @@ -0,0 +1,31 @@ +package options + +import ( + "context" + "io/fs" + "os" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dbfixture" +) + +func WithFixtures(dir string) Option { + return func(ctx context.Context, db *bun.DB) error { + fixture := dbfixture.New(db) + + fsys := os.DirFS(dir) + files, err := fs.ReadDir(fsys, ".") + if err != nil { + return err + } + + names := make([]string, 0) + for _, file := range files { + if !file.IsDir() { + names = append(names, file.Name()) + } + } + + return fixture.Load(ctx, fsys, names...) + } +} diff --git a/api/store/pg/pg.go b/api/store/pg/pg.go index 829a24f1d8b..b620305c2c6 100644 --- a/api/store/pg/pg.go +++ b/api/store/pg/pg.go @@ -8,14 +8,17 @@ import ( "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/api/store/pg/entity" "github.com/shellhub-io/shellhub/api/store/pg/options" - "github.com/shellhub-io/shellhub/pkg/models" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" ) -type pg struct { - driver *bun.DB +type queryOptions struct{} + +type Pg struct { + driver *bun.DB + options *queryOptions } func URI(host, port, user, password, db string) string { @@ -35,14 +38,12 @@ func New(ctx context.Context, uri string, opts ...options.Option) (store.Store, return nil, err } - pg := &pg{driver: bun.NewDB(stdlib.OpenDBFromPool(pool), pgdialect.New())} + pg := &Pg{driver: bun.NewDB(stdlib.OpenDBFromPool(pool), pgdialect.New()), options: &queryOptions{}} if err := pg.driver.Ping(); err != nil { return nil, err } - // We need to register models so we can apply fixtures and relations later - pg.driver.RegisterModel((*models.User)(nil)) - + pg.driver.RegisterModel(entity.Entities()...) // We need to register models so we can apply fixtures and relations later for _, opt := range opts { if err := opt(ctx, pg.driver); err != nil { return nil, err @@ -51,3 +52,7 @@ func New(ctx context.Context, uri string, opts ...options.Option) (store.Store, return pg, nil } + +func (pg *Pg) Driver() *bun.DB { + return pg.driver +} diff --git a/api/store/pg/pg_test.go b/api/store/pg/pg_test.go new file mode 100644 index 00000000000..7654a8098c2 --- /dev/null +++ b/api/store/pg/pg_test.go @@ -0,0 +1,85 @@ +package pg_test + +import ( + "context" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/stdlib" + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/api/store/pg" + "github.com/shellhub-io/shellhub/api/store/pg/dbtest" + "github.com/shellhub-io/shellhub/api/store/pg/options" + log "github.com/sirupsen/logrus" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" +) + +var ( + srv = (*dbtest.Server)(nil) + s = (store.Store)(nil) + driver = (*bun.DB)(nil) +) + +func TestMain(m *testing.M) { + log.Info("Starting store tests") + + ctx := context.Background() + + srv = &dbtest.Server{} + + if err := srv.Up(ctx, false); err != nil { + log.WithError(err).Error("Failed to UP the postgres container") + os.Exit(1) + } + + c, err := srv.ConnectionString(ctx) + if err != nil { + log.WithError(err).Error("Failed to parse postgres connection string") + } + + log.Info("Connecting to ", c) + + _, file, _, _ := runtime.Caller(0) + s, err = pg.New(ctx, c, options.Migrate(filepath.Dir(file)), options.WithFixtures(dbtest.FixturesPath())) + if err != nil { + log.WithError(err).Error("Failed to create the postgres store") + os.Exit(1) + } + + driver, err = connectBun(ctx, c) + if err != nil { + log.WithError(err).Error("Failed to create a test driver") + os.Exit(1) + } + + code := m.Run() + + log.Info("Stopping store tests") + if err := srv.Down(ctx); err != nil { + log.WithError(err).Error("Failed to DOWN the postgres container") + os.Exit(1) + } + + os.Exit(code) +} + +func connectBun(ctx context.Context, uri string) (*bun.DB, error) { + config, err := pgxpool.ParseConfig(uri) + if err != nil { + return nil, err + } + + config.ConnConfig.DefaultQueryExecMode = pgx.QueryExecModeSimpleProtocol + + pool, err := pgxpool.NewWithConfig(ctx, config) + if err != nil { + return nil, err + } + + return bun.NewDB(stdlib.OpenDBFromPool(pool), pgdialect.New()), nil +} diff --git a/api/store/pg/private-key.go b/api/store/pg/private-key.go index 7db5a8528f5..46d9a7c3736 100644 --- a/api/store/pg/private-key.go +++ b/api/store/pg/private-key.go @@ -3,15 +3,27 @@ package pg import ( "context" + "github.com/shellhub-io/shellhub/api/store/pg/entity" + "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/models" ) -func (pg *pg) PrivateKeyCreate(ctx context.Context, key *models.PrivateKey) error { - // TODO: private keys are now saved only in the frontend and this can be removed +func (pg *Pg) PrivateKeyCreate(ctx context.Context, key *models.PrivateKey) error { + key.CreatedAt = clock.Now() + key.UpdatedAt = clock.Now() + + if _, err := pg.driver.NewInsert().Model(entity.PrivateKeyFromModel(key)).Exec(ctx); err != nil { + return fromSqlError(err) + } + return nil } -func (pg *pg) PrivateKeyGet(ctx context.Context, fingerprint string) (*models.PrivateKey, error) { - // TODO: private keys are now saved only in the frontend and this can be removedV - return nil, nil +func (pg *Pg) PrivateKeyGet(ctx context.Context, fingerprint string) (*models.PrivateKey, error) { + k := new(entity.PrivateKey) + if err := pg.driver.NewSelect().Model(k).Where("fingerprint = ?", fingerprint).Scan(ctx); err != nil { + return nil, fromSqlError(err) + } + + return entity.PrivateKeyToModel(k), nil } diff --git a/api/store/pg/public-key.go b/api/store/pg/public-key.go index 9335ec6c5d9..bfbe207d498 100644 --- a/api/store/pg/public-key.go +++ b/api/store/pg/public-key.go @@ -3,56 +3,67 @@ package pg import ( "context" - "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/api/store/pg/entity" + "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/models" + "github.com/shellhub-io/shellhub/pkg/uuid" + "github.com/uptrace/bun" ) -func (pg *pg) PublicKeyCreate(ctx context.Context, key *models.PublicKey) error { - return nil -} +func (pg *Pg) PublicKeyCreate(ctx context.Context, publicKey *models.PublicKey) (string, error) { + publicKey.ID = uuid.Generate() + publicKey.CreatedAt = clock.Now() + publicKey.UpdatedAt = clock.Now() -func (pg *pg) PublicKeyList(ctx context.Context, paginator query.Paginator) ([]models.PublicKey, int, error) { - return nil, 0, nil -} + if _, err := pg.driver.NewInsert().Model(entity.PublicKeyFromModel(publicKey)).Exec(ctx); err != nil { + return "", fromSqlError(err) + } -func (pg *pg) PublicKeyGet(ctx context.Context, fingerprint string, tenantID string) (*models.PublicKey, error) { - return nil, nil + return publicKey.ID, nil } -func (pg *pg) PublicKeyUpdate(ctx context.Context, fingerprint string, tenantID string, key *models.PublicKeyUpdate) (*models.PublicKey, error) { - return nil, nil -} +func (pg *Pg) PublicKeyList(ctx context.Context, opts ...store.QueryOption) ([]models.PublicKey, int, error) { + entities := make([]entity.PublicKey, 0) -func (pg *pg) PublicKeyDelete(ctx context.Context, fingerprint string, tenantID string) error { - return nil -} + query := pg.driver.NewSelect().Model(&entities) + if err := applyOptions(ctx, query, opts...); err != nil { + return nil, 0, fromSqlError(err) + } -func (pg *pg) PublicKeyPushTag(ctx context.Context, tenant, fingerprint, tag string) error { - // TODO: refactor tags entirely - return nil -} + count, err := query.ScanAndCount(ctx) + if err != nil { + return nil, 0, fromSqlError(err) + } -func (pg *pg) PublicKeyPullTag(ctx context.Context, tenant, fingerprint, tag string) error { - // TODO: refactor tags entirely - return nil -} + publicKeys := make([]models.PublicKey, len(entities)) + for i, e := range entities { + publicKeys[i] = *entity.PublicKeyToModel(&e) + } -func (pg *pg) PublicKeySetTags(ctx context.Context, tenant, fingerprint string, tags []string) (matchedCount int64, updatedCount int64, err error) { - // TODO: refactor tags entirely - return 0, 0, nil + return publicKeys, count, nil } -func (pg *pg) PublicKeyBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (updatedCount int64, err error) { - // TODO: refactor tags entirely - return 0, nil +func (pg *Pg) PublicKeyGet(ctx context.Context, ident store.PublicKeyIdent, val string, tenantID string) (*models.PublicKey, error) { + a := new(entity.PublicKey) + if err := pg.driver.NewSelect().Model(a).Where("? = ?", bun.Ident(ident), val).Scan(ctx); err != nil { + return nil, fromSqlError(err) + } + + return entity.PublicKeyToModel(a), nil } -func (pg *pg) PublicKeyBulkDeleteTag(ctx context.Context, tenant, tag string) (updatedCount int64, err error) { - // TODO: refactor tags entirely - return 0, nil +func (pg *Pg) PublicKeySave(ctx context.Context, publicKey *models.PublicKey) error { + a := entity.PublicKeyFromModel(publicKey) + a.UpdatedAt = clock.Now() + _, err := pg.driver.NewUpdate().Model(a).WherePK().Exec(ctx) + + return fromSqlError(err) } -func (pg *pg) PublicKeyGetTags(ctx context.Context, tenant string) (tag []string, size int, err error) { - // TODO: refactor tags entirely - return nil, 0, nil +func (pg *Pg) PublicKeyDelete(ctx context.Context, publicKey *models.PublicKey) error { + a := entity.PublicKeyFromModel(publicKey) + _, err := pg.driver.NewDelete().Model(a).WherePK().Exec(ctx) + + return fromSqlError(err) } diff --git a/api/store/pg/query-options.go b/api/store/pg/query-options.go index 3a7ba30f4bc..bd6960e3f5d 100644 --- a/api/store/pg/query-options.go +++ b/api/store/pg/query-options.go @@ -1,17 +1,277 @@ package pg -import "github.com/shellhub-io/shellhub/api/store" +import ( + "context" + "errors" + "slices" + "strconv" + "strings" -// TODO: maybe these methods can be deprecated with bun + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/uptrace/bun" +) -func (pg *pg) Options() store.QueryOptions { - return nil +var ( + ErrQueryNotFound = errors.New("query not found in context") // ErrQueryNotFound is returned when the query context value is not found or has the wrong type + ErrUnsupportedContainsType = errors.New("unsupported value type for contains comparison") // ErrInvalidContainsValue is returned when a 'contains' filter has an unsupported value type + ErrUnsupportedBoolType = errors.New("unsupported value type for boolean conversion") // ErrUnsupportedBoolType is returned when a 'bool' filter receives an unsupported value type + ErrUnsupportedNumericType = errors.New("unsupported value type for numeric comparison") // ErrUnsupportedNumericType is returned when a 'gt' filter receives an unsupported value type +) + +func (pg *Pg) Options() store.QueryOptions { + return pg.options +} + +func (*queryOptions) Paginate(page query.Paginator) store.QueryOption { + return func(ctx context.Context) error { + query, ok := ctx.Value("query").(*bun.SelectQuery) + if !ok { + return ErrQueryNotFound + } + + query = query.Offset(page.PerPage * (page.Page - 1)).Limit(page.PerPage) + + return nil + } +} + +func (*queryOptions) Order(sorter query.Sorter) store.QueryOption { + return func(ctx context.Context) error { + if sorter.By == "" { + return nil + } + + query, ok := ctx.Value("query").(*bun.SelectQuery) + if !ok { + return ErrQueryNotFound + } + + query = query.OrderExpr("? ?", bun.Ident(sorter.By), bun.Safe(strings.ToUpper(sorter.Order))) + + return nil + } +} + +func (*queryOptions) Filter(filters query.Filters) store.QueryOption { + return func(ctx context.Context) error { + if len(filters.Data) < 1 { + return nil + } + + bunQuery, ok := ctx.Value("query").(*bun.SelectQuery) + if !ok { + return ErrQueryNotFound + } + + var filterErr error // variable to store any error occurring in the WhereGroup + bunQuery = bunQuery.WhereGroup("", func(q *bun.SelectQuery) *bun.SelectQuery { + var currentOperator string = "OR" + firstCondition := true + + for _, filter := range filters.Data { + switch filter.Type { + case query.FilterTypeOperator: + param, ok := filter.Params.(*query.FilterOperator) + if !ok { + return nil + } + + op, valid := parseFilterOperator(param) + if !valid { + continue + } + + currentOperator = op + case query.FilterTypeProperty: + param, ok := filter.Params.(*query.FilterProperty) + if !ok { + return nil + } + + condition, args, valid, err := parseFilterProperty(param) + if err != nil || !valid { + filterErr = err + continue + } + + switch { + case firstCondition: // The first condition always applies a WHERE + q = q.Where(condition, args...) + firstCondition = false + case currentOperator == "AND": + q = q.Where(condition, args...) + case currentOperator == "OR": + q = q.WhereOr(condition, args...) + } + default: + return nil + } + } + + return q + }) + + if filterErr != nil { + return filterErr + } + + return nil + } +} + +func (*queryOptions) WithMember(userID string) store.QueryOption { + return func(ctx context.Context) error { + query, ok := ctx.Value("query").(*bun.SelectQuery) + if !ok { + return ErrQueryNotFound + } + + query = query.Where("EXISTS (SELECT 1 FROM memberships WHERE memberships.namespace_id = namespace.id AND memberships.user_id = ?)", userID) + + return nil + } } -func (pg *pg) CountAcceptedDevices() store.NamespaceQueryOption { - return nil +func (*queryOptions) InNamespace(namespaceID string) store.QueryOption { + return func(ctx context.Context) error { + query, ok := ctx.Value("query").(*bun.SelectQuery) + if !ok { + return ErrQueryNotFound + } + + query = query.Where("namespace_id = ?", namespaceID) + + return nil + } +} + +func (*queryOptions) WithStatus(status string) store.QueryOption { + return func(ctx context.Context) error { + query, ok := ctx.Value("query").(*bun.SelectQuery) + if !ok { + return ErrQueryNotFound + } + + query = query.Where("status = ?", status) + + return nil + } +} + +// parseFilterOperator converts a filter operator to its SQL representation. Supported operators are "AND" and "OR". +// It returns the SQL operator string and a boolean indicating if the operator is valid. +func parseFilterOperator(op *query.FilterOperator) (string, bool) { + return strings.ToUpper(op.Name), slices.Contains([]string{"AND", "OR"}, strings.ToUpper(op.Name)) +} + +// parseFilterProperty constructs the SQL representation of a property filter. It returns a SQL condition string, SQL +// arguments array, boolean indicating if the operator is valid and an error, if any +func parseFilterProperty(fp *query.FilterProperty) (string, []any, bool, error) { + var condition string + var args []any + var err error + + switch fp.Operator { + case "contains": + condition, args, err = fromContains(fp.Name, fp.Value) + case "eq": + condition, args, err = fromEq(fp.Name, fp.Value) + case "bool": + condition, args, err = fromBool(fp.Name, fp.Value) + case "gt": + condition, args, err = fromGt(fp.Name, fp.Value) + case "ne": + condition, args, err = fromNe(fp.Name, fp.Value) + default: + return "", nil, false, nil + } + + if err != nil { + return "", nil, false, err + } + + return condition, args, true, nil +} + +// fromContains converts a "contains" JSON expression to an SQL expression. For strings, it uses ILIKE with '%value%' +// for case-insensitive substring matching. For arrays, it uses the @> (contains) operator to check if the column +// contains all the values in the array. Returns SQL condition string, arguments array, and error if any. +func fromContains(column string, value any) (string, []any, error) { + switch v := value.(type) { + case string: + return "? ILIKE ?", []any{bun.Ident(column), "%" + v + "%"}, nil + case []any: + return "? @> ?", []any{bun.Ident(column), v}, nil + } + + return "", nil, ErrUnsupportedContainsType +} + +// fromEq converts an "eq" (equals) JSON expression to an SQL expression using =. +// Returns SQL condition string, arguments array, and error if any. +func fromEq(column string, value any) (string, []any, error) { + return "? = ?", []any{bun.Ident(column), value}, nil +} + +// fromBool converts a "bool" JSON expression to an SQL expression. It handles various input types (int, string, bool) +// and converts them to boolean values. +// +// - For integers: 0 is false, anything else is true +// +// - For strings: uses strconv.ParseBool +// +// - For booleans: uses the value directly +// +// Returns SQL condition string, arguments array, and error if any. +func fromBool(column string, value any) (string, []any, error) { + var boolValue bool + + switch v := value.(type) { + case int: + boolValue = v != 0 + case string: + var err error + boolValue, err = strconv.ParseBool(v) + if err != nil { + return "", nil, err + } + case bool: + boolValue = v + default: + return "", nil, ErrUnsupportedBoolType + } + + return "? = ?", []any{bun.Ident(column), boolValue}, nil +} + +// fromGt converts a "gt" (greater than) JSON expression to an SQL expression using >. It handles various numeric types +// (int, float, etc.) and string representations of numbers. For strings, it attempts to convert to int first, then to +// float if int conversion fails. Returns SQL condition string, arguments array, and error if any. +func fromGt(column string, value any) (string, []any, error) { + switch v := value.(type) { + case uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64, float32, float64: + return "? > ?", []any{bun.Ident(column), v}, nil + case string: + var num any + var err error + + num, err = strconv.Atoi(v) + if err != nil { + num, err = strconv.ParseFloat(v, 64) + if err != nil { + return "", nil, err + } + } + + return "? > ?", []any{bun.Ident(column), num}, nil + default: + return "", nil, ErrUnsupportedNumericType + } } -func (pg *pg) EnrichMembersData() store.NamespaceQueryOption { - return nil +// fromNe converts a "ne" (not equals) JSON expression to an SQL expression using <>. Returns SQL condition string, +// arguments array, and error if any. +func fromNe(column string, value any) (string, []any, error) { + return "? <> ?", []any{bun.Ident(column), value}, nil } diff --git a/api/store/pg/session.go b/api/store/pg/session.go index 62e17a1e409..e5aa1a41c9b 100644 --- a/api/store/pg/session.go +++ b/api/store/pg/session.go @@ -7,50 +7,50 @@ import ( "github.com/shellhub-io/shellhub/pkg/models" ) -func (pg *pg) SessionList(ctx context.Context, paginator query.Paginator) ([]models.Session, int, error) { +func (pg *Pg) SessionList(ctx context.Context, paginator query.Paginator) ([]models.Session, int, error) { return nil, 0, nil } -func (pg *pg) SessionGet(ctx context.Context, uid models.UID) (*models.Session, error) { +func (pg *Pg) SessionGet(ctx context.Context, uid models.UID) (*models.Session, error) { return nil, nil } -func (pg *pg) SessionUpdate(ctx context.Context, uid models.UID, model *models.Session) error { +func (pg *Pg) SessionUpdate(ctx context.Context, uid models.UID, model *models.Session) error { return nil } -func (pg *pg) SessionSetRecorded(ctx context.Context, uid models.UID, recorded bool) error { +func (pg *Pg) SessionSetRecorded(ctx context.Context, uid models.UID, recorded bool) error { return nil } -func (pg *pg) SessionCreate(ctx context.Context, session models.Session) (*models.Session, error) { +func (pg *Pg) SessionCreate(ctx context.Context, session models.Session) (*models.Session, error) { return nil, nil } -func (pg *pg) SessionSetLastSeen(ctx context.Context, uid models.UID) error { +func (pg *Pg) SessionSetLastSeen(ctx context.Context, uid models.UID) error { return nil } -func (pg *pg) SessionDeleteActives(ctx context.Context, uid models.UID) error { +func (pg *Pg) SessionDeleteActives(ctx context.Context, uid models.UID) error { return nil } -func (pg *pg) SessionUpdateDeviceUID(ctx context.Context, oldUID models.UID, newUID models.UID) error { +func (pg *Pg) SessionUpdateDeviceUID(ctx context.Context, oldUID models.UID, newUID models.UID) error { return nil } -func (pg *pg) SessionActiveCreate(ctx context.Context, uid models.UID, session *models.Session) error { +func (pg *Pg) SessionActiveCreate(ctx context.Context, uid models.UID, session *models.Session) error { return nil } -func (pg *pg) SessionEvent(ctx context.Context, uid models.UID, event *models.SessionEvent) error { +func (pg *Pg) SessionEvent(ctx context.Context, uid models.UID, event *models.SessionEvent) error { return nil } -func (pg *pg) SessionListEvents(ctx context.Context, uid models.UID, seat int, event models.SessionEventType, paginator query.Paginator) ([]models.SessionEvent, int, error) { +func (pg *Pg) SessionListEvents(ctx context.Context, uid models.UID, seat int, event models.SessionEventType, paginator query.Paginator) ([]models.SessionEvent, int, error) { return nil, 0, nil } -func (pg *pg) SessionDeleteEvents(ctx context.Context, uid models.UID, seat int, event models.SessionEventType) error { +func (pg *Pg) SessionDeleteEvents(ctx context.Context, uid models.UID, seat int, event models.SessionEventType) error { return nil } diff --git a/api/store/pg/system.go b/api/store/pg/system.go index 0576d50ff74..5a013d88f85 100644 --- a/api/store/pg/system.go +++ b/api/store/pg/system.go @@ -8,10 +8,10 @@ import ( // TODO: maybe systems config can be an file? -func (pg *pg) SystemGet(ctx context.Context) (*models.System, error) { +func (pg *Pg) SystemGet(ctx context.Context) (*models.System, error) { return nil, nil } -func (pg *pg) SystemSet(ctx context.Context, key string, value any) error { +func (pg *Pg) SystemSet(ctx context.Context, key string, value any) error { return nil } diff --git a/api/store/pg/tags.go b/api/store/pg/tags.go index 334582e8979..947c4f91045 100644 --- a/api/store/pg/tags.go +++ b/api/store/pg/tags.go @@ -4,14 +4,14 @@ import "context" // TODO: refactor tags entirely -func (pg *pg) TagsGet(ctx context.Context, tenant string) (tags []string, n int, err error) { +func (pg *Pg) TagsGet(ctx context.Context, tenant string) (tags []string, n int, err error) { return nil, 0, nil } -func (pg *pg) TagsRename(ctx context.Context, tenant string, oldTag string, newTag string) (updatedCount int64, err error) { +func (pg *Pg) TagsRename(ctx context.Context, tenant string, oldTag string, newTag string) (updatedCount int64, err error) { return 0, nil } -func (pg *pg) TagsDelete(ctx context.Context, tenant string, tag string) (updatedCount int64, err error) { +func (pg *Pg) TagsDelete(ctx context.Context, tenant string, tag string) (updatedCount int64, err error) { return 0, nil } diff --git a/api/store/pg/transaction.go b/api/store/pg/transaction.go index 726ea9e181b..62ac45b8f44 100644 --- a/api/store/pg/transaction.go +++ b/api/store/pg/transaction.go @@ -8,6 +8,6 @@ import ( // TODO: this works with mongodb, but will works with bun? -func (pg *pg) WithTransaction(ctx context.Context, cb store.TransactionCb) error { +func (pg *Pg) WithTransaction(ctx context.Context, cb store.TransactionCb) error { return nil } diff --git a/api/store/pg/user.go b/api/store/pg/user.go index a5a5d0bf996..bf8e2f046b0 100644 --- a/api/store/pg/user.go +++ b/api/store/pg/user.go @@ -3,48 +3,105 @@ package pg import ( "context" + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/api/store/pg/entity" "github.com/shellhub-io/shellhub/pkg/api/query" + "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/models" + "github.com/shellhub-io/shellhub/pkg/uuid" + "github.com/uptrace/bun" ) -func (pg *pg) UserCreate(ctx context.Context, user *models.User) (string, error) { - return "", nil -} +func (pg *Pg) UserCreate(ctx context.Context, user *models.User) (string, error) { + user.ID = uuid.Generate() + user.CreatedAt = clock.Now() + user.UpdatedAt = clock.Now() -func (pg *pg) UserCreateInvited(ctx context.Context, email string) (string, error) { - // TODO: unify create methods - return "", nil -} + if _, err := pg.driver.NewInsert().Model(entity.UserFromModel(user)).Exec(ctx); err != nil { + return "", err + } -func (pg *pg) UserConflicts(ctx context.Context, target *models.UserConflicts) ([]string, bool, error) { - return nil, false, nil + return user.ID, nil } -func (pg *pg) UserList(ctx context.Context, paginator query.Paginator, filters query.Filters) ([]models.User, int, error) { - return nil, 0, nil +func (pg *Pg) UserConflicts(ctx context.Context, target *models.UserConflicts) ([]string, bool, error) { + users := make([]map[string]any, 0) + if err := pg.driver.NewSelect().Model((*entity.User)(nil)).Column("email").Where("email = ?", target.Email).Scan(ctx, &users); err != nil { + return nil, false, err + } + + conflicts := make([]string, 0) + for _, user := range users { + if user["email"] == target.Email { + conflicts = append(conflicts, "email") + } + } + + return conflicts, len(conflicts) > 0, nil } -func (pg *pg) UserGetByID(ctx context.Context, id string, ns bool) (*models.User, int, error) { +func (pg *Pg) UserList(ctx context.Context, paginator query.Paginator, filters query.Filters) ([]models.User, int, error) { return nil, 0, nil } -func (pg *pg) UserGetByUsername(ctx context.Context, username string) (*models.User, error) { - return nil, nil -} +func (pg *Pg) UserGet(ctx context.Context, ident store.UserIdent, val string) (*models.User, error) { + u := new(entity.User) + if err := pg.driver.NewSelect().Model(u).Where("? = ?", bun.Ident(ident), val).Scan(ctx); err != nil { + return nil, fromSqlError(err) + } -func (pg *pg) UserGetByEmail(ctx context.Context, email string) (*models.User, error) { - return nil, nil + return entity.UserToModel(u), nil } -func (pg *pg) UserGetInfo(ctx context.Context, id string) (userInfo *models.UserInfo, err error) { +func (pg *Pg) UserGetInfo(ctx context.Context, id string) (userInfo *models.UserInfo, err error) { // TODO: unify get methods return nil, nil } -func (pg *pg) UserUpdate(ctx context.Context, id string, changes *models.UserChanges) error { - return nil +func (pg *Pg) UserPreferredNamespace(ctx context.Context, ident store.UserIdent, val string) (*models.Namespace, error) { + ns := new(entity.Namespace) + if err := pg.driver.NewSelect(). + Model(ns). + Relation("Memberships.User"). + Join("JOIN users"). + JoinOn("namespace.id = users.preferred_namespace_id OR namespace.id IN (SELECT namespace_id FROM memberships WHERE user_id = users.id)"). // TODO: subquery + Where("users.? = ?", bun.Ident(ident), val). + OrderExpr("CASE WHEN namespace.id = users.preferred_namespace_id THEN 0 ELSE 1 END"). // TODO: segunda ordenacao pela membership mais recente + Limit(1). + Scan(ctx); err != nil { + return nil, fromSqlError(err) + } + + return entity.NamespaceToModel(ns), nil } -func (pg *pg) UserDelete(ctx context.Context, id string) error { - return nil +func (pg *Pg) UserSave(ctx context.Context, user *models.User) error { + u := entity.UserFromModel(user) + u.UpdatedAt = clock.Now() + + r, err := pg.driver.NewUpdate().Model(u).WherePK().Exec(ctx) + if err != nil { + return fromSqlError(err) + } + + if rowsAffected, err := r.RowsAffected(); err != nil || rowsAffected == 0 { + return store.ErrNoDocuments + } + + return fromSqlError(err) +} + +func (pg *Pg) UserDelete(ctx context.Context, user *models.User) error { + u := entity.UserFromModel(user) + + r, err := pg.driver.NewDelete().Model(u).WherePK().Exec(ctx) + if err != nil { + return fromSqlError(err) + } + + if rowsAffected, err := r.RowsAffected(); err != nil || rowsAffected == 0 { + return store.ErrNoDocuments + } + + return fromSqlError(err) } diff --git a/api/store/pg/user_test.go b/api/store/pg/user_test.go new file mode 100644 index 00000000000..80695dda9e2 --- /dev/null +++ b/api/store/pg/user_test.go @@ -0,0 +1,355 @@ +package pg_test + +import ( + "context" + "regexp" + "testing" + "time" + + "github.com/shellhub-io/shellhub/api/store" + "github.com/shellhub-io/shellhub/api/store/pg/entity" + "github.com/shellhub-io/shellhub/pkg/clock" + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/shellhub-io/shellhub/pkg/uuid" + "github.com/stretchr/testify/require" +) + +func TestPg_UserCreate(t *testing.T) { + cases := []struct { + it string + ctx context.Context + user *models.User + assert func(context.Context, *testing.T) + }{ + { + it: "should generate an UUIDv7", + ctx: context.Background(), + user: &models.User{ + Status: models.UserStatusConfirmed, + Origin: models.UserOriginLocal, + ExternalID: "", + Name: "Paul Bryson", + Email: "paul.bryson@test.com", + PasswordDigest: "$2y$12$VVm2ETx7AvaGlfMYqNYK9uzU2M45YZ70YnT..O.s1o2zdE1pekhq6", + MaxNamespaces: -1, + EmailMarketing: true, + Preferences: models.UserPreferences{ + RecoveryEmail: "jane.smith@test.com", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + PreferredNamespace: "", + }, + }, + assert: func(ctx context.Context, t *testing.T) { + u := new(models.User) + require.NoError(t, driver.NewSelect().Model(u).Where("email = ?", "paul.bryson@test.com").Scan(ctx)) + + uuidPattern := regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`) + require.True(t, uuidPattern.MatchString(u.ID)) + }, + }, + { + it: "should set CreatedAt and UpdatedAt timestamps", + ctx: context.Background(), + user: &models.User{ + Status: models.UserStatusConfirmed, + Origin: models.UserOriginLocal, + Name: "Stephen Aaron", + Username: "stephen_aaron", + Email: "stephen.aaron@test.com", + PasswordDigest: "$2y$12$AnotherHashedPassword", + MaxNamespaces: -1, + EmailMarketing: true, + Preferences: models.UserPreferences{ + RecoveryEmail: "jane.smith@test.com", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + PreferredNamespace: "", + }, + }, + assert: func(ctx context.Context, t *testing.T) { + u := new(models.User) + require.NoError(t, driver.NewSelect().Model(u).Where("email = ?", "stephen.aaron@test.com").Scan(ctx)) + + require.WithinDuration(t, time.Now(), u.CreatedAt, 1*time.Second) + require.WithinDuration(t, time.Now(), u.UpdatedAt, 1*time.Second) + }, + }, + } + + for _, tc := range cases { + t.Run(tc.it, func(tt *testing.T) { + tt.Parallel() + + insertedID, err := s.UserCreate(tc.ctx, tc.user) + require.NoError(t, err) + tc.assert(tc.ctx, tt) + + _, err = driver.NewDelete().Table("users").Where("id = ?", insertedID).Exec(tc.ctx) + require.NoError(tt, err) + }) + } +} + +func TestPg_UserGet(t *testing.T) { + type Expected struct { + user *models.User + err error + } + + cases := []struct { + it string + ctx context.Context + ident store.UserIdent + val string + expected Expected + }{ + { + it: "should returns an error when the user does not exists", + ctx: context.Background(), + ident: store.UserIdentEmail, + val: "nonexistent@test.com", + expected: Expected{ + user: nil, + err: store.ErrNoDocuments, + }, + }, + { + it: "should returns an error when the user does not exists", + ctx: context.Background(), + ident: store.UserIdentEmail, + val: "john.doe@test.com", + expected: Expected{ + user: &models.User{ + ID: "0195cefa-aa01-7efb-8098-c9c173056250", + CreatedAt: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC), + UpdatedAt: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC), + LastLogin: time.Time{}, + Status: models.UserStatusConfirmed, + Origin: models.UserOriginLocal, + ExternalID: "", + Name: "Jonh Doe", + Username: "john_doe", + Email: "john.doe@test.com", + MaxNamespaces: -1, + PasswordDigest: "$2y$12$VVm2ETx7AvaGlfMYqNYK9uzU2M45YZ70YnT..O.s1o2zdE1pekhq6", + EmailMarketing: true, + Preferences: models.UserPreferences{ + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + RecoveryEmail: "jane.smith@test.com", + PreferredNamespace: "", + }, + }, + err: nil, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.it, func(tt *testing.T) { + tt.Parallel() + + user, err := s.UserGet(tc.ctx, tc.ident, tc.val) + require.Equal(tt, tc.expected, Expected{user, err}) + }) + } +} + +func TestPg_UserSave(t *testing.T) { + newUser := func(ctx context.Context) (*models.User, error) { + user := &models.User{ + Status: models.UserStatusConfirmed, + Origin: models.UserOriginLocal, + Name: "Test User For Save", + Username: "test_save_user", + Email: "test.save@test.com", + PasswordDigest: "$2y$12$VVm2ETx7AvaGlfMYqNYK9uzU2M45YZ70YnT..O.s1o2zdE1pekhq6", + EmailMarketing: true, + MaxNamespaces: -1, + Preferences: models.UserPreferences{ + RecoveryEmail: "recovery.email@test.com", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + PreferredNamespace: "", + }, + } + + id, err := s.UserCreate(ctx, user) + if err != nil { + return nil, err + } + + // Recupera o usuário completo do banco + return s.UserGet(ctx, store.UserIdentID, id) + } + + type Expected struct { + err error + } + + cases := []struct { + it string + ctx context.Context + setup func(context.Context) (*models.User, error) + update func(*models.User) *models.User + mocks func() + ensure func(context.Context, *models.User, *testing.T) + expected Expected + }{ + { + it: "should update user and set UpdatedAt", + ctx: context.Background(), + setup: func(ctx context.Context) (*models.User, error) { + return newUser(ctx) + }, + update: func(user *models.User) *models.User { + // Cria uma cópia com as alterações que queremos aplicar + updatedUser := *user + updatedUser.Name = "Updated Name" + updatedUser.Username = "updated_username" + updatedUser.Email = "updated.email@test.com" + return &updatedUser + }, + mocks: func() { + mockClock := new(clockmock.Clock) + clock.DefaultBackend = mockClock + mockClock.On("Now").Return(time.Date(2025, 2, 1, 10, 0, 0, 0, time.UTC)) + }, + ensure: func(ctx context.Context, user *models.User, t *testing.T) { + // Recupera o usuário do banco para verificar se as alterações foram aplicadas + updatedUser, err := s.UserGet(ctx, store.UserIdentID, user.ID) + require.NoError(t, err) + + require.Equal(t, "Updated Name", updatedUser.Name) + require.Equal(t, "updated_username", updatedUser.Username) + require.Equal(t, "updated.email@test.com", updatedUser.Email) + require.Equal(t, time.Date(2025, 2, 1, 10, 0, 0, 0, time.UTC), updatedUser.UpdatedAt) + }, + expected: Expected{ + err: nil, + }, + }, + { + it: "should return error when user does not exist", + ctx: context.Background(), + setup: func(ctx context.Context) (*models.User, error) { + // Cria um usuário que não existe no banco + return &models.User{ + ID: uuid.Generate(), // ID aleatório que não deve existir + }, nil + }, + update: func(user *models.User) *models.User { + // Não precisamos alterar nada, apenas retornar o mesmo usuário + return user + }, + mocks: nil, + ensure: nil, + expected: Expected{ + err: store.ErrNoDocuments, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.it, func(tt *testing.T) { + tt.Parallel() + + // Configuração de mocks, se necessário + if tc.mocks != nil { + tc.mocks() + } + + // Preparação: cria o usuário para o teste + originalUser, err := tc.setup(tc.ctx) + require.NoError(tt, err) + + // Atualiza o usuário com as modificações definidas no caso de teste + userToUpdate := tc.update(originalUser) + + require.Equal(tt, tc.expected.err,s.UserSave(tc.ctx, userToUpdate)) + tc.ensure(tc.ctx, userToUpdate, tt) + }) + } +} + +func TestPg_UserDelete(t *testing.T) { + newUser := func(ctx context.Context) (string, error) { + user := &models.User{ + ID: uuid.Generate(), + CreatedAt: clock.Now(), + UpdatedAt: clock.Now(), + LastLogin: clock.Now(), + Status: models.UserStatusConfirmed, + Origin: models.UserOriginLocal, + ExternalID: "", + Name: "Test User For Delete", + Username: "test_delete_user", + Email: "test.delete@test.com", + PasswordDigest: "$2y$12$VVm2ETx7AvaGlfMYqNYK9uzU2M45YZ70YnT..O.s1o2zdE1pekhq6", + EmailMarketing: true, + MaxNamespaces: -1, + Preferences: models.UserPreferences{ + RecoveryEmail: "recovery.email@test.com", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + PreferredNamespace: "", + }, + } + + id, err := s.UserCreate(ctx, user) + return id, err + } + + type Expected struct { + err error + ensure func(*testing.T, string) + } + + cases := []struct { + it string + ctx context.Context + setup func(context.Context) (*models.User, error) + expected Expected + }{ + { + it: "should return error when user does not exist", + ctx: context.Background(), + setup: func(ctx context.Context) (*models.User, error) { + return &models.User{ID: uuid.Generate()}, nil + }, + expected: Expected{ + err: store.ErrNoDocuments, + ensure: func(*testing.T, string) {}, + }, + }, + { + it: "should delete user when exists", + ctx: context.Background(), + setup: func(ctx context.Context) (*models.User, error) { + id, err := newUser(ctx) + if err != nil { + return nil, err + } + + user, err := s.UserGet(ctx, store.UserIdentID, id) + return user, err + }, + expected: Expected{ + err: nil, + ensure: func(t *testing.T, id string) { + exists, err := driver.NewSelect().Model((*entity.User)(nil)).Where("id = ?", id).Exists(context.TODO()) + require.NoError(t, err) + require.False(t, exists) + }, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.it, func(tt *testing.T) { + tt.Parallel() + + user, err := tc.setup(tc.ctx) + require.NoError(tt, err) + + require.Equal(tt, tc.expected.err, s.UserDelete(tc.ctx, user)) + tc.expected.ensure(tt, user.ID) + }) + } +} diff --git a/api/store/pg/utils.go b/api/store/pg/utils.go index 920499cc6f6..623bb307d23 100644 --- a/api/store/pg/utils.go +++ b/api/store/pg/utils.go @@ -1,10 +1,12 @@ package pg import ( + "context" "database/sql" "io" "github.com/shellhub-io/shellhub/api/store" + "github.com/uptrace/bun" ) func fromSqlError(err error) error { @@ -17,3 +19,14 @@ func fromSqlError(err error) error { return err } } + +func applyOptions(ctx context.Context, query *bun.SelectQuery, opts ...store.QueryOption) error { + ctxWithQuery := context.WithValue(ctx, "query", query) + for _, opt := range opts { + if err := opt(ctxWithQuery); err != nil { + return fromSqlError(err) + } + } + + return nil +} diff --git a/api/store/publickey.go b/api/store/publickey.go index e2fc31a9146..859a7270809 100644 --- a/api/store/publickey.go +++ b/api/store/publickey.go @@ -3,14 +3,19 @@ package store import ( "context" - "github.com/shellhub-io/shellhub/pkg/api/query" "github.com/shellhub-io/shellhub/pkg/models" ) +type PublicKeyIdent string + +const ( + PublicKeyIdentFingerprint PublicKeyIdent = "fingerprint" +) + type PublicKeyStore interface { - PublicKeyList(ctx context.Context, paginator query.Paginator) ([]models.PublicKey, int, error) - PublicKeyGet(ctx context.Context, fingerprint string, tenantID string) (*models.PublicKey, error) - PublicKeyCreate(ctx context.Context, key *models.PublicKey) error - PublicKeyUpdate(ctx context.Context, fingerprint string, tenantID string, key *models.PublicKeyUpdate) (*models.PublicKey, error) - PublicKeyDelete(ctx context.Context, fingerprint string, tenantID string) error + PublicKeyCreate(ctx context.Context, publicKey *models.PublicKey) (string, error) + PublicKeyList(ctx context.Context, opts ...QueryOption) ([]models.PublicKey, int, error) + PublicKeyGet(ctx context.Context, ident PublicKeyIdent, val string, tenantID string) (*models.PublicKey, error) + PublicKeySave(ctx context.Context, publicKey *models.PublicKey) error + PublicKeyDelete(ctx context.Context, publicKey *models.PublicKey) error } diff --git a/api/store/publickey_tags.go b/api/store/publickey_tags.go deleted file mode 100644 index 9c2abc6bdee..00000000000 --- a/api/store/publickey_tags.go +++ /dev/null @@ -1,35 +0,0 @@ -package store - -import "context" - -type PublicKeyTagsStore interface { - // PublicKeyPushTag adds a new tag to the list of tags for a device with the specified UID. - // Returns an error if any issues occur during the tag addition or ErrNoDocuments when matching documents are found. - // - // The tag need to exist on a device. If it is not true, the action will fail. - PublicKeyPushTag(ctx context.Context, tenant, fingerprint, tag string) error - - // PublicKeyPullTag removes a tag from the list of tags for a device with the specified UID. - // Returns an error if any issues occur during the tag removal or ErrNoDocuments when matching documents are found. - // - // To remove a tag, that tag needs to exist on a device. If it is not, the action will fail. - PublicKeyPullTag(ctx context.Context, tenant, fingerprint, tag string) error - - // PublicKeySetTags sets the tags for a public key with the specified fingerprint and tenant. - // It returns the number of matching documents, the number of modified documents, and any encountered errors. - // - // All tags need to exist on a device. If it is not true, the update action will fail. - PublicKeySetTags(ctx context.Context, tenant, fingerprint string, tags []string) (matchedCount int64, updatedCount int64, err error) - - // PublicKeyBulkRenameTag replaces all occurrences of the old tag with the new tag for all public keys to the specified tenant. - // Returns the number of documents updated and an error if any issues occur during the tag renaming. - PublicKeyBulkRenameTag(ctx context.Context, tenant, currentTag, newTag string) (updatedCount int64, err error) - - // PublicKeyBulkDeleteTag removes a tag from all public keys belonging to the specified tenant. - // Returns the number of documents updated and an error if any issues occur during the tag deletion. - PublicKeyBulkDeleteTag(ctx context.Context, tenant, tag string) (updatedCount int64, err error) - - // PublicKeyGetTags retrieves all tags associated with the tenant. - // Returns the tags, the number of tags, and an error if any issues occur. - PublicKeyGetTags(ctx context.Context, tenant string) (tag []string, size int, err error) -} diff --git a/api/store/query-options.go b/api/store/query-options.go index 4aa734df598..6bba02a9f19 100644 --- a/api/store/query-options.go +++ b/api/store/query-options.go @@ -3,16 +3,16 @@ package store import ( "context" - "github.com/shellhub-io/shellhub/pkg/models" + "github.com/shellhub-io/shellhub/pkg/api/query" ) -type NamespaceQueryOption func(ctx context.Context, ns *models.Namespace) error +type QueryOption func(ctx context.Context) error type QueryOptions interface { - // CountAcceptedDevices counts the devices with a status 'accepted' - // in the namespace. - CountAcceptedDevices() NamespaceQueryOption - - // EnrichMembersData join the user's data into members array. - EnrichMembersData() NamespaceQueryOption + Paginate(query.Paginator) QueryOption + Order(query.Sorter) QueryOption + Filter(query.Filters) QueryOption + WithMember(string) QueryOption + InNamespace(string) QueryOption + WithStatus(string) QueryOption } diff --git a/api/store/store.go b/api/store/store.go index 7f6be01e33d..9f97cbec21f 100644 --- a/api/store/store.go +++ b/api/store/store.go @@ -4,12 +4,10 @@ package store type Store interface { TagsStore DeviceStore - DeviceTagsStore SessionStore UserStore NamespaceStore PublicKeyStore - PublicKeyTagsStore PrivateKeyStore StatsStore APIKeyStore diff --git a/api/store/user.go b/api/store/user.go index b25acb5fd7b..2802021804e 100644 --- a/api/store/user.go +++ b/api/store/user.go @@ -7,24 +7,19 @@ import ( "github.com/shellhub-io/shellhub/pkg/models" ) -type UserStore interface { - UserList(ctx context.Context, paginator query.Paginator, filters query.Filters) ([]models.User, int, error) +type UserIdent string +const ( + UserIdentID UserIdent = "id" + UserIdentEmail UserIdent = "email" + UserIdentUsername UserIdent = "username" +) + +type UserStore interface { // UserCreate creates a new user with the provided data. `user.CreatedAt` is set to now before save. // It returns the inserted ID or an error, if any. UserCreate(ctx context.Context, user *models.User) (insertedID string, err error) - // UserCreateInvited creates a new user with the status `UserStatusInvited`. This kind of user requires - // only an email, which must be unique. These users are not fully registered and must complete their - // registration process before they can proceed to access other parts of the application. - // - // It returns the inserted ID or an error, if any. - UserCreateInvited(ctx context.Context, email string) (insertedID string, err error) - - UserGetByUsername(ctx context.Context, username string) (*models.User, error) - UserGetByEmail(ctx context.Context, email string) (*models.User, error) - UserGetByID(ctx context.Context, id string, ns bool) (*models.User, int, error) - // UserConflicts reports whether the target contains conflicting attributes with the database. Pass zero values for // attributes you do not wish to match on. For example, the following call checks for conflicts based on email only: // @@ -34,21 +29,20 @@ type UserStore interface { // It returns an array of conflicting attribute fields and an error, if any. UserConflicts(ctx context.Context, target *models.UserConflicts) (conflicts []string, has bool, err error) - // UserUpdate updates a user with the specified ID using the given changes. Any zero values in the changes - // (e.g. empty strings) will be ignored during the update. For instance, the following call updates - // only the LastLogin attribute: - // - // err := s.store.UserUpdate(ctx, id, &models.UserChanges{LastLogin: time.Now()}) - // - // - // It returns an error if any. - // - // NOTE: The changes parameter can accept pointers, in which case a zero value will be represented as "nil". - UserUpdate(ctx context.Context, id string, changes *models.UserChanges) error + UserList(ctx context.Context, paginator query.Paginator, filters query.Filters) ([]models.User, int, error) + + // UserGet retrieves a user based on the provided [UserIdent]. It returns an error if none record was found. + UserGet(ctx context.Context, ident UserIdent, val string) (*models.User, error) // UserGetInfo retrieves the user's information, like the owned and associated namespaces. // It returns an error if the user is not part of any namespace. UserGetInfo(ctx context.Context, id string) (userInfo *models.UserInfo, err error) - UserDelete(ctx context.Context, id string) error + UserPreferredNamespace(ctx context.Context, ident UserIdent, val string) (*models.Namespace, error) + + // UserSave updates the user. It returns an error if any. + UserSave(ctx context.Context, user *models.User) (err error) + + // UserSave deletes the user. It returns an error if any. + UserDelete(ctx context.Context, user *models.User) (err error) } diff --git a/cli/go.mod b/cli/go.mod index d7b3e916ed5..41032f2d964 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,8 +1,8 @@ module github.com/shellhub-io/shellhub/cli -go 1.22.4 +go 1.23.0 -toolchain go1.22.8 +toolchain go1.24.1 require ( github.com/shellhub-io/shellhub v0.13.4 @@ -25,21 +25,31 @@ require ( github.com/jackc/pgx/v5 v5.7.4 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/labstack/echo/v4 v4.13.3 // indirect + github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.2.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/oiime/logrusbun v0.1.2-0.20241011112815-4df3a0fb0e11 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sethvargo/go-envconfig v0.9.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/uptrace/bun v1.2.11 // indirect + github.com/uptrace/bun/dbfixture v1.2.11 // indirect github.com/uptrace/bun/dialect/pgdialect v1.2.11 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/cli/go.sum b/cli/go.sum index 1bf4120cdbf..3ad8af04339 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,7 +1,39 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= +github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -10,6 +42,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -26,22 +60,63 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= +github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 h1:1KuuSOy4ZNgW0KA2oYIngXVFhQcXxhLqCVK7cBcldkk= +github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/oiime/logrusbun v0.1.2-0.20241011112815-4df3a0fb0e11 h1:rAqW9sGcM0VsfBwgeBzHk0yebrRwfeSJFy9Egqi0fmM= +github.com/oiime/logrusbun v0.1.2-0.20241011112815-4df3a0fb0e11/go.mod h1:HH9akx9teKgQPX41TYpLLRNxaL8q9R+ltzABnwUHfBM= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE= github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= +github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= +github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -53,32 +128,67 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= +github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= +github.com/testcontainers/testcontainers-go/modules/postgres v0.37.0 h1:hsVwFkS6s+79MbKEO+W7A1wNIw1fmkMtF4fg83m6kbc= +github.com/testcontainers/testcontainers-go/modules/postgres v0.37.0/go.mod h1:Qj/eGbRbO/rEYdcRLmN+bEojzatP/+NS1y8ojl2PQsc= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun v0.3.9/go.mod h1:aL6D9vPw8DXaTQTwGrEPtUderBYXx7ShUmPfnxnqscw= github.com/uptrace/bun v1.2.11 h1:l9dTymsdZZAoSZ1+Qo3utms0RffgkDbIv+1UGk8N1wQ= github.com/uptrace/bun v1.2.11/go.mod h1:ww5G8h59UrOnCHmZ8O1I/4Djc7M/Z3E+EWFS2KLB6dQ= +github.com/uptrace/bun/dbfixture v1.2.11 h1:9rKYVDwQCLdXtPkgzMgvDsHKHXelNlXmnpuNHvFz2w0= +github.com/uptrace/bun/dbfixture v1.2.11/go.mod h1:E2H4A4/dx6+xB61qGsjVqV17Pqyb+Gg5QTmswy4hUpk= github.com/uptrace/bun/dialect/pgdialect v1.2.11 h1:n0VKWm1fL1dwJK5TRxYYLaRKRe14BOg2+AQgpvqzG/M= github.com/uptrace/bun/dialect/pgdialect v1.2.11/go.mod h1:NvV1S/zwtwBnW8yhJ3XEKAQEw76SkeH7yUhfrx3W1Eo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/cli/main.go b/cli/main.go index 0175ddfee26..42f73e7ba86 100644 --- a/cli/main.go +++ b/cli/main.go @@ -4,6 +4,7 @@ import ( "context" "github.com/shellhub-io/shellhub/api/store/pg" + "github.com/shellhub-io/shellhub/api/store/pg/options" "github.com/shellhub-io/shellhub/cli/cmd" "github.com/shellhub-io/shellhub/cli/services" "github.com/shellhub-io/shellhub/pkg/envs" @@ -42,7 +43,7 @@ func main() { } uri := pg.URI(cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresUser, cfg.PostgresPassword, cfg.PostgresDB) - store, err := pg.New(ctx, uri) + store, err := pg.New(ctx, uri, options.Log("DEBUG", true)) if err != nil { log. WithError(err). diff --git a/cli/services/namespaces.go b/cli/services/namespaces.go index 5b68a91b1cc..3b6122ed9ed 100644 --- a/cli/services/namespaces.go +++ b/cli/services/namespaces.go @@ -3,6 +3,7 @@ package services import ( "context" + "github.com/shellhub-io/shellhub/api/store" "github.com/shellhub-io/shellhub/cli/pkg/inputs" "github.com/shellhub-io/shellhub/pkg/api/authorizer" "github.com/shellhub-io/shellhub/pkg/clock" @@ -23,7 +24,7 @@ func (s *service) NamespaceCreate(ctx context.Context, input *inputs.NamespaceCr return nil, ErrNamespaceInvalid } - user, err := s.store.UserGetByUsername(ctx, input.Owner) + user, err := s.store.UserGet(ctx, store.UserIdentUsername, input.Owner) if err != nil { return nil, ErrUserNotFound } @@ -55,11 +56,14 @@ func (s *service) NamespaceCreate(ctx context.Context, input *inputs.NamespaceCr ns.Type = models.TypePersonal } - ns, err = s.store.NamespaceCreate(ctx, ns) - if err != nil { + if _, err = s.store.NamespaceCreate(ctx, ns); err != nil { return nil, ErrDuplicateNamespace } + if err := s.store.NamespaceCreateMemberships(ctx, ns.TenantID, ns.Members...); err != nil { + return nil, err + } + return ns, nil } @@ -69,17 +73,17 @@ func (s *service) NamespaceAddMember(ctx context.Context, input *inputs.MemberAd return nil, ErrInvalidFormat } - user, err := s.store.UserGetByUsername(ctx, input.Username) + user, err := s.store.UserGet(ctx, store.UserIdentUsername, input.Username) if err != nil { return nil, ErrUserNotFound } - ns, err := s.store.NamespaceGetByName(ctx, input.Namespace) + ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentName, input.Namespace) if err != nil { return nil, ErrNamespaceNotFound } - if err = s.store.NamespaceAddMember(ctx, ns.TenantID, &models.Member{ID: user.ID, Role: input.Role}); err != nil { + if err = s.store.NamespaceCreateMemberships(ctx, ns.TenantID, models.Member{ID: user.ID, Role: input.Role}); err != nil { return nil, ErrFailedNamespaceAddMember } @@ -92,19 +96,19 @@ func (s *service) NamespaceRemoveMember(ctx context.Context, input *inputs.Membe return nil, ErrInvalidFormat } - user, err := s.store.UserGetByUsername(ctx, input.Username) - if err != nil { - return nil, ErrUserNotFound - } + // user, err := s.store.UserGet(ctx, store.UserIdentUsername, input.Username) + // if err != nil { + // return nil, ErrUserNotFound + // } - ns, err := s.store.NamespaceGetByName(ctx, input.Namespace) + ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentName, input.Namespace) if err != nil { return nil, ErrNamespaceNotFound } - if err = s.store.NamespaceRemoveMember(ctx, ns.TenantID, user.ID); err != nil { - return nil, ErrFailedNamespaceRemoveMember - } + // if err = s.store.NamespaceRemoveMember(ctx, ns.TenantID, user.ID); err != nil { + // return nil, ErrFailedNamespaceRemoveMember + // } return ns, nil } @@ -115,12 +119,12 @@ func (s *service) NamespaceDelete(ctx context.Context, input *inputs.NamespaceDe return ErrNamespaceInvalid } - ns, err := s.store.NamespaceGetByName(ctx, input.Namespace) + ns, err := s.store.NamespaceGet(ctx, store.NamespaceIdentName, input.Namespace) if err != nil { return ErrNamespaceNotFound } - if err := s.store.NamespaceDelete(ctx, ns.TenantID); err != nil { + if err := s.store.NamespaceDelete(ctx, ns); err != nil { return ErrFailedDeleteNamespace } diff --git a/cli/services/users.go b/cli/services/users.go index 36908aa6afa..fa70537de00 100644 --- a/cli/services/users.go +++ b/cli/services/users.go @@ -4,27 +4,17 @@ import ( "context" "slices" + "github.com/shellhub-io/shellhub/api/store" "github.com/shellhub-io/shellhub/cli/pkg/inputs" "github.com/shellhub-io/shellhub/pkg/clock" + "github.com/shellhub-io/shellhub/pkg/hash" "github.com/shellhub-io/shellhub/pkg/models" ) // UserCreate adds a new user based on the provided user's data. This method validates data and // checks for conflicts. func (s *service) UserCreate(ctx context.Context, input *inputs.UserCreate) (*models.User, error) { - // TODO: convert username and email to lower case. - userData := models.UserData{ - Name: input.Username, - Email: input.Email, - Username: input.Username, - } - - // TODO: validate this at cmd layer - if ok, err := s.validator.Struct(userData); !ok || err != nil { - return nil, ErrUserDataInvalid - } - - if conflicts, has, _ := s.store.UserConflicts(ctx, &models.UserConflicts{Email: userData.Email, Username: userData.Username}); has { + if conflicts, has, _ := s.store.UserConflicts(ctx, &models.UserConflicts{Email: input.Email, Username: input.Username}); has { containsEmail := slices.Contains(conflicts, "email") containsUsername := slices.Contains(conflicts, "username") @@ -40,23 +30,20 @@ func (s *service) UserCreate(ctx context.Context, input *inputs.UserCreate) (*mo } } - password, err := models.HashUserPassword(input.Password) + pwdDigest, err := hash.Do(input.Password) if err != nil { return nil, ErrUserPasswordInvalid } - // TODO: validate this at cmd layer - if ok, err := s.validator.Struct(password); !ok || err != nil { - return nil, ErrUserPasswordInvalid - } - user := &models.User{ - Origin: models.UserOriginLocal, - UserData: userData, - Password: password, - Status: models.UserStatusConfirmed, - CreatedAt: clock.Now(), - MaxNamespaces: MaxNumberNamespacesCommunity, + Origin: models.UserOriginLocal, + Name: input.Username, + Email: input.Email, + Username: input.Username, + PasswordDigest: pwdDigest, + Status: models.UserStatusConfirmed, + CreatedAt: clock.Now(), + MaxNamespaces: MaxNumberNamespacesCommunity, Preferences: models.UserPreferences{ AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, @@ -77,7 +64,7 @@ func (s *service) UserDelete(ctx context.Context, input *inputs.UserDelete) erro return ErrUserDataInvalid } - user, err := s.store.UserGetByUsername(ctx, input.Username) + user, err := s.store.UserGet(ctx, store.UserIdentUsername, input.Username) if err != nil { return ErrUserNotFound } @@ -88,18 +75,18 @@ func (s *service) UserDelete(ctx context.Context, input *inputs.UserDelete) erro } for _, ns := range userInfo.OwnedNamespaces { - if err := s.store.NamespaceDelete(ctx, ns.TenantID); err != nil { + if err := s.store.NamespaceDelete(ctx, &ns); err != nil { return err } } - for _, ns := range userInfo.AssociatedNamespaces { - if err := s.store.NamespaceRemoveMember(ctx, ns.TenantID, user.ID); err != nil { - return err - } - } + // for _, ns := range userInfo.AssociatedNamespaces { + // if err := s.store.NamespaceRemoveMember(ctx, ns.TenantID, user.ID); err != nil { + // return err + // } + // } - if err := s.store.UserDelete(ctx, user.ID); err != nil { + if err := s.store.UserDelete(ctx, user); err != nil { return ErrFailedDeleteUser } @@ -112,22 +99,21 @@ func (s *service) UserUpdate(ctx context.Context, input *inputs.UserUpdate) erro return ErrUserDataInvalid } - user, err := s.store.UserGetByUsername(ctx, input.Username) + user, err := s.store.UserGet(ctx, store.UserIdentUsername, input.Username) if err != nil { return ErrUserNotFound } - password, err := models.HashUserPassword(input.Password) - if err != nil { - return ErrUserPasswordInvalid - } + if input.Password != "" { + pwdDigest, err := hash.Do(input.Password) + if err != nil { + return ErrUserPasswordInvalid + } - // TODO: validate this at cmd layer - if ok, err := s.validator.Struct(password); !ok || err != nil { - return ErrUserPasswordInvalid + user.PasswordDigest = pwdDigest } - if err := s.store.UserUpdate(ctx, user.ID, &models.UserChanges{Password: password.Hash}); err != nil { + if err := s.store.UserSave(ctx, user); err != nil { return ErrFailedUpdateUser } diff --git a/docker-compose.yml b/docker-compose.yml index 62016f32cf4..c7f01274f2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,8 +71,6 @@ services: test: "curl -f http://api:8080/api/healthcheck || exit 1" interval: 30s start_period: 10s - volumes: - - ./api/store/pg/migrations:/migrations ui: image: shellhubio/ui:${SHELLHUB_VERSION} restart: unless-stopped diff --git a/pkg/api/requests/namespace.go b/pkg/api/requests/namespace.go index 75ca6dfc002..f67856fce50 100644 --- a/pkg/api/requests/namespace.go +++ b/pkg/api/requests/namespace.go @@ -22,8 +22,10 @@ type MemberParam struct { // NamespaceCreate is the structure to represent the request data for create namespace endpoint. type NamespaceList struct { + UserID string `header:"X-ID" validate:"required"` query.Paginator query.Filters + query.Sorter } // NamespaceCreate is the structure to represent the request data for create namespace endpoint. @@ -46,7 +48,7 @@ type NamespaceDelete struct { // NamespaceEdit is the structure to represent the request data for edit namespace endpoint. type NamespaceEdit struct { - TenantParam + TenantID string `param:"tenant" validate:"required,uuid"` Name string `json:"name" validate:"omitempty,hostname_rfc1123,excludes=."` Settings struct { SessionRecord *bool `json:"session_record" validate:"omitempty"` diff --git a/pkg/api/requests/publickey.go b/pkg/api/requests/publickey.go index e6c83748c52..4ffa0f099fa 100644 --- a/pkg/api/requests/publickey.go +++ b/pkg/api/requests/publickey.go @@ -1,5 +1,7 @@ package requests +import "github.com/shellhub-io/shellhub/pkg/api/query" + // FingerprintParam is a structure to represent and validate a public key fingerprint as path param. type FingerprintParam struct { Fingerprint string `param:"fingerprint" validate:"required"` @@ -30,6 +32,12 @@ type PublicKeyCreate struct { Fingerprint string `json:"-"` } +type PublicKeyList struct { + TenantID string `header:"X-Tenant-ID"` + query.Paginator + query.Sorter +} + // PublicKeyUpdate is the structure to represent the request data for update public key endpoint. type PublicKeyUpdate struct { FingerprintParam diff --git a/pkg/models/device.go b/pkg/models/device.go index 7ad3bc64ebe..8b5c272197b 100644 --- a/pkg/models/device.go +++ b/pkg/models/device.go @@ -24,6 +24,9 @@ type Device struct { PublicKey string `json:"public_key" bson:"public_key"` TenantID string `json:"tenant_id" bson:"tenant_id"` + CreatedAt time.Time `json:"created_at" bson:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + // LastSeen represents the timestamp of the most recent ping from the device to the server. LastSeen time.Time `json:"last_seen" bson:"last_seen"` // DisconnectedAt stores the timestamp when the device disconnected from the server. @@ -41,7 +44,6 @@ type Device struct { Namespace string `json:"namespace" bson:",omitempty"` Status DeviceStatus `json:"status" bson:"status,omitempty" validate:"oneof=accepted rejected pending unused"` StatusUpdatedAt time.Time `json:"status_updated_at" bson:"status_updated_at,omitempty"` - CreatedAt time.Time `json:"created_at" bson:"created_at,omitempty"` RemoteAddr string `json:"remote_addr" bson:"remote_addr"` Position *DevicePosition `json:"position" bson:"position"` Tags []string `json:"tags" bson:"tags,omitempty"` diff --git a/pkg/models/member.go b/pkg/models/member.go index f6b713a1379..394fd690e85 100644 --- a/pkg/models/member.go +++ b/pkg/models/member.go @@ -14,8 +14,9 @@ const ( ) type Member struct { - ID string `json:"id,omitempty" bson:"id,omitempty"` - AddedAt time.Time `json:"added_at" bson:"added_at"` + ID string `json:"id,omitempty" bson:"id,omitempty"` + AddedAt time.Time `json:"added_at" bson:"added_at"` + UpdatedAt time.Time `json:"-" bson:"u.()pdated_at"` // ExpiresAt specifies the expiration date of the invite. This attribute is only applicable in *Cloud* instances, // and it is ignored for members whose status is not 'pending'. diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index eb6e531dd91..4b072fa3c0a 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -2,6 +2,29 @@ package models import "time" +// default Announcement Message for the shellhub namespace +const DefaultAnnouncementMessage = ` +****************************************************************** +* * +* Welcome to ShellHub Community Edition! * +* * +* ShellHub is a next-generation SSH server, providing a * +* seamless, secure, and user-friendly solution for remote * +* access management. With ShellHub, you can manage all your * +* devices effortlessly from a single platform, ensuring optimal * +* security and productivity. * +* * +* Want to learn more about ShellHub and explore other editions? * +* Visit: https://shellhub.io * +* * +* Join our community and contribute to our open-source project: * +* https://github.com/shellhub-io/shellhub * +* * +* For assistance, please contact the system administrator. * +* * +****************************************************************** +` + type Namespace struct { Name string `json:"name" validate:"required,hostname_rfc1123,excludes=.,lowercase"` Owner string `json:"owner"` @@ -13,6 +36,7 @@ type Namespace struct { MaxDevices int `json:"max_devices" bson:"max_devices"` DevicesCount int `json:"devices_count" bson:"devices_count,omitempty"` CreatedAt time.Time `json:"created_at" bson:"created_at"` + UpdatedAt time.Time `json:"-" bson:"updated_at"` Billing *Billing `json:"billing" bson:"billing,omitempty"` Type Type `json:"type" bson:"type"` } @@ -53,31 +77,15 @@ type NamespaceSettings struct { ConnectionAnnouncement string `json:"connection_announcement" bson:"connection_announcement"` } -type NamespaceChanges struct { - Name string `bson:"name,omitempty"` - SessionRecord *bool `bson:"settings.session_record,omitempty"` - ConnectionAnnouncement *string `bson:"settings.connection_announcement,omitempty"` +// NamespaceConflicts holds user attributes that must be unique for each itam and can be utilized in queries +// to identify conflicts. +type NamespaceConflicts struct { + Name string } -// default Announcement Message for the shellhub namespace -const DefaultAnnouncementMessage = ` -****************************************************************** -* * -* Welcome to ShellHub Community Edition! * -* * -* ShellHub is a next-generation SSH server, providing a * -* seamless, secure, and user-friendly solution for remote * -* access management. With ShellHub, you can manage all your * -* devices effortlessly from a single platform, ensuring optimal * -* security and productivity. * -* * -* Want to learn more about ShellHub and explore other editions? * -* Visit: https://shellhub.io * -* * -* Join our community and contribute to our open-source project: * -* https://github.com/shellhub-io/shellhub * -* * -* For assistance, please contact the system administrator. * -* * -****************************************************************** -` +// Distinct removes the c attributes whether it's equal to the namespace's attributes. +func (c *NamespaceConflicts) Distinct(namespace *Namespace) { + if c.Name == namespace.Name { + c.Name = "" + } +} diff --git a/pkg/models/privatekey.go b/pkg/models/privatekey.go index 32ac0dff1ad..74546c971a1 100644 --- a/pkg/models/privatekey.go +++ b/pkg/models/privatekey.go @@ -6,4 +6,5 @@ type PrivateKey struct { Data []byte `json:"data"` Fingerprint string `json:"fingerprint"` CreatedAt time.Time `json:"created_at" bson:"created_at"` + UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } diff --git a/pkg/models/publickey.go b/pkg/models/publickey.go index 20f6536959b..a7cb1d87151 100644 --- a/pkg/models/publickey.go +++ b/pkg/models/publickey.go @@ -34,9 +34,11 @@ func (p *PublicKeyFields) Validate() error { } type PublicKey struct { + ID string `json:"-"` Data []byte `json:"data"` Fingerprint string `json:"fingerprint"` CreatedAt time.Time `json:"created_at" bson:"created_at"` + UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` TenantID string `json:"tenant_id" bson:"tenant_id"` PublicKeyFields `bson:",inline"` } diff --git a/pkg/models/user.go b/pkg/models/user.go index c313c20d590..93829fcee0e 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -3,7 +3,6 @@ package models import ( "time" - "github.com/shellhub-io/shellhub/pkg/hash" "github.com/shellhub-io/shellhub/pkg/validator" ) @@ -69,27 +68,23 @@ type User struct { // MaxNamespaces represents the count of namespaces that the user can owns. MaxNamespaces int `json:"max_namespaces" bson:"max_namespaces"` CreatedAt time.Time `json:"created_at" bson:"created_at"` + UpdatedAt time.Time `json:"updated_at"` LastLogin time.Time `json:"last_login" bson:"last_login"` EmailMarketing bool `json:"email_marketing" bson:"email_marketing"` - UserData `bson:",inline"` + + Name string `json:"name" validate:"required,name"` + Username string `json:"username" bson:"username" validate:"required,username"` + Email string `json:"email" bson:"email" validate:"required,email"` + + // PasswordDigest stores the hashed password. + PasswordDigest string `json:"-"` + // MFA contains attributes related to a user's MFA settings. Use [UserMFA.Enabled] to // check if MFA is active for the user. // // NOTE: MFA is available as a cloud-only feature and must be ignored in community. MFA UserMFA `json:"mfa" bson:"mfa"` Preferences UserPreferences `json:"preferences" bson:"preferences"` - Password UserPassword `bson:",inline"` -} - -type UserData struct { - Name string `json:"name" validate:"required,name"` - Username string `json:"username" bson:"username" validate:"required,username"` - Email string `json:"email" bson:"email" validate:"required,email"` - // RecoveryEmail is a custom, non-unique email address that a user can use to recover their account - // when they lose access to all other methods. It must never be equal to [UserData.Email]. - // - // NOTE: Recovery email is available as a cloud-only feature and must be ignored in community. - RecoveryEmail string `json:"recovery_email" bson:"recovery_email" validate:"omitempty,email"` } // UserMFA represents the attributes related to MFA for a user. @@ -108,35 +103,12 @@ type UserPreferences struct { // AuthMethods indicates the authentication methods that the user can use to authenticate. AuthMethods []UserAuthMethod `json:"auth_methods" bson:"auth_methods"` -} - -type UserPassword struct { - // Plain contains the plain text password. - Plain string `json:"password" bson:"-" validate:"required,password"` - // Hash contains the hashed pasword from plain text. - Hash string `json:"-" bson:"password"` -} - -// HashUserPassword receives a plain password and hash it, returning -// a [UserPassword]. -func HashUserPassword(plain string) (UserPassword, error) { - p := UserPassword{ - Plain: plain, - } - - var err error - p.Hash, err = hash.Do(p.Plain) - - return p, err -} -// Compare reports whether a plain password matches with hash. -// -// For compatibility purposes, it can compare using both SHA256 and bcrypt algorithms. -// Hashes starting with "$" are assumed to be a bcrypt hash; otherwise, they are treated as -// SHA256 hashes. -func (p *UserPassword) Compare(plain string) bool { - return hash.CompareWith(plain, p.Hash) + // RecoveryEmail is a custom, non-unique email address that a user can use to recover their account + // when they lose access to all other methods. It must never be equal to [UserData.Email]. + // + // NOTE: Recovery email is available as a cloud-only feature and must be ignored in community. + RecoveryEmail string `json:"recovery_email" bson:"recovery_email" validate:"omitempty,email"` } // UserAuthIdentifier is an username or email used to authenticate. @@ -176,23 +148,6 @@ type UserTokenRecover struct { CreatedAt time.Time `json:"created_at" bson:"created_at"` } -// UserChanges specifies the attributes that can be updated for a user. Any zero values in this -// struct must be ignored. If an attribute is a pointer type, its zero value is represented as `nil`. -type UserChanges struct { - LastLogin time.Time `bson:"last_login,omitempty"` - Name string `bson:"name,omitempty"` - Username string `bson:"username,omitempty"` - Email string `bson:"email,omitempty"` - RecoveryEmail string `bson:"recovery_email,omitempty"` - Password string `bson:"password,omitempty"` - Status UserStatus `bson:"status,omitempty"` - ExternalID *string `bson:"external_id,omitempty"` - PreferredNamespace *string `bson:"preferences.preferred_namespace,omitempty"` - MaxNamespaces *int `bson:"max_namespaces,omitempty"` - EmailMarketing *bool `bson:"email_marketing,omitempty"` - AuthMethods []UserAuthMethod `bson:"preferences.auth_methods,omitempty"` -} - // UserConflicts holds user attributes that must be unique for each itam and can be utilized in queries // to identify conflicts. type UserConflicts struct { diff --git a/pkg/uuid/uuid.go b/pkg/uuid/uuid.go index 817a2128169..85bb9d8845f 100644 --- a/pkg/uuid/uuid.go +++ b/pkg/uuid/uuid.go @@ -26,5 +26,7 @@ type goUUID struct{} // This function is responsible for generating UUID v4 of the google package. func (g *goUUID) Generate() string { - return uuid.NewString() + uuid, _ := uuid.NewV7() + + return uuid.String() } diff --git a/ssh/session/session.go b/ssh/session/session.go index 01e8c405664..da557a13fe1 100644 --- a/ssh/session/session.go +++ b/ssh/session/session.go @@ -502,9 +502,9 @@ func (s *Session) Auth(ctx gliderssh.Context, auth Auth) error { return err } - if err := sess.register(); err != nil { - return err - } + // if err := sess.register(); err != nil { + // return err + // } snap.save(sess, StateRegistered) @@ -514,9 +514,9 @@ func (s *Session) Auth(ctx gliderssh.Context, auth Auth) error { return err } - if err := sess.authenticate(); err != nil { - return err - } + // if err := sess.authenticate(); err != nil { + // return err + // } default: // The default arm is intended to avoid [StateNil] and [StateCreated], what are used before the authentication. return errors.New("invalid session state")