From 7fb2acc500dd7bf54d3d338d7c314a78517abb4e Mon Sep 17 00:00:00 2001 From: Peter BALIVET Date: Mon, 23 Jun 2025 10:38:43 +0200 Subject: [PATCH 1/4] Add IP Address to listening session to provide a more accurate history of the network location for each playback. --- .../modals/ListeningSessionModal.vue | 4 +- server/managers/PlaybackSessionManager.js | 2 + ...2.20.1-add-ipaddress-to-playbacksession.js | 67 +++++++++++++++++++ server/models/PlaybackSession.js | 5 ++ server/objects/PlaybackSession.js | 5 ++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 server/migrations/v2.20.1-add-ipaddress-to-playbacksession.js diff --git a/client/components/modals/ListeningSessionModal.vue b/client/components/modals/ListeningSessionModal.vue index a2469836..7194b985 100644 --- a/client/components/modals/ListeningSessionModal.vue +++ b/client/components/modals/ListeningSessionModal.vue @@ -89,7 +89,7 @@

{{ $strings.LabelDevice }}

{{ clientDisplayName }}

-

{{ deviceInfo.ipAddress }}

+

{{ _session.ipAddress }}

{{ osDisplayName }}

{{ deviceInfo.browserName }}

{{ deviceDisplayName }}

