From 059377b684c7fe7d210b9373dbc82a6921e633d0 Mon Sep 17 00:00:00 2001 From: Erik Dubbelboer Date: Fri, 10 May 2024 16:29:47 +0200 Subject: [PATCH] Add support for lobby filtering Using https://github.com/poki/mongodb-filter-to-postgres Co-authored-by: Koen Bollen <213502+koenbollen@users.noreply.github.com> --- cmd/testproxy/main.go | 24 +++++++++++ features/lobbies.feature | 55 +++++++++++++++++++++++--- features/support/steps/backend.ts | 20 +++++++--- features/support/steps/network.ts | 57 ++++++++++++++++++++++++++- features/support/world.ts | 1 + go.mod | 3 +- go.sum | 19 +++++++++ internal/signaling/stores/postgres.go | 47 +++++++++++++++------- internal/signaling/stores/setup.go | 11 +++++- internal/signaling/stores/shared.go | 3 ++ lib/types.ts | 2 + 11 files changed, 212 insertions(+), 30 deletions(-) diff --git a/cmd/testproxy/main.go b/cmd/testproxy/main.go index 9362bb0..5c46d7b 100644 --- a/cmd/testproxy/main.go +++ b/cmd/testproxy/main.go @@ -2,12 +2,15 @@ package main import ( "context" + "io" "net" "net/http" + "os" "os/signal" "syscall" "time" + "github.com/jackc/pgx/v5/pgxpool" "github.com/koenbollen/logging" "github.com/poki/netlib/internal/util" "go.uber.org/zap" @@ -23,6 +26,15 @@ func main() { defer logger.Info("fin") ctx = logging.WithLogger(ctx, logger) + db, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL")) + if err != nil { + logger.Fatal("failed to connect", zap.Error(err)) + } + + if err := db.Ping(ctx); err != nil { + logger.Fatal("failed to ping db", zap.Error(err)) + } + connections := make(map[string]net.Conn) interrupts := make(map[string]bool) http.HandleFunc("/create", func(w http.ResponseWriter, r *http.Request) { @@ -85,6 +97,18 @@ func main() { delete(connections, id) } }) + http.HandleFunc("/sql", func(w http.ResponseWriter, r *http.Request) { + sql, err := io.ReadAll(r.Body) + if err != nil { + panic(err) + } + + // This process is only ran during tests. + _, err = db.Exec(ctx, string(sql)) + if err != nil { + panic(err) + } + }) addr := util.Getenv("ADDR", ":8080") server := &http.Server{ diff --git a/features/lobbies.feature b/features/lobbies.feature index da82fed..f1d13f5 100644 --- a/features/lobbies.feature +++ b/features/lobbies.feature @@ -2,6 +2,7 @@ Feature: Lobby Discovery Background: Given the "signaling" backend is running + And the "testproxy" backend is running Scenario: List empty lobby set Given "green" is connected and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884" @@ -21,7 +22,7 @@ Feature: Lobby Discovery And "blue" is connected and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884" When "blue" creates a lobby with these settings: - """ + """json { "public": true } @@ -29,7 +30,7 @@ Feature: Lobby Discovery And "blue" receives the network event "lobby" with the argument "prb67ouj837u" When "green" requests all lobbies - Then "green" should have received only these lobbies + Then "green" should have received only these lobbies: | code | playerCount | | prb67ouj837u | 1 | @@ -39,14 +40,14 @@ Feature: Lobby Discovery And "yellow" is connected and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884" When "blue" creates a lobby with these settings: - """ + """json { "public": true } """ And "blue" receives the network event "lobby" with the argument "dhgp75mn2bll" And "yellow" creates a lobby with these settings: - """ + """json { "public": false } @@ -54,11 +55,53 @@ Feature: Lobby Discovery And "yellow" receives the network event "lobby" with the argument "1qva9vyurwbbl" When "green" requests all lobbies - Then "green" should have received only these lobbies + Then "green" should have received only these lobbies: | code | playerCount | public | | dhgp75mn2bll | 1 | true | + Scenario: Filter on playerCount + Given "green" is connected and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884" + And these lobbies exist: + | code | game | playerCount | public | + | 0qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 1 | true | + | 1qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 2 | false | + | 2qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 3 | true | + | 3qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 4 | true | + | 4qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 5 | true | + | 5qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 6 | true | + | 6qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 7 | false | + | 7qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 8 | true | + | 8qva9vyurwbbl | 54fa57d5-b4bd-401d-981d-2c13de99be27 | 9 | true | + | 9qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 10 | true | + When "green" requests lobbies with this filter: + """json + { + "playerCount": {"$gte": 5} + } + """ + Then "green" should have received only these lobbies: + | code | playerCount | public | + | 4qva9vyurwbbl | 5 | true | + | 5qva9vyurwbbl | 6 | true | + | 7qva9vyurwbbl | 8 | true | + | 9qva9vyurwbbl | 10 | true | + Scenario: Filter on customData + Given "green" is connected and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884" + And these lobbies exist: + | code | game | playerCount | meta | public | created_at | + | 0qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 1 | {"map": "de_nuke"} | true | 2020-01-01 | + | 1qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 1 | {"map": "de_dust"} | true | 2020-01-02 | + | 2qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 1 | {"map": "de_nuke"} | true | 2020-01-03 | - + When "green" requests lobbies with this filter: + """json + { + "map": "de_nuke", + "createdAt": {"$gte": "2020-01-02"} + } + """ + Then "green" should have received only these lobbies: + | code | + | 2qva9vyurwbbl | diff --git a/features/support/steps/backend.ts b/features/support/steps/backend.ts index cf8671c..3d34b7d 100644 --- a/features/support/steps/backend.ts +++ b/features/support/steps/backend.ts @@ -5,13 +5,19 @@ import { World } from '../world' Given('the {string} backend is running', async function (this: World, backend: string) { return await new Promise(resolve => { const port = 10000 + Math.ceil(Math.random() * 1000) + const env: NodeJS.ProcessEnv = { + ...process.env, + ADDR: `127.0.0.1:${port}`, + ENV: 'test' + } + + if (this.databaseURL === undefined) { + env.DATABASE_URL = this.databaseURL + } + const prc = spawn(`/tmp/netlib-cucumber-${backend}`, [], { windowsHide: true, - env: { - ...process.env, - ADDR: `127.0.0.1:${port}`, - ENV: 'test' - } + env }) prc.stderr.setEncoding('utf8') prc.stderr.on('data', (data: string) => { @@ -19,7 +25,9 @@ Given('the {string} backend is running', async function (this: World, backend: s lines.forEach(line => { try { const entry = JSON.parse(line) - if (entry.message === 'listening') { + if (entry.message === 'using database') { + this.databaseURL = entry.url + } else if (entry.message === 'listening') { resolve(undefined) } } catch (_) { diff --git a/features/support/steps/network.ts b/features/support/steps/network.ts index 57d1500..5222c0c 100644 --- a/features/support/steps/network.ts +++ b/features/support/steps/network.ts @@ -52,6 +52,52 @@ Given('{string} are joined in a lobby', async function (this: World, playerNames } }) +Given('these lobbies exist:', async function (this: World, lobbies: DataTable) { + if (this.testproxyURL === undefined) { + throw new Error('testproxy not active') + } + + const columns: string[] = [] + const values: string[] = [] + + lobbies.hashes().forEach(row => { + const v: string[] = [] + + Object.keys(row).forEach(key => { + const value = row[key] as string + if (key === 'playerCount') { + if (!columns.includes('peers')) { + columns.push('peers') + } + + const n = parseInt(value, 10) + const peers: string[] = [] + + for (let i = 0; i < n; i++) { + peers.push(`'peer${i}'`) + } + + v.push(`ARRAY [${peers.join(', ')}]`) + } else { + if (!columns.includes(key)) { + columns.push(key) + } + + v.push(`'${value}'`) + } + }) + + values.push(`(${v.join(', ')})`) + }) + + console.log('INSERT INTO lobbies (' + columns.join(', ') + ') VALUES ' + values.join(', ')) + + await fetch(`${this.testproxyURL}/sql`, { + method: 'POST', + body: 'INSERT INTO lobbies (' + columns.join(', ') + ') VALUES ' + values.join(', ') + }) +}) + When('{string} creates a network for game {string}', function (this: World, playerName: string, gameID: string) { this.createPlayer(playerName, gameID) }) @@ -106,6 +152,15 @@ When('{string} requests all lobbies', async function (this: World, playerName: s player.lastReceivedLobbies = lobbies }) +When('{string} requests lobbies with this filter:', async function (this: World, playerName: string, filter: string) { + const player = this.players.get(playerName) + if (player == null) { + return 'no such player' + } + const lobbies = await player.network.list(filter) + player.lastReceivedLobbies = lobbies +}) + Then('{string} receives the network event {string}', async function (this: World, playerName: string, eventName: string) { const player = this.players.get(playerName) if (player == null) { @@ -160,7 +215,7 @@ Then('{string} should receive {int} lobbies', function (this: World, playerName: return player.lastReceivedLobbies?.length === expectedLobbyCount }) -Then('{string} should have received only these lobbies', function (this: World, playerName: string, expectedLobbies: DataTable) { +Then('{string} should have received only these lobbies:', function (this: World, playerName: string, expectedLobbies: DataTable) { const player = this.players.get(playerName) if (player == null) { throw new Error('no such player') diff --git a/features/support/world.ts b/features/support/world.ts index a3ed1fc..7e35aa5 100644 --- a/features/support/world.ts +++ b/features/support/world.ts @@ -32,6 +32,7 @@ export class World extends CucumberWorld { public signalingURL?: string public testproxyURL?: string public useTestProxy: boolean = false + public databaseURL?: string public players: Map = new Map() diff --git a/go.mod b/go.mod index 5b10cc2..df24532 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,13 @@ module github.com/poki/netlib -go 1.20 +go 1.22.2 require ( github.com/golang-migrate/migrate/v4 v4.16.2 github.com/jackc/pgx/v5 v5.4.2 github.com/koenbollen/logging v0.0.0-20230520102501-e01d64214504 github.com/ory/dockertest/v3 v3.10.0 + github.com/poki/mongodb-filter-to-postgres v0.0.0-20240503105833-0b1842cffb65 github.com/rs/cors v1.9.0 github.com/rs/xid v1.5.0 go.uber.org/zap v1.24.0 diff --git a/go.sum b/go.sum index c75eb06..f628880 100644 --- a/go.sum +++ b/go.sum @@ -5,19 +5,23 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/continuity v0.4.1 h1:wQnVrjIyQ8vhU2sgOiL5T07jo+ouqc2bnKsv5/EqGhU= github.com/containerd/continuity v0.4.1/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 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/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= +github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0= github.com/docker/cli v24.0.4+incompatible h1:Y3bYF9ekNTm2VFz5U/0BlMdJy73D+Y1iAAZ8l63Ydzw= github.com/docker/cli v24.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -38,6 +42,7 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -51,8 +56,10 @@ github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -85,13 +92,16 @@ github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/koenbollen/logging v0.0.0-20230520102501-e01d64214504 h1:4XwVIPnDZkE3EMNd5DAMedHVH+t7Ge9Lig50+EzwsD4= github.com/koenbollen/logging v0.0.0-20230520102501-e01d64214504/go.mod h1:XqaLEwx7CTcTVg3M8J4ZrWJ3W5oBUCnVcOteDzTSzVI= 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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -101,6 +111,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 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.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -113,6 +124,8 @@ 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/poki/mongodb-filter-to-postgres v0.0.0-20240503105833-0b1842cffb65 h1:3NaeAVswO0NlXp/kbQRg1WRQog3ma5CuktKnn214ekI= +github.com/poki/mongodb-filter-to-postgres v0.0.0-20240503105833-0b1842cffb65/go.mod h1:euoY8HWNMnOQRo+yKhMPKwMUKW6Z6sNmmsmSJzhURTk= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= @@ -126,6 +139,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= @@ -142,6 +156,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= @@ -160,6 +175,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -190,8 +206,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T 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/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -200,5 +218,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/internal/signaling/stores/postgres.go b/internal/signaling/stores/postgres.go index 2113402..1387089 100644 --- a/internal/signaling/stores/postgres.go +++ b/internal/signaling/stores/postgres.go @@ -12,27 +12,28 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/koenbollen/logging" + "github.com/poki/mongodb-filter-to-postgres/filter" "github.com/poki/netlib/internal/util" "go.uber.org/zap" ) -type notificationPayload struct { - Topic string `json:"t"` - Data []byte `json:"d"` -} - type PostgresStore struct { DB *pgxpool.Pool mutex sync.Mutex callbacks map[string]map[uint64]SubscriptionCallback nextCallbackIndex uint64 + filterConverter *filter.Converter } func NewPostgresStore(ctx context.Context, db *pgxpool.Pool) (*PostgresStore, error) { s := &PostgresStore{ DB: db, callbacks: make(map[string]map[uint64]SubscriptionCallback), + filterConverter: filter.NewConverter( + filter.WithNestedJSONB("meta", "code", "playerCount", "createdAt", "updatedAt"), + filter.WithEmptyCondition("TRUE"), // No filter returns all lobbies. + ), } go s.run(ctx) return s, nil @@ -274,18 +275,38 @@ func (s *PostgresStore) GetLobby(ctx context.Context, game, lobbyCode string) ([ } func (s *PostgresStore) ListLobbies(ctx context.Context, game, filter string) ([]Lobby, error) { + // TODO: Remove this. + if filter == "" { + filter = "{}" + } - // TODO: Filters + where, values, err := s.filterConverter.Convert([]byte(filter), 2) + if err != nil { + logger := logging.GetLogger(ctx) + logger.Warn("failed to convert filter", zap.String("filter", filter), zap.Error(err)) + return nil, fmt.Errorf("invalid filter: %w", err) + } var lobbies []Lobby rows, err := s.DB.Query(ctx, ` - SELECT code, peers, public, meta + WITH lobbies AS ( + SELECT + code, + ARRAY_LENGTH(peers, 1) AS "playerCount", + public, + meta, + created_at AS "createdAt", + updated_at AS "updatedAt" + FROM lobbies + WHERE game = $1 + AND public = true + ) + SELECT * FROM lobbies - WHERE game = $1 - AND public = true - ORDER BY created_at DESC + WHERE `+where+` + ORDER BY "createdAt" DESC LIMIT 50 - `, game) + `, append([]any{game}, values...)...) if err != nil { return nil, err } @@ -293,12 +314,10 @@ func (s *PostgresStore) ListLobbies(ctx context.Context, game, filter string) ([ for rows.Next() { var lobby Lobby - var peers []string - err = rows.Scan(&lobby.Code, &peers, &lobby.Public, &lobby.CustomData) + err = rows.Scan(&lobby.Code, &lobby.PlayerCount, &lobby.Public, &lobby.CustomData, &lobby.CreatedAt, &lobby.UpdatedAt) if err != nil { return nil, err } - lobby.PlayerCount = len(peers) lobbies = append(lobbies, lobby) } if err = rows.Err(); err != nil { diff --git a/internal/signaling/stores/setup.go b/internal/signaling/stores/setup.go index 12ef96d..d7498a0 100644 --- a/internal/signaling/stores/setup.go +++ b/internal/signaling/stores/setup.go @@ -7,12 +7,16 @@ import ( "time" "github.com/jackc/pgx/v5/pgxpool" + "github.com/koenbollen/logging" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/poki/netlib/migrations" + "go.uber.org/zap" ) func FromEnv(ctx context.Context) (Store, chan struct{}, error) { + logger := logging.GetLogger(ctx) + if url, ok := os.LookupEnv("DATABASE_URL"); ok { db, err := pgxpool.New(ctx, url) if err != nil { @@ -53,7 +57,7 @@ func FromEnv(ctx context.Context) (Store, chan struct{}, error) { flushed := make(chan struct{}) go func() { <-ctx.Done() - pool.Purge(resource) + pool.Purge(resource) // nolint:errcheck close(flushed) }() if err := resource.Expire(120); err != nil { @@ -61,6 +65,9 @@ func FromEnv(ctx context.Context) (Store, chan struct{}, error) { } databaseUrl := fmt.Sprintf("postgres://test:test@%s/test?sslmode=disable", resource.GetHostPort("5432/tcp")) + // This log message is used by the test suite to pass the database URL to the testproxy. + logger.Info("using database", zap.String("url", databaseUrl)) + var db *pgxpool.Pool pool.MaxWait = 120 * time.Second if err = pool.Retry(func() error { @@ -85,5 +92,5 @@ func FromEnv(ctx context.Context) (Store, chan struct{}, error) { } return store, flushed, nil } - return nil, nil, fmt.Errorf("no database configured expost DATABASE_URL or DOCKER_HOST to run locally") + return nil, nil, fmt.Errorf("no database configured export DATABASE_URL or DOCKER_HOST to run locally") } diff --git a/internal/signaling/stores/shared.go b/internal/signaling/stores/shared.go index 517390e..f1a32da 100644 --- a/internal/signaling/stores/shared.go +++ b/internal/signaling/stores/shared.go @@ -42,6 +42,9 @@ type Lobby struct { Password string `json:"password"` CustomData map[string]any `json:"customData"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + peers map[string]struct{} } diff --git a/lib/types.ts b/lib/types.ts index 4f51466..647268e 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -18,6 +18,8 @@ export interface LobbySettings { export interface LobbyListEntry extends LobbySettings{ code: string playerCount: number + createdAt: string + updatedAt: string } interface Base {