From 15d2715a7408e888c1cd1b5f781b06ea49b4f5da Mon Sep 17 00:00:00 2001 From: Kevin Gatera Date: Mon, 2 Mar 2026 18:02:36 -0500 Subject: [PATCH] fix postgres old user id json lookup --- server/models/User.js | 20 ++++++++++--- test/server/models/User.test.js | 50 +++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/server/models/User.js b/server/models/User.js index c7ee78dbd..c260ad08e 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -422,12 +422,24 @@ class User extends Model { const cachedUser = userCache.getById(userId) || userCache.getByOldId(userId) if (cachedUser) return cachedUser - const idMatcher = this.sequelize.getDialect() === 'postgres' ? sequelize.where(sequelize.cast(sequelize.col('user.id'), 'text'), userId) : { id: userId } + const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(userId) + if (isUuid) { + const byId = await this.findByPk(userId, { + include: this.sequelize.models.mediaProgress + }) + if (byId) { + userCache.set(byId) + return byId + } + } + + const oldIdMatcher = + this.sequelize.getDialect() === 'postgres' + ? sequelize.where(sequelize.literal(`"extraData"#>>'{oldUserId}'`), userId) + : { 'extraData.oldUserId': userId } const user = await this.findOne({ - where: { - [sequelize.Op.or]: [idMatcher, { 'extraData.oldUserId': userId }] - }, + where: oldIdMatcher, include: this.sequelize.models.mediaProgress }) diff --git a/test/server/models/User.test.js b/test/server/models/User.test.js index 9e2bf5888..ce187f93e 100644 --- a/test/server/models/User.test.js +++ b/test/server/models/User.test.js @@ -1,7 +1,5 @@ const { expect } = require('chai') const sinon = require('sinon') -const sequelize = require('sequelize') - const User = require('../../../server/models/User') describe('User model', () => { @@ -17,7 +15,7 @@ describe('User model', () => { sinon.restore() }) - it('should cast id to text on postgres when checking legacy token ids', async () => { + it('should resolve UUID ids via primary-key lookup on postgres', async () => { User.sequelize = { getDialect: () => 'postgres', models: { @@ -25,6 +23,46 @@ describe('User model', () => { } } + const user = { id: 'e8e677b2-da16-4220-ab67-443b7714caf9' } + const findByPkStub = sinon.stub(User, 'findByPk').resolves(user) + const findOneStub = sinon.stub(User, 'findOne').resolves(null) + + const result = await User.getUserByIdOrOldId('e8e677b2-da16-4220-ab67-443b7714caf9') + + expect(result).to.equal(user) + expect(findByPkStub.calledOnce).to.equal(true) + expect(findOneStub.called).to.equal(false) + }) + + it('should query legacy oldUserId with postgres-safe JSON matcher', async () => { + User.sequelize = { + getDialect: () => 'postgres', + models: { + mediaProgress: {} + } + } + + const findByPkStub = sinon.stub(User, 'findByPk').resolves(null) + const findOneStub = sinon.stub(User, 'findOne').resolves(null) + + await User.getUserByIdOrOldId('root') + + expect(findByPkStub.called).to.equal(false) + expect(findOneStub.calledOnce).to.equal(true) + + const options = findOneStub.firstCall.args[0] + expect(options.where.attribute.val).to.equal('"extraData"#>>\'{oldUserId}\'') + expect(options.where.logic).to.equal('root') + }) + + it('should keep sqlite oldUserId matcher unchanged', async () => { + User.sequelize = { + getDialect: () => 'sqlite', + models: { + mediaProgress: {} + } + } + const findOneStub = sinon.stub(User, 'findOne').resolves(null) await User.getUserByIdOrOldId('root') @@ -32,11 +70,7 @@ describe('User model', () => { expect(findOneStub.calledOnce).to.equal(true) const options = findOneStub.firstCall.args[0] - const whereOr = options.where[sequelize.Op.or] - - expect(whereOr).to.have.length(2) - expect(whereOr[0].attribute.type).to.equal('text') - expect(whereOr[1]).to.deep.equal({ 'extraData.oldUserId': 'root' }) + expect(options.where).to.deep.equal({ 'extraData.oldUserId': 'root' }) }) }) })