@@ -136,7 +136,7 @@ export default { return this._session.deviceInfo || {} }, hasDeviceInfo() { - return Object.keys(this.deviceInfo).length + return Object.keys(this.deviceInfo).length || this._session.ipAddress }, osDisplayName() { if (!this.deviceInfo.osName) return null diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index fb12feda..58026ce2 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -62,6 +62,7 @@ class PlaybackSessionManager { if (existingDevice.update(deviceInfo)) { await Database.deviceModel.updateFromOld(existingDevice) } + existingDevice.ipAddress = ip return existingDevice } } @@ -181,6 +182,7 @@ class PlaybackSessionManager { // New session from local session = new PlaybackSession(sessionJson) session.deviceInfo = deviceInfo + session.ipAddress = deviceInfo.ipAddress if (session.mediaMetadata == null) { session.mediaMetadata = {} diff --git a/server/migrations/v2.20.1-add-ipaddress-to-playbacksession.js b/server/migrations/v2.20.1-add-ipaddress-to-playbacksession.js new file mode 100644 index 00000000..cdf7dc51 --- /dev/null +++ b/server/migrations/v2.20.1-add-ipaddress-to-playbacksession.js @@ -0,0 +1,67 @@ +/** + * @typedef MigrationContext + * @property {import('sequelize').QueryInterface} queryInterface - a Sequelize QueryInterface object. + * @property {import('../Logger')} logger - a Logger object. + * + * @typedef MigrationOptions + * @property {MigrationContext} context - an object containing the migration context. + */ + +const migrationVersion = '2.20.1' +const migrationName = `${migrationVersion}-add-ipaddress-to-playbacksession` +const loggerPrefix = `[${migrationVersion} migration]` + +/** + * This migration script adds the ipAddress column to the playbackSessions table. + * + * @param {MigrationOptions} options - an object containing the migration context. + * @returns {Promise} - A promise that resolves when the migration is complete. + */ +async function up({ context: { queryInterface, logger } }) { + logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`) + + if (await queryInterface.tableExists('playbackSessions')) { + const tableDescription = await queryInterface.describeTable('playbackSessions') + if (!tableDescription.ipAddress) { + logger.info(`${loggerPrefix} Adding ipAddress column to playbackSessions table`) + await queryInterface.addColumn('playbackSessions', 'ipAddress', { + type: queryInterface.sequelize.Sequelize.DataTypes.STRING, + allowNull: true + }) + logger.info(`${loggerPrefix} Added ipAddress column to playbackSessions table`) + } else { + logger.info(`${loggerPrefix} ipAddress column already exists in playbackSessions table`) + } + } else { + logger.info(`${loggerPrefix} playbackSessions table does not exist`) + } + + logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`) +} + +/** + * This migration script removes the ipAddress column from the playbackSessions table. + * + * @param {MigrationOptions} options - an object containing the migration context. + * @returns {Promise} - A promise that resolves when the migration is complete. + */ +async function down({ context: { queryInterface, logger } }) { + logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`) + + if (await queryInterface.tableExists('playbackSessions')) { + const tableDescription = await queryInterface.describeTable('playbackSessions') + if (tableDescription.ipAddress) { + logger.info(`${loggerPrefix} Removing ipAddress column from playbackSessions table`) + await queryInterface.removeColumn('playbackSessions', 'ipAddress') + logger.info(`${loggerPrefix} Removed ipAddress column from playbackSessions table`) + } else { + logger.info(`${loggerPrefix} ipAddress column does not exist in playbackSessions table`) + } + } else { + logger.info(`${loggerPrefix} playbackSessions table does not exist`) + } + + logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`) +} + +module.exports = { up, down } \ No newline at end of file diff --git a/server/models/PlaybackSession.js b/server/models/PlaybackSession.js index 196fbda6..b1ead0f9 100644 --- a/server/models/PlaybackSession.js +++ b/server/models/PlaybackSession.js @@ -16,6 +16,8 @@ class PlaybackSession extends Model { this.displayTitle /** @type {string} */ this.displayAuthor + /** @type {string} */ + this.ipAddress /** @type {number} */ this.duration /** @type {number} */ @@ -93,6 +95,7 @@ class PlaybackSession extends Model { displayAuthor: playbackSessionExpanded.displayAuthor, coverPath: playbackSessionExpanded.coverPath, duration: playbackSessionExpanded.duration, + ipAddress: playbackSessionExpanded.ipAddress, playMethod: playbackSessionExpanded.playMethod, mediaPlayer: playbackSessionExpanded.mediaPlayer, deviceInfo: playbackSessionExpanded.device?.getOldDevice() || null, @@ -141,6 +144,7 @@ class PlaybackSession extends Model { displayTitle: oldPlaybackSession.displayTitle, displayAuthor: oldPlaybackSession.displayAuthor, duration: oldPlaybackSession.duration, + ipAddress: oldPlaybackSession.ipAddress, playMethod: oldPlaybackSession.playMethod, mediaPlayer: oldPlaybackSession.mediaPlayer, startTime: oldPlaybackSession.startTime, @@ -184,6 +188,7 @@ class PlaybackSession extends Model { displayTitle: DataTypes.STRING, displayAuthor: DataTypes.STRING, duration: DataTypes.FLOAT, + ipAddress: DataTypes.STRING, playMethod: DataTypes.INTEGER, mediaPlayer: DataTypes.STRING, startTime: DataTypes.FLOAT, diff --git a/server/objects/PlaybackSession.js b/server/objects/PlaybackSession.js index ba031b66..8e92e6a6 100644 --- a/server/objects/PlaybackSession.js +++ b/server/objects/PlaybackSession.js @@ -23,6 +23,7 @@ class PlaybackSession { this.playMethod = null this.mediaPlayer = null this.deviceInfo = null + this.ipAddress = null this.serverVersion = null this.date = null @@ -67,6 +68,7 @@ class PlaybackSession { playMethod: this.playMethod, mediaPlayer: this.mediaPlayer, deviceInfo: this.deviceInfo?.toJSON() || null, + ipAddress: this.ipAddress, serverVersion: this.serverVersion, date: this.date, dayOfWeek: this.dayOfWeek, @@ -101,6 +103,7 @@ class PlaybackSession { playMethod: this.playMethod, mediaPlayer: this.mediaPlayer, deviceInfo: this.deviceInfo?.toJSON() || null, + ipAddress: this.ipAddress, serverVersion: this.serverVersion, date: this.date, dayOfWeek: this.dayOfWeek, @@ -125,6 +128,7 @@ class PlaybackSession { this.duration = session.duration this.playMethod = session.playMethod this.mediaPlayer = session.mediaPlayer || null + this.ipAddress = session.ipAddress || null // Temp do not store old IDs if (this.libraryId?.startsWith('lib_')) { @@ -222,6 +226,7 @@ class PlaybackSession { this.mediaPlayer = mediaPlayer this.deviceInfo = deviceInfo || new DeviceInfo() + this.ipAddress = this.deviceInfo.ipAddress this.serverVersion = serverVersion this.timeListening = 0 From a5f33a5502626735f9289c8fb9d33e52304293f2 Mon Sep 17 00:00:00 2001 From: Peter BALIVET Date: Mon, 23 Jun 2025 11:34:04 +0200 Subject: [PATCH 2/4] Fix on print of LabelDevice + redundant code changes --- client/components/modals/ListeningSessionModal.vue | 2 +- server/managers/PlaybackSessionManager.js | 1 - server/objects/PlaybackSession.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/client/components/modals/ListeningSessionModal.vue b/client/components/modals/ListeningSessionModal.vue index 7194b985..39e07c39 100644 --- a/client/components/modals/ListeningSessionModal.vue +++ b/client/components/modals/ListeningSessionModal.vue @@ -136,7 +136,7 @@ export default { return this._session.deviceInfo || {} }, hasDeviceInfo() { - return Object.keys(this.deviceInfo).length || this._session.ipAddress + return Object.keys(this.deviceInfo).length }, osDisplayName() { if (!this.deviceInfo.osName) return null diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index 58026ce2..6fedfa71 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -62,7 +62,6 @@ class PlaybackSessionManager { if (existingDevice.update(deviceInfo)) { await Database.deviceModel.updateFromOld(existingDevice) } - existingDevice.ipAddress = ip return existingDevice } } diff --git a/server/objects/PlaybackSession.js b/server/objects/PlaybackSession.js index 8e92e6a6..8c5820d3 100644 --- a/server/objects/PlaybackSession.js +++ b/server/objects/PlaybackSession.js @@ -226,7 +226,7 @@ class PlaybackSession { this.mediaPlayer = mediaPlayer this.deviceInfo = deviceInfo || new DeviceInfo() - this.ipAddress = this.deviceInfo.ipAddress + this.ipAddress = deviceInfo?.ipAddress || null this.serverVersion = serverVersion this.timeListening = 0 From a7e01ad6eec972e2f016b50a6aad709451dce019 Mon Sep 17 00:00:00 2001 From: Peter BALIVET Date: Mon, 23 Jun 2025 13:59:32 +0200 Subject: [PATCH 3/4] Fix bad migration version --- ...cksession.js => v2.25.2-add-ipaddress-to-playbacksession.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename server/migrations/{v2.20.1-add-ipaddress-to-playbacksession.js => v2.25.2-add-ipaddress-to-playbacksession.js} (98%) diff --git a/server/migrations/v2.20.1-add-ipaddress-to-playbacksession.js b/server/migrations/v2.25.2-add-ipaddress-to-playbacksession.js similarity index 98% rename from server/migrations/v2.20.1-add-ipaddress-to-playbacksession.js rename to server/migrations/v2.25.2-add-ipaddress-to-playbacksession.js index cdf7dc51..0d2eb0d0 100644 --- a/server/migrations/v2.20.1-add-ipaddress-to-playbacksession.js +++ b/server/migrations/v2.25.2-add-ipaddress-to-playbacksession.js @@ -7,7 +7,7 @@ * @property {MigrationContext} context - an object containing the migration context. */ -const migrationVersion = '2.20.1' +const migrationVersion = '2.25.2' const migrationName = `${migrationVersion}-add-ipaddress-to-playbacksession` const loggerPrefix = `[${migrationVersion} migration]` From 8eeb9e33055335db016693f90f030a23ef79474b Mon Sep 17 00:00:00 2001 From: Peter BALIVET Date: Mon, 23 Jun 2025 16:16:02 +0200 Subject: [PATCH 4/4] Added v2.25.2 DB migration unit tests file --- ...2-add-ipaddress-to-playbacksession.test.js | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/server/migrations/v2.25.2-add-ipaddress-to-playbacksession.test.js diff --git a/test/server/migrations/v2.25.2-add-ipaddress-to-playbacksession.test.js b/test/server/migrations/v2.25.2-add-ipaddress-to-playbacksession.test.js new file mode 100644 index 00000000..e822767a --- /dev/null +++ b/test/server/migrations/v2.25.2-add-ipaddress-to-playbacksession.test.js @@ -0,0 +1,46 @@ +const chai = require('chai') +const sinon = require('sinon') +const { expect } = chai + +const { DataTypes } = require('sequelize') + +const { up, down } = require('../../../server/migrations/v2.25.2-add-ipaddress-to-playbacksession') + +describe('Migration v2.25.2-add-ipaddress-to-playbacksession', () => { + let queryInterface, logger + + beforeEach(() => { + queryInterface = { + addColumn: sinon.stub().resolves(), + removeColumn: sinon.stub().resolves() + } + + logger = { + info: sinon.stub(), + error: sinon.stub() + } + }) + + describe('up', () => { + it('should add the ipAddress column to playbackSessions table', async () => { + await up(queryInterface, logger) + + expect(queryInterface.addColumn.calledOnce).to.be.true + expect( + queryInterface.addColumn.calledWith('playbackSessions', 'ipAddress', { + type: DataTypes.STRING, + allowNull: true + }) + ).to.be.true + }) + }) + + describe('down', () => { + it('should remove the ipAddress column from playbackSessions table', async () => { + await down(queryInterface, logger) + + expect(queryInterface.removeColumn.calledOnce).to.be.true + expect(queryInterface.removeColumn.calledWith('playbackSessions', 'ipAddress')).to.be.true + }) + }) +})