From 0665aa5432ab5c44f7da4a8572fb78a9ff0ccebd Mon Sep 17 00:00:00 2001 From: isra el Date: Tue, 27 Jan 2026 19:46:09 +0300 Subject: [PATCH] feat: support sim card selection via api --- .../main/java/com/vernu/sms/TextBeeUtils.java | 168 ++++++++++++++++++ .../vernu/sms/activities/MainActivity.java | 13 ++ .../com/vernu/sms/dtos/HeartbeatInputDTO.java | 9 + .../sms/dtos/RegisterDeviceInputDTO.java | 9 + .../vernu/sms/dtos/SimInfoCollectionDTO.java | 27 +++ .../java/com/vernu/sms/dtos/SimInfoDTO.java | 97 ++++++++++ .../java/com/vernu/sms/models/SMSPayload.java | 9 + .../com/vernu/sms/services/FCMService.java | 40 ++++- .../vernu/sms/workers/HeartbeatWorker.java | 8 + api/src/gateway/gateway.dto.ts | 53 ++++++ api/src/gateway/gateway.service.ts | 48 ++++- api/src/gateway/schemas/device.schema.ts | 35 ++++ api/src/gateway/schemas/sms.schema.ts | 3 + 13 files changed, 509 insertions(+), 10 deletions(-) create mode 100644 android/app/src/main/java/com/vernu/sms/dtos/SimInfoCollectionDTO.java create mode 100644 android/app/src/main/java/com/vernu/sms/dtos/SimInfoDTO.java diff --git a/android/app/src/main/java/com/vernu/sms/TextBeeUtils.java b/android/app/src/main/java/com/vernu/sms/TextBeeUtils.java index 6e708de..6688b02 100644 --- a/android/app/src/main/java/com/vernu/sms/TextBeeUtils.java +++ b/android/app/src/main/java/com/vernu/sms/TextBeeUtils.java @@ -15,6 +15,7 @@ import androidx.core.content.ContextCompat; import com.google.firebase.crashlytics.FirebaseCrashlytics; import com.vernu.sms.services.StickyNotificationService; import com.vernu.sms.helpers.SharedPreferenceHelper; +import com.vernu.sms.dtos.SimInfoDTO; import java.util.ArrayList; import java.util.List; @@ -117,4 +118,171 @@ public class TextBeeUtils { public static void logException(Throwable throwable, String message) { logException(throwable, message, null); } + + /** + * Collects all available SIM information (physical SIMs and eSIMs) from the device + * + * @param context The application context + * @return List of SimInfoDTO objects containing SIM information, or empty list if permission not granted + */ + public static List collectSimInfo(Context context) { + List simInfoList = new ArrayList<>(); + + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "READ_PHONE_STATE permission not granted, cannot collect SIM info"); + return simInfoList; + } + + try { + SubscriptionManager subscriptionManager = SubscriptionManager.from(context); + List subscriptionInfoList = subscriptionManager.getActiveSubscriptionInfoList(); + + if (subscriptionInfoList == null) { + Log.d(TAG, "No active subscriptions found"); + return simInfoList; + } + + for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) { + SimInfoDTO simInfo = new SimInfoDTO(); + simInfo.setSubscriptionId(subscriptionInfo.getSubscriptionId()); + + // Get ICCID (may be null for eSIM) + try { + String iccId = subscriptionInfo.getIccId(); + if (iccId != null && !iccId.isEmpty()) { + simInfo.setIccId(iccId); + } + } catch (Exception e) { + Log.d(TAG, "Could not get ICCID for subscription " + subscriptionInfo.getSubscriptionId()); + } + + // Get Card ID + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + int cardId = subscriptionInfo.getCardId(); + if (cardId != SubscriptionManager.INVALID_CARD_ID) { + simInfo.setCardId(cardId); + } + } + } catch (Exception e) { + Log.d(TAG, "Could not get Card ID for subscription " + subscriptionInfo.getSubscriptionId()); + } + + // Get carrier name + try { + CharSequence carrierName = subscriptionInfo.getCarrierName(); + if (carrierName != null) { + simInfo.setCarrierName(carrierName.toString()); + } + } catch (Exception e) { + Log.d(TAG, "Could not get carrier name for subscription " + subscriptionInfo.getSubscriptionId()); + } + + // Get display name + try { + CharSequence displayName = subscriptionInfo.getDisplayName(); + if (displayName != null) { + simInfo.setDisplayName(displayName.toString()); + } + } catch (Exception e) { + Log.d(TAG, "Could not get display name for subscription " + subscriptionInfo.getSubscriptionId()); + } + + // Get SIM slot index + try { + int simSlotIndex = subscriptionInfo.getSimSlotIndex(); + if (simSlotIndex >= 0) { + simInfo.setSimSlotIndex(simSlotIndex); + } + } catch (Exception e) { + Log.d(TAG, "Could not get SIM slot index for subscription " + subscriptionInfo.getSubscriptionId()); + } + + // Get MCC + try { + String mcc = subscriptionInfo.getMccString(); + if (mcc != null && !mcc.isEmpty()) { + simInfo.setMcc(mcc); + } + } catch (Exception e) { + Log.d(TAG, "Could not get MCC for subscription " + subscriptionInfo.getSubscriptionId()); + } + + // Get MNC + try { + String mnc = subscriptionInfo.getMncString(); + if (mnc != null && !mnc.isEmpty()) { + simInfo.setMnc(mnc); + } + } catch (Exception e) { + Log.d(TAG, "Could not get MNC for subscription " + subscriptionInfo.getSubscriptionId()); + } + + // Get country ISO + try { + String countryIso = subscriptionInfo.getCountryIso(); + if (countryIso != null && !countryIso.isEmpty()) { + simInfo.setCountryIso(countryIso); + } + } catch (Exception e) { + Log.d(TAG, "Could not get country ISO for subscription " + subscriptionInfo.getSubscriptionId()); + } + + // Get subscription type (0 = physical SIM, 1 = eSIM) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + int subscriptionType = subscriptionInfo.getSubscriptionType(); + if (subscriptionType == SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM) { + simInfo.setSubscriptionType("PHYSICAL_SIM"); + } else if (subscriptionType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) { + simInfo.setSubscriptionType("ESIM"); + } + } else { + // For older Android versions, default to PHYSICAL_SIM + simInfo.setSubscriptionType("PHYSICAL_SIM"); + } + } catch (Exception e) { + Log.d(TAG, "Could not get subscription type for subscription " + subscriptionInfo.getSubscriptionId()); + } + + simInfoList.add(simInfo); + } + } catch (Exception e) { + Log.e(TAG, "Error collecting SIM info: " + e.getMessage(), e); + } + + return simInfoList; + } + + /** + * Validates if a subscription ID exists in the active subscriptions + * + * @param context The application context + * @param subscriptionId The subscription ID to validate + * @return true if the subscription ID exists, false otherwise + */ + public static boolean isValidSubscriptionId(Context context, int subscriptionId) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + return false; + } + + try { + SubscriptionManager subscriptionManager = SubscriptionManager.from(context); + List subscriptionInfoList = subscriptionManager.getActiveSubscriptionInfoList(); + + if (subscriptionInfoList == null) { + return false; + } + + for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) { + if (subscriptionInfo.getSubscriptionId() == subscriptionId) { + return true; + } + } + } catch (Exception e) { + Log.e(TAG, "Error validating subscription ID: " + e.getMessage(), e); + } + + return false; + } } diff --git a/android/app/src/main/java/com/vernu/sms/activities/MainActivity.java b/android/app/src/main/java/com/vernu/sms/activities/MainActivity.java index 9730418..a4fdd57 100644 --- a/android/app/src/main/java/com/vernu/sms/activities/MainActivity.java +++ b/android/app/src/main/java/com/vernu/sms/activities/MainActivity.java @@ -30,6 +30,7 @@ import com.vernu.sms.TextBeeUtils; import com.vernu.sms.R; import com.vernu.sms.dtos.RegisterDeviceInputDTO; import com.vernu.sms.dtos.RegisterDeviceResponseDTO; +import com.vernu.sms.dtos.SimInfoCollectionDTO; import com.vernu.sms.helpers.SharedPreferenceHelper; import com.vernu.sms.helpers.VersionTracker; import com.vernu.sms.helpers.HeartbeatManager; @@ -375,6 +376,12 @@ public class MainActivity extends AppCompatActivity { registerDeviceInput.setAppVersionCode(BuildConfig.VERSION_CODE); registerDeviceInput.setAppVersionName(BuildConfig.VERSION_NAME); + // Collect SIM information + SimInfoCollectionDTO simInfoCollection = new SimInfoCollectionDTO(); + simInfoCollection.setLastUpdated(System.currentTimeMillis()); + simInfoCollection.setSims(TextBeeUtils.collectSimInfo(mContext)); + registerDeviceInput.setSimInfo(simInfoCollection); + // If the user provided a device ID, use it for updating instead of creating new if (!deviceIdInput.isEmpty()) { Log.d(TAG, "Updating device with deviceId: "+ deviceIdInput); @@ -525,6 +532,12 @@ public class MainActivity extends AppCompatActivity { updateDeviceInput.setAppVersionCode(BuildConfig.VERSION_CODE); updateDeviceInput.setAppVersionName(BuildConfig.VERSION_NAME); + // Collect SIM information + SimInfoCollectionDTO simInfoCollection = new SimInfoCollectionDTO(); + simInfoCollection.setLastUpdated(System.currentTimeMillis()); + simInfoCollection.setSims(TextBeeUtils.collectSimInfo(mContext)); + updateDeviceInput.setSimInfo(simInfoCollection); + Call apiCall = ApiManager.getApiService().updateDevice(deviceIdToUse, apiKey, updateDeviceInput); apiCall.enqueue(new Callback() { @Override diff --git a/android/app/src/main/java/com/vernu/sms/dtos/HeartbeatInputDTO.java b/android/app/src/main/java/com/vernu/sms/dtos/HeartbeatInputDTO.java index c946305..5de6b49 100644 --- a/android/app/src/main/java/com/vernu/sms/dtos/HeartbeatInputDTO.java +++ b/android/app/src/main/java/com/vernu/sms/dtos/HeartbeatInputDTO.java @@ -16,6 +16,7 @@ public class HeartbeatInputDTO { private String timezone; private String locale; private Boolean receiveSMSEnabled; + private SimInfoCollectionDTO simInfo; public HeartbeatInputDTO() { } @@ -139,4 +140,12 @@ public class HeartbeatInputDTO { public void setReceiveSMSEnabled(Boolean receiveSMSEnabled) { this.receiveSMSEnabled = receiveSMSEnabled; } + + public SimInfoCollectionDTO getSimInfo() { + return simInfo; + } + + public void setSimInfo(SimInfoCollectionDTO simInfo) { + this.simInfo = simInfo; + } } diff --git a/android/app/src/main/java/com/vernu/sms/dtos/RegisterDeviceInputDTO.java b/android/app/src/main/java/com/vernu/sms/dtos/RegisterDeviceInputDTO.java index b366e50..07d7e37 100644 --- a/android/app/src/main/java/com/vernu/sms/dtos/RegisterDeviceInputDTO.java +++ b/android/app/src/main/java/com/vernu/sms/dtos/RegisterDeviceInputDTO.java @@ -12,6 +12,7 @@ public class RegisterDeviceInputDTO { private String osVersion; private String appVersionName; private int appVersionCode; + private SimInfoCollectionDTO simInfo; public RegisterDeviceInputDTO() { } @@ -107,4 +108,12 @@ public class RegisterDeviceInputDTO { public void setAppVersionCode(int appVersionCode) { this.appVersionCode = appVersionCode; } + + public SimInfoCollectionDTO getSimInfo() { + return simInfo; + } + + public void setSimInfo(SimInfoCollectionDTO simInfo) { + this.simInfo = simInfo; + } } diff --git a/android/app/src/main/java/com/vernu/sms/dtos/SimInfoCollectionDTO.java b/android/app/src/main/java/com/vernu/sms/dtos/SimInfoCollectionDTO.java new file mode 100644 index 0000000..e955034 --- /dev/null +++ b/android/app/src/main/java/com/vernu/sms/dtos/SimInfoCollectionDTO.java @@ -0,0 +1,27 @@ +package com.vernu.sms.dtos; + +import java.util.List; + +public class SimInfoCollectionDTO { + private long lastUpdated; + private List sims; + + public SimInfoCollectionDTO() { + } + + public long getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(long lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public List getSims() { + return sims; + } + + public void setSims(List sims) { + this.sims = sims; + } +} diff --git a/android/app/src/main/java/com/vernu/sms/dtos/SimInfoDTO.java b/android/app/src/main/java/com/vernu/sms/dtos/SimInfoDTO.java new file mode 100644 index 0000000..43cc6a8 --- /dev/null +++ b/android/app/src/main/java/com/vernu/sms/dtos/SimInfoDTO.java @@ -0,0 +1,97 @@ +package com.vernu.sms.dtos; + +public class SimInfoDTO { + private int subscriptionId; + private String iccId; + private Integer cardId; + private String carrierName; + private String displayName; + private Integer simSlotIndex; + private String mcc; + private String mnc; + private String countryIso; + private String subscriptionType; + + public SimInfoDTO() { + } + + public int getSubscriptionId() { + return subscriptionId; + } + + public void setSubscriptionId(int subscriptionId) { + this.subscriptionId = subscriptionId; + } + + public String getIccId() { + return iccId; + } + + public void setIccId(String iccId) { + this.iccId = iccId; + } + + public Integer getCardId() { + return cardId; + } + + public void setCardId(Integer cardId) { + this.cardId = cardId; + } + + public String getCarrierName() { + return carrierName; + } + + public void setCarrierName(String carrierName) { + this.carrierName = carrierName; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public Integer getSimSlotIndex() { + return simSlotIndex; + } + + public void setSimSlotIndex(Integer simSlotIndex) { + this.simSlotIndex = simSlotIndex; + } + + public String getMcc() { + return mcc; + } + + public void setMcc(String mcc) { + this.mcc = mcc; + } + + public String getMnc() { + return mnc; + } + + public void setMnc(String mnc) { + this.mnc = mnc; + } + + public String getCountryIso() { + return countryIso; + } + + public void setCountryIso(String countryIso) { + this.countryIso = countryIso; + } + + public String getSubscriptionType() { + return subscriptionType; + } + + public void setSubscriptionType(String subscriptionType) { + this.subscriptionType = subscriptionType; + } +} diff --git a/android/app/src/main/java/com/vernu/sms/models/SMSPayload.java b/android/app/src/main/java/com/vernu/sms/models/SMSPayload.java index b73bcd6..b9ec05f 100644 --- a/android/app/src/main/java/com/vernu/sms/models/SMSPayload.java +++ b/android/app/src/main/java/com/vernu/sms/models/SMSPayload.java @@ -6,6 +6,7 @@ public class SMSPayload { private String message; private String smsId; private String smsBatchId; + private Integer simSubscriptionId; // Legacy fields that are no longer used private String[] receivers; @@ -45,4 +46,12 @@ public class SMSPayload { public void setSmsBatchId(String smsBatchId) { this.smsBatchId = smsBatchId; } + + public Integer getSimSubscriptionId() { + return simSubscriptionId; + } + + public void setSimSubscriptionId(Integer simSubscriptionId) { + this.simSubscriptionId = simSubscriptionId; + } } 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 92c717b..8fb9d03 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 @@ -19,6 +19,7 @@ import com.vernu.sms.activities.MainActivity; import com.vernu.sms.helpers.SMSHelper; import com.vernu.sms.helpers.SharedPreferenceHelper; import com.vernu.sms.models.SMSPayload; +import com.vernu.sms.TextBeeUtils; import com.vernu.sms.dtos.RegisterDeviceInputDTO; import com.vernu.sms.dtos.RegisterDeviceResponseDTO; import com.vernu.sms.ApiManager; @@ -63,9 +64,35 @@ public class FCMService extends FirebaseMessagingService { return; } - // Get preferred SIM - int preferredSim = SharedPreferenceHelper.getSharedPreferenceInt( - this, AppConstants.SHARED_PREFS_PREFERRED_SIM_KEY, -1); + // Determine which SIM to use (priority: backend-provided > app preference > device default) + Integer simSubscriptionId = null; + + // First, check if backend provided a SIM subscription ID + if (smsPayload.getSimSubscriptionId() != null) { + int backendSimId = smsPayload.getSimSubscriptionId(); + // Validate that the subscription ID exists + if (TextBeeUtils.isValidSubscriptionId(this, backendSimId)) { + simSubscriptionId = backendSimId; + Log.d(TAG, "Using backend-provided SIM subscription ID: " + backendSimId); + } else { + Log.w(TAG, "Backend-provided SIM subscription ID " + backendSimId + " is not valid, falling back to app preference"); + } + } + + // If backend didn't provide a valid SIM, check app preference + if (simSubscriptionId == null) { + int preferredSim = SharedPreferenceHelper.getSharedPreferenceInt( + this, AppConstants.SHARED_PREFS_PREFERRED_SIM_KEY, -1); + if (preferredSim != -1) { + // Validate that the preferred SIM still exists + if (TextBeeUtils.isValidSubscriptionId(this, preferredSim)) { + simSubscriptionId = preferredSim; + Log.d(TAG, "Using app-preferred SIM subscription ID: " + preferredSim); + } else { + Log.w(TAG, "App-preferred SIM subscription ID " + preferredSim + " is no longer valid, using device default"); + } + } + } // Check if SMS payload contains valid recipients String[] recipients = smsPayload.getRecipients(); @@ -82,9 +109,10 @@ public class FCMService extends FirebaseMessagingService { for (String recipient : recipients) { boolean smsSent; - // Try to send using default or specific SIM based on preference - if (preferredSim == -1) { + // Send using determined SIM (or device default if simSubscriptionId is null) + if (simSubscriptionId == null) { // Use default SIM + Log.d(TAG, "Using device default SIM"); smsSent = SMSHelper.sendSMS( recipient, smsPayload.getMessage(), @@ -98,7 +126,7 @@ public class FCMService extends FirebaseMessagingService { smsSent = SMSHelper.sendSMSFromSpecificSim( recipient, smsPayload.getMessage(), - preferredSim, + simSubscriptionId, smsPayload.getSmsId(), smsPayload.getSmsBatchId(), this 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 0558b2a..095d8cc 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 @@ -21,7 +21,9 @@ 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; @@ -179,6 +181,12 @@ public class HeartbeatWorker extends Worker { ); 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(); diff --git a/api/src/gateway/gateway.dto.ts b/api/src/gateway/gateway.dto.ts index d08df9a..29cbc2e 100644 --- a/api/src/gateway/gateway.dto.ts +++ b/api/src/gateway/gateway.dto.ts @@ -1,5 +1,45 @@ import { ApiProperty } from '@nestjs/swagger' +export class SimInfoDTO { + @ApiProperty({ type: Number, required: true }) + subscriptionId: number + + @ApiProperty({ type: String, required: false }) + iccId?: string + + @ApiProperty({ type: Number, required: false }) + cardId?: number + + @ApiProperty({ type: String, required: false }) + carrierName?: string + + @ApiProperty({ type: String, required: false }) + displayName?: string + + @ApiProperty({ type: Number, required: false }) + simSlotIndex?: number + + @ApiProperty({ type: String, required: false }) + mcc?: string + + @ApiProperty({ type: String, required: false }) + mnc?: string + + @ApiProperty({ type: String, required: false }) + countryIso?: string + + @ApiProperty({ type: String, required: false, enum: ['PHYSICAL_SIM', 'ESIM'] }) + subscriptionType?: string +} + +export class SimInfoCollectionDTO { + @ApiProperty({ type: Date, required: true }) + lastUpdated: Date + + @ApiProperty({ type: [SimInfoDTO], required: true }) + sims: SimInfoDTO[] +} + export class RegisterDeviceInputDTO { @ApiProperty({ type: Boolean }) enabled?: boolean @@ -33,6 +73,9 @@ export class RegisterDeviceInputDTO { @ApiProperty({ type: String }) appVersionCode?: number + + @ApiProperty({ type: SimInfoCollectionDTO, required: false }) + simInfo?: SimInfoCollectionDTO } export class SMSData { @@ -51,6 +94,13 @@ export class SMSData { }) recipients: string[] + @ApiProperty({ + type: Number, + required: false, + description: 'Optional SIM subscription ID to use for sending SMS', + }) + simSubscriptionId?: number + // TODO: restructure the Payload such that it contains bactchId, smsId, recipients and message in an optimized way // message: string // bactchId: string @@ -406,6 +456,9 @@ export class HeartbeatInputDTO { description: 'Whether receive SMS feature is enabled', }) receiveSMSEnabled?: boolean + + @ApiProperty({ type: SimInfoCollectionDTO, required: false }) + simInfo?: SimInfoCollectionDTO } export class HeartbeatResponseDTO { diff --git a/api/src/gateway/gateway.service.ts b/api/src/gateway/gateway.service.ts index 7285faf..5fefd4d 100644 --- a/api/src/gateway/gateway.service.ts +++ b/api/src/gateway/gateway.service.ts @@ -46,13 +46,23 @@ export class GatewayService { buildId: input.buildId, }) + const deviceData: any = { ...input, user } + + // Handle simInfo if provided + if (input.simInfo) { + deviceData.simInfo = { + ...input.simInfo, + lastUpdated: input.simInfo.lastUpdated || new Date(), + } + } + if (device && device.appVersionCode <= 11) { return await this.updateDevice(device._id.toString(), { - ...input, + ...deviceData, enabled: true, }) } else { - return await this.deviceModel.create({ ...input, user }) + return await this.deviceModel.create(deviceData) } } @@ -82,10 +92,20 @@ export class GatewayService { if (input.enabled !== false) { input.enabled = true; } + + const updateData: any = { ...input } + + // Handle simInfo if provided + if (input.simInfo) { + updateData.simInfo = { + ...input.simInfo, + lastUpdated: input.simInfo.lastUpdated || new Date(), + } + } return await this.deviceModel.findByIdAndUpdate( deviceId, - { $set: input }, + { $set: updateData }, { new: true }, ) } @@ -183,12 +203,18 @@ export class GatewayService { recipient, requestedAt: new Date(), status: 'pending', + ...(smsData.simSubscriptionId !== undefined && { + simSubscriptionId: smsData.simSubscriptionId, + }), }) const updatedSMSData = { smsId: sms._id, smsBatchId: smsBatch._id, message, recipients: [recipient], + ...(smsData.simSubscriptionId !== undefined && { + simSubscriptionId: smsData.simSubscriptionId, + }), // Legacy fields to be removed in the future smsBody: message, @@ -376,15 +402,21 @@ export class GatewayService { smsBatch: smsBatch._id, message: message, type: SMSType.SENT, -recipient, + recipient, requestedAt: new Date(), status: 'pending', + ...(smsData.simSubscriptionId !== undefined && { + simSubscriptionId: smsData.simSubscriptionId, + }), }) const updatedSMSData = { smsId: sms._id, smsBatchId: smsBatch._id, message, recipients: [recipient], + ...(smsData.simSubscriptionId !== undefined && { + simSubscriptionId: smsData.simSubscriptionId, + }), // Legacy fields to be removed in the future smsBody: message, @@ -1033,6 +1065,14 @@ const updatedSms = await this.smsModel.findByIdAndUpdate( } } + // Update simInfo if provided + if (input.simInfo !== undefined) { + updateData.simInfo = { + ...input.simInfo, + lastUpdated: input.simInfo.lastUpdated || now, + } + } + // Update device with all changes await this.deviceModel.findByIdAndUpdate(deviceId, { $set: updateData, diff --git a/api/src/gateway/schemas/device.schema.ts b/api/src/gateway/schemas/device.schema.ts index 7ee8958..454b57c 100644 --- a/api/src/gateway/schemas/device.schema.ts +++ b/api/src/gateway/schemas/device.schema.ts @@ -150,6 +150,41 @@ export class Device { locale?: string lastUpdated?: Date } + + @Prop({ + type: { + lastUpdated: Date, + sims: [ + { + subscriptionId: Number, + iccId: String, + cardId: Number, + carrierName: String, + displayName: String, + simSlotIndex: Number, + mcc: String, + mnc: String, + countryIso: String, + subscriptionType: String, + }, + ], + }, + }) + simInfo: { + lastUpdated?: Date + sims?: Array<{ + subscriptionId: number + iccId?: string + cardId?: number + carrierName?: string + displayName?: string + simSlotIndex?: number + mcc?: string + mnc?: string + countryIso?: string + subscriptionType?: string + }> + } } export const DeviceSchema = SchemaFactory.createForClass(Device) diff --git a/api/src/gateway/schemas/sms.schema.ts b/api/src/gateway/schemas/sms.schema.ts index b3db790..370d51e 100644 --- a/api/src/gateway/schemas/sms.schema.ts +++ b/api/src/gateway/schemas/sms.schema.ts @@ -62,6 +62,9 @@ export class SMS { @Prop({ type: String, default: 'pending' }) status: 'pending' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received' + @Prop({ type: Number, required: false }) + simSubscriptionId?: number + // misc metadata for debugging @Prop({ type: Object }) metadata: Record