From ede8e4c210607069f2fd301b9c6033a7ff6826d5 Mon Sep 17 00:00:00 2001 From: isra el Date: Thu, 29 Jan 2026 18:47:51 +0300 Subject: [PATCH 1/3] chore: add fallback heartbeat check using fcm --- .../vernu/sms/helpers/HeartbeatHelper.java | 209 ++++++++++++++++++ .../com/vernu/sms/services/FCMService.java | 52 ++++- .../vernu/sms/workers/HeartbeatWorker.java | 186 ++-------------- api/src/gateway/gateway.module.ts | 3 +- api/src/gateway/tasks/heartbeat-check.task.ts | 97 ++++++++ 5 files changed, 373 insertions(+), 174 deletions(-) create mode 100644 android/app/src/main/java/com/vernu/sms/helpers/HeartbeatHelper.java create mode 100644 api/src/gateway/tasks/heartbeat-check.task.ts diff --git a/android/app/src/main/java/com/vernu/sms/helpers/HeartbeatHelper.java b/android/app/src/main/java/com/vernu/sms/helpers/HeartbeatHelper.java new file mode 100644 index 0000000..e927ffe --- /dev/null +++ b/android/app/src/main/java/com/vernu/sms/helpers/HeartbeatHelper.java @@ -0,0 +1,209 @@ +package com.vernu.sms.helpers; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.BatteryManager; +import android.os.StatFs; +import android.os.SystemClock; +import android.util.Log; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.vernu.sms.ApiManager; +import com.vernu.sms.AppConstants; +import com.vernu.sms.BuildConfig; +import com.vernu.sms.dtos.HeartbeatInputDTO; +import com.vernu.sms.dtos.HeartbeatResponseDTO; +import com.vernu.sms.dtos.SimInfoCollectionDTO; +import com.vernu.sms.TextBeeUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import retrofit2.Call; +import retrofit2.Response; + +public class HeartbeatHelper { + private static final String TAG = "HeartbeatHelper"; + + /** + * Collects device information and sends a heartbeat request to the API. + * + * @param context Application context + * @param deviceId Device ID + * @param apiKey API key for authentication + * @return true if heartbeat was sent successfully, false otherwise + */ + public static boolean sendHeartbeat(Context context, String deviceId, String apiKey) { + if (deviceId == null || deviceId.isEmpty()) { + Log.d(TAG, "Device not registered, skipping heartbeat"); + return false; + } + + if (apiKey == null || apiKey.isEmpty()) { + Log.e(TAG, "API key not available, skipping heartbeat"); + return false; + } + + // Collect device information + HeartbeatInputDTO heartbeatInput = new HeartbeatInputDTO(); + + try { + // Get FCM token (blocking wait) + try { + CountDownLatch latch = new CountDownLatch(1); + final String[] fcmToken = new String[1]; + FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> { + if (task.isSuccessful()) { + fcmToken[0] = task.getResult(); + } + latch.countDown(); + }); + if (latch.await(5, TimeUnit.SECONDS) && fcmToken[0] != null) { + heartbeatInput.setFcmToken(fcmToken[0]); + } + } catch (Exception e) { + Log.e(TAG, "Failed to get FCM token: " + e.getMessage()); + // Continue without FCM token + } + + // Get battery information + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatus = context.registerReceiver(null, ifilter); + if (batteryStatus != null) { + int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + int batteryPct = (int) ((level / (float) scale) * 100); + heartbeatInput.setBatteryPercentage(batteryPct); + + int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || + status == BatteryManager.BATTERY_STATUS_FULL; + heartbeatInput.setIsCharging(isCharging); + } + + // Get network type + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm != null) { + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork != null && activeNetwork.isConnected()) { + if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { + heartbeatInput.setNetworkType("wifi"); + } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { + heartbeatInput.setNetworkType("cellular"); + } else { + heartbeatInput.setNetworkType("none"); + } + } else { + heartbeatInput.setNetworkType("none"); + } + } + + // Get app version + heartbeatInput.setAppVersionName(BuildConfig.VERSION_NAME); + heartbeatInput.setAppVersionCode(BuildConfig.VERSION_CODE); + + // Get device uptime + heartbeatInput.setDeviceUptimeMillis(SystemClock.uptimeMillis()); + + // Get memory information + Runtime runtime = Runtime.getRuntime(); + heartbeatInput.setMemoryFreeBytes(runtime.freeMemory()); + heartbeatInput.setMemoryTotalBytes(runtime.totalMemory()); + heartbeatInput.setMemoryMaxBytes(runtime.maxMemory()); + + // Get storage information + File internalStorage = context.getFilesDir(); + StatFs statFs = new StatFs(internalStorage.getPath()); + long availableBytes = statFs.getAvailableBytes(); + long totalBytes = statFs.getTotalBytes(); + heartbeatInput.setStorageAvailableBytes(availableBytes); + heartbeatInput.setStorageTotalBytes(totalBytes); + + // Get system information + heartbeatInput.setTimezone(TimeZone.getDefault().getID()); + heartbeatInput.setLocale(Locale.getDefault().toString()); + + // Get receive SMS enabled status + boolean receiveSMSEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean( + context, + AppConstants.SHARED_PREFS_RECEIVE_SMS_ENABLED_KEY, + false + ); + heartbeatInput.setReceiveSMSEnabled(receiveSMSEnabled); + + // Collect SIM information + SimInfoCollectionDTO simInfoCollection = new SimInfoCollectionDTO(); + simInfoCollection.setLastUpdated(System.currentTimeMillis()); + simInfoCollection.setSims(TextBeeUtils.collectSimInfo(context)); + heartbeatInput.setSimInfo(simInfoCollection); + + // Send heartbeat request + Call call = ApiManager.getApiService().heartbeat(deviceId, apiKey, heartbeatInput); + Response response = call.execute(); + + if (response.isSuccessful() && response.body() != null) { + HeartbeatResponseDTO responseBody = response.body(); + if (responseBody.fcmTokenUpdated) { + Log.d(TAG, "FCM token was updated during heartbeat"); + } + Log.d(TAG, "Heartbeat sent successfully"); + return true; + } else { + Log.e(TAG, "Failed to send heartbeat. Response code: " + (response.code())); + return false; + } + } catch (IOException e) { + Log.e(TAG, "Heartbeat API call failed: " + e.getMessage()); + return false; + } catch (Exception e) { + Log.e(TAG, "Error collecting device information: " + e.getMessage()); + return false; + } + } + + /** + * Checks if device is eligible to send heartbeat (registered, enabled, heartbeat enabled). + * + * @param context Application context + * @return true if device is eligible, false otherwise + */ + public static boolean isDeviceEligibleForHeartbeat(Context context) { + // Check if device is registered + String deviceId = SharedPreferenceHelper.getSharedPreferenceString( + context, + AppConstants.SHARED_PREFS_DEVICE_ID_KEY, + "" + ); + + if (deviceId.isEmpty()) { + return false; + } + + // Check if device is enabled + boolean deviceEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean( + context, + AppConstants.SHARED_PREFS_GATEWAY_ENABLED_KEY, + false + ); + + if (!deviceEnabled) { + return false; + } + + // Check if heartbeat feature is enabled + boolean heartbeatEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean( + context, + AppConstants.SHARED_PREFS_HEARTBEAT_ENABLED_KEY, + true // Default to true + ); + + return heartbeatEnabled; + } +} diff --git a/android/app/src/main/java/com/vernu/sms/services/FCMService.java b/android/app/src/main/java/com/vernu/sms/services/FCMService.java index 8fb9d03..c923d31 100644 --- a/android/app/src/main/java/com/vernu/sms/services/FCMService.java +++ b/android/app/src/main/java/com/vernu/sms/services/FCMService.java @@ -18,6 +18,8 @@ import com.vernu.sms.R; import com.vernu.sms.activities.MainActivity; import com.vernu.sms.helpers.SMSHelper; import com.vernu.sms.helpers.SharedPreferenceHelper; +import com.vernu.sms.helpers.HeartbeatHelper; +import com.vernu.sms.helpers.HeartbeatManager; import com.vernu.sms.models.SMSPayload; import com.vernu.sms.TextBeeUtils; import com.vernu.sms.dtos.RegisterDeviceInputDTO; @@ -37,7 +39,16 @@ public class FCMService extends FirebaseMessagingService { Log.d(TAG, remoteMessage.getData().toString()); try { - // Parse SMS payload data + // Check message type first + String messageType = remoteMessage.getData().get("type"); + + if ("heartbeat_check".equals(messageType)) { + // Handle heartbeat check request from backend + handleHeartbeatCheck(); + return; + } + + // Parse SMS payload data (legacy handling) Gson gson = new Gson(); SMSPayload smsPayload = gson.fromJson(remoteMessage.getData().get("smsData"), SMSPayload.class); @@ -55,6 +66,45 @@ public class FCMService extends FirebaseMessagingService { } } + /** + * Handle heartbeat check request from backend + */ + private void handleHeartbeatCheck() { + Log.d(TAG, "Received heartbeat check request from backend"); + + // Check if device is eligible for heartbeat + if (!HeartbeatHelper.isDeviceEligibleForHeartbeat(this)) { + Log.d(TAG, "Device not eligible for heartbeat, skipping heartbeat check"); + return; + } + + // Get device ID and API key + String deviceId = SharedPreferenceHelper.getSharedPreferenceString( + this, + AppConstants.SHARED_PREFS_DEVICE_ID_KEY, + "" + ); + + String apiKey = SharedPreferenceHelper.getSharedPreferenceString( + this, + AppConstants.SHARED_PREFS_API_KEY_KEY, + "" + ); + + // Send heartbeat using shared helper + boolean success = HeartbeatHelper.sendHeartbeat(this, deviceId, apiKey); + + if (success) { + Log.d(TAG, "Heartbeat sent successfully in response to backend check"); + // Ensure scheduled work is added if missing + HeartbeatManager.scheduleHeartbeat(this); + } else { + Log.e(TAG, "Failed to send heartbeat in response to backend check"); + // Still try to ensure scheduled work is added + HeartbeatManager.scheduleHeartbeat(this); + } + } + /** * Send SMS to recipients using the provided payload */ diff --git a/android/app/src/main/java/com/vernu/sms/workers/HeartbeatWorker.java b/android/app/src/main/java/com/vernu/sms/workers/HeartbeatWorker.java index 095d8cc..6fb2c02 100644 --- a/android/app/src/main/java/com/vernu/sms/workers/HeartbeatWorker.java +++ b/android/app/src/main/java/com/vernu/sms/workers/HeartbeatWorker.java @@ -1,39 +1,15 @@ package com.vernu.sms.workers; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.BatteryManager; -import android.os.Build; -import android.os.StatFs; -import android.os.SystemClock; import android.util.Log; import androidx.annotation.NonNull; import androidx.work.Worker; import androidx.work.WorkerParameters; -import com.google.firebase.messaging.FirebaseMessaging; -import com.vernu.sms.ApiManager; import com.vernu.sms.AppConstants; -import com.vernu.sms.BuildConfig; -import com.vernu.sms.dtos.HeartbeatInputDTO; -import com.vernu.sms.dtos.HeartbeatResponseDTO; -import com.vernu.sms.dtos.SimInfoCollectionDTO; import com.vernu.sms.helpers.SharedPreferenceHelper; -import com.vernu.sms.TextBeeUtils; - -import java.io.File; -import java.io.IOException; -import java.util.Locale; -import java.util.TimeZone; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import retrofit2.Call; -import retrofit2.Response; +import com.vernu.sms.helpers.HeartbeatHelper; public class HeartbeatWorker extends Worker { private static final String TAG = "HeartbeatWorker"; @@ -47,166 +23,32 @@ public class HeartbeatWorker extends Worker { public Result doWork() { Context context = getApplicationContext(); - // Check if device is registered + // Check if device is eligible for heartbeat + if (!HeartbeatHelper.isDeviceEligibleForHeartbeat(context)) { + Log.d(TAG, "Device not eligible for heartbeat, skipping"); + return Result.success(); // Not a failure, just skip + } + + // Get device ID and API key String deviceId = SharedPreferenceHelper.getSharedPreferenceString( context, AppConstants.SHARED_PREFS_DEVICE_ID_KEY, "" ); - if (deviceId.isEmpty()) { - Log.d(TAG, "Device not registered, skipping heartbeat"); - return Result.success(); // Not a failure, just skip - } - - // Check if device is enabled - boolean deviceEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean( - context, - AppConstants.SHARED_PREFS_GATEWAY_ENABLED_KEY, - false - ); - - if (!deviceEnabled) { - Log.d(TAG, "Device not enabled, skipping heartbeat"); - return Result.success(); // Not a failure, just skip - } - - // Check if heartbeat feature is enabled - boolean heartbeatEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean( - context, - AppConstants.SHARED_PREFS_HEARTBEAT_ENABLED_KEY, - true // Default to true - ); - - if (!heartbeatEnabled) { - Log.d(TAG, "Heartbeat feature disabled, skipping heartbeat"); - return Result.success(); // Not a failure, just skip - } - String apiKey = SharedPreferenceHelper.getSharedPreferenceString( context, AppConstants.SHARED_PREFS_API_KEY_KEY, "" ); - if (apiKey.isEmpty()) { - Log.e(TAG, "API key not available, skipping heartbeat"); - return Result.success(); // Not a failure, just skip - } + // Send heartbeat using shared helper + boolean success = HeartbeatHelper.sendHeartbeat(context, deviceId, apiKey); - // Collect device information - HeartbeatInputDTO heartbeatInput = new HeartbeatInputDTO(); - - try { - // Get FCM token (blocking wait) - try { - CountDownLatch latch = new CountDownLatch(1); - final String[] fcmToken = new String[1]; - FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> { - if (task.isSuccessful()) { - fcmToken[0] = task.getResult(); - } - latch.countDown(); - }); - if (latch.await(5, TimeUnit.SECONDS) && fcmToken[0] != null) { - heartbeatInput.setFcmToken(fcmToken[0]); - } - } catch (Exception e) { - Log.e(TAG, "Failed to get FCM token: " + e.getMessage()); - // Continue without FCM token - } - - // Get battery information - IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - Intent batteryStatus = context.registerReceiver(null, ifilter); - if (batteryStatus != null) { - int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - int batteryPct = (int) ((level / (float) scale) * 100); - heartbeatInput.setBatteryPercentage(batteryPct); - - int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); - boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || - status == BatteryManager.BATTERY_STATUS_FULL; - heartbeatInput.setIsCharging(isCharging); - } - - // Get network type - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (cm != null) { - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - if (activeNetwork != null && activeNetwork.isConnected()) { - if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { - heartbeatInput.setNetworkType("wifi"); - } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { - heartbeatInput.setNetworkType("cellular"); - } else { - heartbeatInput.setNetworkType("none"); - } - } else { - heartbeatInput.setNetworkType("none"); - } - } - - // Get app version - heartbeatInput.setAppVersionName(BuildConfig.VERSION_NAME); - heartbeatInput.setAppVersionCode(BuildConfig.VERSION_CODE); - - // Get device uptime - heartbeatInput.setDeviceUptimeMillis(SystemClock.uptimeMillis()); - - // Get memory information - Runtime runtime = Runtime.getRuntime(); - heartbeatInput.setMemoryFreeBytes(runtime.freeMemory()); - heartbeatInput.setMemoryTotalBytes(runtime.totalMemory()); - heartbeatInput.setMemoryMaxBytes(runtime.maxMemory()); - - // Get storage information - File internalStorage = context.getFilesDir(); - StatFs statFs = new StatFs(internalStorage.getPath()); - long availableBytes = statFs.getAvailableBytes(); - long totalBytes = statFs.getTotalBytes(); - heartbeatInput.setStorageAvailableBytes(availableBytes); - heartbeatInput.setStorageTotalBytes(totalBytes); - - // Get system information - heartbeatInput.setTimezone(TimeZone.getDefault().getID()); - heartbeatInput.setLocale(Locale.getDefault().toString()); - - // Get receive SMS enabled status - boolean receiveSMSEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean( - context, - AppConstants.SHARED_PREFS_RECEIVE_SMS_ENABLED_KEY, - false - ); - heartbeatInput.setReceiveSMSEnabled(receiveSMSEnabled); - - // Collect SIM information - SimInfoCollectionDTO simInfoCollection = new SimInfoCollectionDTO(); - simInfoCollection.setLastUpdated(System.currentTimeMillis()); - simInfoCollection.setSims(TextBeeUtils.collectSimInfo(context)); - heartbeatInput.setSimInfo(simInfoCollection); - - // Send heartbeat request - Call call = ApiManager.getApiService().heartbeat(deviceId, apiKey, heartbeatInput); - Response response = call.execute(); - - if (response.isSuccessful() && response.body() != null) { - HeartbeatResponseDTO responseBody = response.body(); - if (responseBody.fcmTokenUpdated) { - Log.d(TAG, "FCM token was updated during heartbeat"); - } - Log.d(TAG, "Heartbeat sent successfully"); - return Result.success(); - } else { - Log.e(TAG, "Failed to send heartbeat. Response code: " + (response.code())); - return Result.retry(); - } - } catch (IOException e) { - Log.e(TAG, "Heartbeat API call failed: " + e.getMessage()); - return Result.retry(); - } catch (Exception e) { - Log.e(TAG, "Error collecting device information: " + e.getMessage()); + if (success) { + return Result.success(); + } else { + Log.e(TAG, "Failed to send heartbeat, will retry"); return Result.retry(); } } diff --git a/api/src/gateway/gateway.module.ts b/api/src/gateway/gateway.module.ts index 7a38af2..93f408f 100644 --- a/api/src/gateway/gateway.module.ts +++ b/api/src/gateway/gateway.module.ts @@ -14,6 +14,7 @@ import { ConfigModule } from '@nestjs/config' import { SmsQueueService } from './queue/sms-queue.service' import { SmsQueueProcessor } from './queue/sms-queue.processor' import { SmsStatusUpdateTask } from './tasks/sms-status-update.task' +import { HeartbeatCheckTask } from './tasks/heartbeat-check.task' @Module({ imports: [ @@ -50,7 +51,7 @@ import { SmsStatusUpdateTask } from './tasks/sms-status-update.task' ConfigModule, ], controllers: [GatewayController], - providers: [GatewayService, SmsQueueService, SmsQueueProcessor, SmsStatusUpdateTask], + providers: [GatewayService, SmsQueueService, SmsQueueProcessor, SmsStatusUpdateTask, HeartbeatCheckTask], exports: [MongooseModule, GatewayService, SmsQueueService], }) export class GatewayModule {} diff --git a/api/src/gateway/tasks/heartbeat-check.task.ts b/api/src/gateway/tasks/heartbeat-check.task.ts new file mode 100644 index 0000000..435c858 --- /dev/null +++ b/api/src/gateway/tasks/heartbeat-check.task.ts @@ -0,0 +1,97 @@ +import { Injectable, Logger } from '@nestjs/common' +import { Cron, CronExpression } from '@nestjs/schedule' +import { InjectModel } from '@nestjs/mongoose' +import { Model } from 'mongoose' +import { Device, DeviceDocument } from '../schemas/device.schema' +import * as firebaseAdmin from 'firebase-admin' +import { Message } from 'firebase-admin/messaging' + +@Injectable() +export class HeartbeatCheckTask { + private readonly logger = new Logger(HeartbeatCheckTask.name) + + constructor( + @InjectModel(Device.name) private deviceModel: Model, + ) {} + + /** + * Cron job that runs every 5 minutes to check for devices with stale heartbeats + * (>30 minutes) and send FCM push notifications to trigger heartbeat requests. + */ + @Cron(CronExpression.EVERY_5_MINUTES) + async checkAndTriggerStaleHeartbeats() { + this.logger.log('Running cron job to check for stale heartbeats') + + const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000) + + try { + // Find devices with stale heartbeats + const devices = await this.deviceModel.find({ + heartbeatEnabled: true, + enabled: true, + $or: [ + { lastHeartbeat: null }, + { lastHeartbeat: { $lt: thirtyMinutesAgo } }, + ], + fcmToken: { $exists: true, $ne: null }, + }) + + if (devices.length === 0) { + this.logger.log('No devices with stale heartbeats found') + return + } + + this.logger.log( + `Found ${devices.length} device(s) with stale heartbeats, sending FCM notifications`, + ) + + // Send FCM messages to trigger heartbeats + const fcmMessages: Message[] = [] + const deviceIds: string[] = [] + + for (const device of devices) { + if (!device.fcmToken) { + continue + } + + const fcmMessage: Message = { + data: { + type: 'heartbeat_check', + }, + token: device.fcmToken, + android: { + priority: 'high', + }, + } + + fcmMessages.push(fcmMessage) + deviceIds.push(device._id.toString()) + } + + if (fcmMessages.length === 0) { + this.logger.warn('No valid FCM tokens found for devices with stale heartbeats') + return + } + + // Send FCM messages + const response = await firebaseAdmin.messaging().sendEach(fcmMessages) + + this.logger.log( + `Sent ${response.successCount} heartbeat check FCM notification(s), ${response.failureCount} failed`, + ) + + // Log failures for debugging + if (response.failureCount > 0) { + response.responses.forEach((resp, index) => { + if (!resp.success) { + this.logger.error( + `Failed to send heartbeat check to device ${deviceIds[index]}: ${resp.error?.message || 'Unknown error'}`, + ) + } + }) + } + } catch (error) { + this.logger.error('Error checking and triggering stale heartbeats', error) + } + } +} From 312e3a4fd4467ecfc2085bb265665206d184fc9b Mon Sep 17 00:00:00 2001 From: isra el Date: Thu, 29 Jan 2026 19:16:25 +0300 Subject: [PATCH 2/3] chore(api): remove unnecessary fields from heartbeat data --- api/src/gateway/gateway.service.ts | 32 ------------------------------ 1 file changed, 32 deletions(-) diff --git a/api/src/gateway/gateway.service.ts b/api/src/gateway/gateway.service.ts index 2aa11b9..a34480e 100644 --- a/api/src/gateway/gateway.service.ts +++ b/api/src/gateway/gateway.service.ts @@ -1118,38 +1118,6 @@ const updatedSms = await this.smsModel.findByIdAndUpdate( updateData['deviceUptimeInfo.lastUpdated'] = now } - // Update memoryInfo if any memory field provided - if ( - input.memoryFreeBytes !== undefined || - input.memoryTotalBytes !== undefined || - input.memoryMaxBytes !== undefined - ) { - if (input.memoryFreeBytes !== undefined) { - updateData['memoryInfo.freeBytes'] = input.memoryFreeBytes - } - if (input.memoryTotalBytes !== undefined) { - updateData['memoryInfo.totalBytes'] = input.memoryTotalBytes - } - if (input.memoryMaxBytes !== undefined) { - updateData['memoryInfo.maxBytes'] = input.memoryMaxBytes - } - updateData['memoryInfo.lastUpdated'] = now - } - - // Update storageInfo if any storage field provided - if ( - input.storageAvailableBytes !== undefined || - input.storageTotalBytes !== undefined - ) { - if (input.storageAvailableBytes !== undefined) { - updateData['storageInfo.availableBytes'] = input.storageAvailableBytes - } - if (input.storageTotalBytes !== undefined) { - updateData['storageInfo.totalBytes'] = input.storageTotalBytes - } - updateData['storageInfo.lastUpdated'] = now - } - // Update systemInfo if timezone or locale provided if (input.timezone !== undefined || input.locale !== undefined) { if (input.timezone !== undefined) { From b6dde1a096ca34fc59c7bfd32a49ed036c11560f Mon Sep 17 00:00:00 2001 From: isra el Date: Thu, 29 Jan 2026 19:25:14 +0300 Subject: [PATCH 3/3] fix failing test --- api/src/gateway/gateway.service.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/src/gateway/gateway.service.spec.ts b/api/src/gateway/gateway.service.spec.ts index 71011ad..a50732e 100644 --- a/api/src/gateway/gateway.service.spec.ts +++ b/api/src/gateway/gateway.service.spec.ts @@ -50,6 +50,7 @@ describe('GatewayService', () => { const mockSmsModel = { create: jest.fn(), find: jest.fn(), + findOne: jest.fn(), updateMany: jest.fn(), countDocuments: jest.fn(), } @@ -179,7 +180,7 @@ describe('GatewayService', () => { }) expect(service.updateDevice).toHaveBeenCalledWith( mockDevice._id.toString(), - { ...mockDeviceInput, enabled: true } + { ...mockDeviceInput, enabled: true, user: mockUser } ) expect(result).toBeDefined() @@ -538,6 +539,7 @@ describe('GatewayService', () => { beforeEach(() => { mockDeviceModel.findById.mockResolvedValue(mockDevice) + mockSmsModel.findOne.mockResolvedValue(null) mockSmsModel.create.mockResolvedValue(mockSms) mockDeviceModel.findByIdAndUpdate.mockImplementation(() => ({ exec: jest.fn().mockResolvedValue(true),