diff --git a/core/_cli/migrations/20230913000001-add-timezone-locked-to-streams.js b/core/_cli/migrations/20230913000001-add-timezone-locked-to-streams.js new file mode 100644 index 000000000..c19b0cf8e --- /dev/null +++ b/core/_cli/migrations/20230913000001-add-timezone-locked-to-streams.js @@ -0,0 +1,17 @@ +'use strict' +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addColumn( + 'streams', + 'timezone_locked', + { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false + } + ) + }, + down: (queryInterface, Sequelize) => { + return queryInterface.removeColumn('users', 'timezone_locked') + } +} diff --git a/core/_models/streams/stream.js b/core/_models/streams/stream.js index ee24b7e09..f810b7991 100644 --- a/core/_models/streams/stream.js +++ b/core/_models/streams/stream.js @@ -66,6 +66,11 @@ module.exports = function (sequelize, DataTypes) { type: DataTypes.STRING(40), allowNull: true }, + timezoneLocked: { + type: DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + }, maxSampleRate: { type: DataTypes.INTEGER, allowNull: true @@ -151,7 +156,7 @@ module.exports = function (sequelize, DataTypes) { Stream.belongsTo(models.User, { as: 'created_by', foreignKey: 'created_by_id' }) } Stream.attributes = { - full: ['id', 'name', 'description', 'start', 'end', 'project_id', 'is_public', 'latitude', 'longitude', 'altitude', 'timezone', 'max_sample_rate', 'external_id', 'created_by_id', 'created_at', 'updated_at'], + full: ['id', 'name', 'description', 'start', 'end', 'project_id', 'is_public', 'latitude', 'longitude', 'altitude', 'timezone', 'timezone_locked', 'max_sample_rate', 'external_id', 'created_by_id', 'created_at', 'updated_at'], lite: ['id', 'name', 'start', 'end', 'latitude', 'longitude', 'altitude', 'is_public'] } Stream.include = includeBuilder(Stream, 'stream', Stream.attributes.lite) diff --git a/core/streams/create.int.test.js b/core/streams/create.int.test.js index ad711b07b..5f65e5acd 100644 --- a/core/streams/create.int.test.js +++ b/core/streams/create.int.test.js @@ -69,6 +69,7 @@ describe('POST /streams', () => { const stream = await models.Stream.findByPk(id) expect(stream.name).toBe(requestBody.name) expect(stream.createdById).toBe(seedValues.primaryUserId) + expect(stream.timezone).toBe('Asia/Phnom_Penh') expect(stream.externalId).toBe(123) }) diff --git a/core/streams/dao/index.js b/core/streams/dao/index.js index e78dfd3bc..596194772 100644 --- a/core/streams/dao/index.js +++ b/core/streams/dao/index.js @@ -13,16 +13,17 @@ const availableIncludes = [ Project.include({ required: false }) ] -function computedAdditions (stream) { +function computedAdditions (data, stream = {}) { const additions = {} - const { latitude, longitude } = stream + const { latitude, longitude } = data if (latitude !== undefined && longitude !== undefined) { const country = crg.get_country(latitude, longitude) if (country) { additions.countryName = country.name } - - additions.timezone = getTzByLatLng(latitude, longitude) + if (!stream.timezone_locked) { + additions.timezone = getTzByLatLng(latitude, longitude) + } } return additions } @@ -230,16 +231,16 @@ async function query (filters, options = {}) { * @throws EmptyResultError when stream not found * @throws ForbiddenError when `updatableBy` user does not have update permission on the stream */ -async function update (id, stream, options = {}) { +async function update (id, data, options = {}) { + const transaction = options.transaction || null + const stream = await get(id, { transaction }) if (options.updatableBy && !(await hasPermission(UPDATE, options.updatableBy, id, STREAM))) { throw new ForbiddenError() } - const fullStream = { ...stream, ...computedAdditions(stream) } - const transaction = options.transaction || null + const fullStream = { ...data, ...computedAdditions(data, stream) } if (fullStream.name) { - const existingStream = await get(id, { transaction }) - if (existingStream && existingStream.project_id && existingStream.name !== fullStream.name) { - const duplicateStreamInProject = await query({ names: [fullStream.name], projects: [fullStream.project_id || existingStream.project_id] }, { fields: 'id', transaction }) + if (stream && stream.project_id && stream.name !== fullStream.name) { + const duplicateStreamInProject = await query({ names: [fullStream.name], projects: [fullStream.project_id || stream.project_id] }, { fields: 'id', transaction }) if (duplicateStreamInProject.total > 0) { throw new ValidationError('Duplicate stream name in the project') } diff --git a/core/streams/update.int.test.js b/core/streams/update.int.test.js index ee53f12dc..3a1501d1a 100644 --- a/core/streams/update.int.test.js +++ b/core/streams/update.int.test.js @@ -169,4 +169,36 @@ describe('PATCH /streams/:id', () => { expect(response.statusCode).toBe(204) }) + + test('timezone is updated when coordinates are changed', async () => { + const newLat = 10 + const newLon = 20 + const stream = { id: 'am1', name: 'Big tree', latitude: 18, longitude: -66, timezone: 'America/Puerto_Rico', timezoneLocked: false, createdById: seedValues.primaryUserId } + await models.Stream.create(stream) + + const requestBody = { latitude: newLat, longitude: newLon } + const response = await request(app).patch(`/${stream.id}`).send(requestBody) + + expect(response.statusCode).toBe(204) + const streamUpdated = await models.Stream.findByPk(stream.id) + expect(streamUpdated.latitude).toBe(newLat) + expect(streamUpdated.longitude).toBe(newLon) + expect(streamUpdated.timezone).toBe('Africa/Ndjamena') + }) + + test('timezone is not updated when coordinates are changed, but timezone_locked is true', async () => { + const newLat = 10 + const newLon = 20 + const stream = { id: 'am1', name: 'Big tree', latitude: 18, longitude: -66, timezone: 'America/Puerto_Rico', timezoneLocked: true, createdById: seedValues.primaryUserId } + await models.Stream.create(stream) + + const requestBody = { latitude: newLat, longitude: newLon } + const response = await request(app).patch(`/${stream.id}`).send(requestBody) + + expect(response.statusCode).toBe(204) + const streamUpdated = await models.Stream.findByPk(stream.id) + expect(streamUpdated.latitude).toBe(newLat) + expect(streamUpdated.longitude).toBe(newLon) + expect(streamUpdated.timezone).toBe(stream.timezone) + }) })