Skip to content

Commit bd6bcfd

Browse files
authored
Merge pull request #211 from vernu/dev
allow device deletion
2 parents e372305 + 5467a85 commit bd6bcfd

File tree

7 files changed

+88
-27
lines changed

7 files changed

+88
-27
lines changed

api/src/billing/billing.service.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { User, UserDocument } from '../users/schemas/user.schema'
1616
import { CheckoutResponseDTO, PlanDTO } from './billing.dto'
1717
import { SMSDocument } from '../gateway/schemas/sms.schema'
1818
import { SMS } from '../gateway/schemas/sms.schema'
19-
import { Device, DeviceDocument } from '../gateway/schemas/device.schema'
2019
import { validateEvent } from '@polar-sh/sdk/webhooks'
2120
import {
2221
PolarWebhookPayload,
@@ -41,7 +40,6 @@ export class BillingService {
4140
private subscriptionModel: Model<SubscriptionDocument>,
4241
@InjectModel(User.name) private userModel: Model<UserDocument>,
4342
@InjectModel(SMS.name) private smsModel: Model<SMSDocument>,
44-
@InjectModel(Device.name) private deviceModel: Model<DeviceDocument>,
4543
@InjectModel(PolarWebhookPayload.name)
4644
private polarWebhookPayloadModel: Model<PolarWebhookPayloadDocument>,
4745
@InjectModel(CheckoutSession.name)
@@ -69,17 +67,13 @@ export class BillingService {
6967
})
7068
.populate('plan')
7169

72-
// Get user's devices and usage data
73-
const userDevices = await this.deviceModel.find({ user: user._id }, '_id')
74-
const deviceIds = userDevices.map((d) => d._id)
75-
7670
const processedSmsToday = await this.smsModel.countDocuments({
77-
device: { $in: deviceIds },
71+
user: user._id,
7872
createdAt: { $gte: new Date(new Date().setHours(0, 0, 0, 0)) },
7973
})
8074

8175
const processedSmsLastMonth = await this.smsModel.countDocuments({
82-
device: { $in: deviceIds },
76+
user: user._id,
8377
createdAt: {
8478
$gte: new Date(new Date().setMonth(new Date().getMonth() - 1)),
8579
},
@@ -568,16 +562,12 @@ export class BillingService {
568562
let hasReachedLimit = false
569563
let message = ''
570564

571-
// Get user's devices and then count SMS
572-
const userDevices = await this.deviceModel.find({ user: user._id }, '_id')
573-
const deviceIds = userDevices.map((d) => d._id)
574-
575565
const processedSmsToday = await this.smsModel.countDocuments({
576-
device: { $in: deviceIds },
566+
user: user._id,
577567
createdAt: { $gte: new Date(new Date().setHours(0, 0, 0, 0)) },
578568
})
579569
const processedSmsLastMonth = await this.smsModel.countDocuments({
580-
device: { $in: deviceIds },
570+
user: user._id,
581571
createdAt: {
582572
$gte: new Date(new Date().setMonth(new Date().getMonth() - 1)),
583573
},
@@ -724,19 +714,13 @@ export class BillingService {
724714

725715
const effectiveLimits = this.getEffectiveLimits(subscription, plan)
726716

727-
// First get all devices belonging to the user
728-
const userDevices = await this.deviceModel
729-
.find({ user: new Types.ObjectId(userId) })
730-
.select('_id')
731-
const deviceIds = userDevices.map((device) => device._id)
732-
733717
const processedSmsToday = await this.smsModel.countDocuments({
734-
device: { $in: deviceIds },
718+
user: new Types.ObjectId(userId),
735719
createdAt: { $gte: new Date(new Date().setHours(0, 0, 0, 0)) },
736720
})
737721

738722
const processedSmsLastMonth = await this.smsModel.countDocuments({
739-
device: { $in: deviceIds },
723+
user: new Types.ObjectId(userId),
740724
createdAt: {
741725
$gte: new Date(new Date().setMonth(new Date().getMonth() - 1)),
742726
},

api/src/gateway/gateway.module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { forwardRef, Module } from '@nestjs/common'
22
import { MongooseModule } from '@nestjs/mongoose'
33
import { Device, DeviceSchema } from './schemas/device.schema'
4+
import { DeviceTombstone, DeviceTombstoneSchema } from './schemas/device-tombstone.schema'
45
import { GatewayController } from './gateway.controller'
56
import { GatewayService } from './gateway.service'
67
import { AuthModule } from '../auth/auth.module'
@@ -23,6 +24,10 @@ import { HeartbeatCheckTask } from './tasks/heartbeat-check.task'
2324
name: Device.name,
2425
schema: DeviceSchema,
2526
},
27+
{
28+
name: DeviceTombstone.name,
29+
schema: DeviceTombstoneSchema,
30+
},
2631
{
2732
name: SMS.name,
2833
schema: SMSSchema,

api/src/gateway/gateway.service.spec.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { GatewayService } from './gateway.service'
33
import { AuthModule } from '../auth/auth.module'
44
import { getModelToken } from '@nestjs/mongoose'
55
import { Device, DeviceDocument } from './schemas/device.schema'
6+
import { DeviceTombstone } from './schemas/device-tombstone.schema'
67
import { SMS } from './schemas/sms.schema'
78
import { SMSBatch } from './schemas/sms-batch.schema'
89
import { AuthService } from '../auth/auth.service'
@@ -29,6 +30,7 @@ jest.mock('firebase-admin', () => ({
2930
describe('GatewayService', () => {
3031
let service: GatewayService
3132
let deviceModel: Model<DeviceDocument>
33+
let deviceTombstoneModel: Model<any>
3234
let smsModel: Model<SMS>
3335
let smsBatchModel: Model<SMSBatch>
3436
let authService: AuthService
@@ -60,6 +62,10 @@ describe('GatewayService', () => {
6062
findByIdAndUpdate: jest.fn(),
6163
}
6264

65+
const mockDeviceTombstoneModel = {
66+
updateOne: jest.fn(),
67+
}
68+
6369
const mockAuthService = {
6470
getUserApiKeys: jest.fn(),
6571
}
@@ -85,6 +91,10 @@ describe('GatewayService', () => {
8591
provide: getModelToken(Device.name),
8692
useValue: mockDeviceModel,
8793
},
94+
{
95+
provide: getModelToken(DeviceTombstone.name),
96+
useValue: mockDeviceTombstoneModel,
97+
},
8898
{
8999
provide: getModelToken(SMS.name),
90100
useValue: mockSmsModel,
@@ -115,6 +125,9 @@ describe('GatewayService', () => {
115125

116126
service = module.get<GatewayService>(GatewayService)
117127
deviceModel = module.get<Model<DeviceDocument>>(getModelToken(Device.name))
128+
deviceTombstoneModel = module.get<Model<any>>(
129+
getModelToken(DeviceTombstone.name),
130+
)
118131
smsModel = module.get<Model<SMS>>(getModelToken(SMS.name))
119132
smsBatchModel = module.get<Model<SMSBatch>>(getModelToken(SMSBatch.name))
120133
authService = module.get<AuthService>(AuthService)
@@ -299,16 +312,18 @@ describe('GatewayService', () => {
299312
})
300313

301314
describe('deleteDevice', () => {
302-
const mockDeviceId = 'device123'
315+
const mockDeviceId = '507f1f77bcf86cd799439011'
303316
const mockDevice = { _id: mockDeviceId, model: 'Pixel 6' }
304317

305-
it('should return empty object when device exists', async () => {
318+
it('should tombstone and delete when device exists', async () => {
306319
mockDeviceModel.findById.mockResolvedValue(mockDevice)
307320

308321
const result = await service.deleteDevice(mockDeviceId)
309322

310323
expect(mockDeviceModel.findById).toHaveBeenCalledWith(mockDeviceId)
311-
expect(result).toEqual({})
324+
expect(mockDeviceTombstoneModel.updateOne).toHaveBeenCalled()
325+
expect(mockDeviceModel.findByIdAndDelete).toHaveBeenCalledWith(mockDeviceId)
326+
expect(result).toEqual({ success: true })
312327
})
313328

314329
it('should throw an error if device does not exist', async () => {

api/src/gateway/gateway.service.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { InjectModel } from '@nestjs/mongoose'
33
import { Device, DeviceDocument } from './schemas/device.schema'
44
import { Model, Types } from 'mongoose'
55
import * as firebaseAdmin from 'firebase-admin'
6+
import { DeviceTombstone, DeviceTombstoneDocument } from './schemas/device-tombstone.schema'
67
import {
78
ReceivedSMSDTO,
89
RegisterDeviceInputDTO,
@@ -28,6 +29,8 @@ import { SmsQueueService } from './queue/sms-queue.service'
2829
export class GatewayService {
2930
constructor(
3031
@InjectModel(Device.name) private deviceModel: Model<DeviceDocument>,
32+
@InjectModel(DeviceTombstone.name)
33+
private deviceTombstoneModel: Model<DeviceTombstoneDocument>,
3134
@InjectModel(SMS.name) private smsModel: Model<SMS>,
3235
@InjectModel(SMSBatch.name) private smsBatchModel: Model<SMSBatch>,
3336
private authService: AuthService,
@@ -141,8 +144,21 @@ export class GatewayService {
141144
)
142145
}
143146

144-
return {}
145-
// return await this.deviceModel.findByIdAndDelete(deviceId)
147+
await this.deviceTombstoneModel.updateOne(
148+
{ deviceId: new Types.ObjectId(deviceId) },
149+
{
150+
$setOnInsert: {
151+
deviceId: new Types.ObjectId(deviceId),
152+
userId: device.user,
153+
deletedAt: new Date(),
154+
},
155+
},
156+
{ upsert: true },
157+
)
158+
159+
await this.deviceModel.findByIdAndDelete(deviceId)
160+
161+
return { success: true }
146162
}
147163

148164
private calculateDelayFromScheduledAt(scheduledAt?: string): number | undefined {
@@ -256,6 +272,7 @@ export class GatewayService {
256272

257273
try {
258274
smsBatch = await this.smsBatchModel.create({
275+
user: device.user,
259276
device: device._id,
260277
message,
261278
recipientCount: recipients.length,
@@ -278,6 +295,7 @@ export class GatewayService {
278295
for (let recipient of recipients) {
279296
recipient = recipient.replace(/\s+/g, "")
280297
const sms = await this.smsModel.create({
298+
user: device.user,
281299
device: device._id,
282300
smsBatch: smsBatch._id,
283301
message: message,
@@ -465,6 +483,7 @@ export class GatewayService {
465483
const { messageTemplate, messages } = body
466484

467485
const smsBatch = await this.smsBatchModel.create({
486+
user: device.user,
468487
device: device._id,
469488
message: messageTemplate,
470489
recipientCount: messages
@@ -504,6 +523,7 @@ export class GatewayService {
504523
for (let recipient of recipients) {
505524
recipient = recipient.replace(/\s+/g, "")
506525
smsDocumentsToInsert.push({
526+
user: device.user,
507527
device: device._id,
508528
smsBatch: smsBatch._id,
509529
message: message,
@@ -765,6 +785,7 @@ export class GatewayService {
765785
}
766786

767787
const sms = await this.smsModel.create({
788+
user: device.user,
768789
device: device._id,
769790
message: dto.message,
770791
type: SMSType.RECEIVED,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
2+
import { Document, Types } from 'mongoose'
3+
import { User } from '../../users/schemas/user.schema'
4+
5+
export type DeviceTombstoneDocument = DeviceTombstone & Document
6+
7+
@Schema({ timestamps: true })
8+
export class DeviceTombstone {
9+
_id?: Types.ObjectId
10+
11+
@Prop({ type: Types.ObjectId, required: true, unique: true, index: true })
12+
deviceId: Types.ObjectId
13+
14+
@Prop({ type: Types.ObjectId, ref: User.name, required: true, index: true })
15+
userId: User | Types.ObjectId
16+
17+
@Prop({ type: Date, required: true })
18+
deletedAt: Date
19+
}
20+
21+
export const DeviceTombstoneSchema =
22+
SchemaFactory.createForClass(DeviceTombstone)
23+
24+
DeviceTombstoneSchema.index({ userId: 1, deletedAt: -1 })
25+

api/src/gateway/schemas/sms-batch.schema.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
22
import { Document, Types } from 'mongoose'
33
import { Device } from './device.schema'
4+
import { User } from '../../users/schemas/user.schema'
45

56
export type SMSBatchDocument = SMSBatch & Document
67

78
@Schema({ timestamps: true })
89
export class SMSBatch {
910
_id?: Types.ObjectId
1011

12+
@Prop({ type: Types.ObjectId, ref: User.name, required: true, index: true })
13+
user: User | Types.ObjectId
14+
1115
@Prop({ type: Types.ObjectId, ref: Device.name })
1216
device: Device
1317

@@ -53,3 +57,5 @@ export class SMSBatch {
5357
}
5458

5559
export const SMSBatchSchema = SchemaFactory.createForClass(SMSBatch)
60+
61+
SMSBatchSchema.index({ user: 1, createdAt: -1 })

api/src/gateway/schemas/sms.schema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
22
import { Document, Types } from 'mongoose'
33
import { Device } from './device.schema'
44
import { SMSBatch } from './sms-batch.schema'
5+
import { User } from '../../users/schemas/user.schema'
56

67
export type SMSDocument = SMS & Document
78

89
@Schema({ timestamps: true })
910
export class SMS {
1011
_id?: Types.ObjectId
1112

13+
@Prop({ type: Types.ObjectId, ref: User.name, required: true, index: true })
14+
user: User | Types.ObjectId
15+
1216
@Prop({ type: Types.ObjectId, ref: Device.name, required: true })
1317
device: Device | Types.ObjectId
1418

@@ -84,3 +88,4 @@ export const SMSSchema = SchemaFactory.createForClass(SMS)
8488

8589

8690
SMSSchema.index({ device: 1, type: 1, receivedAt: -1 })
91+
SMSSchema.index({ user: 1, createdAt: -1, type: 1 })

0 commit comments

Comments
 (0)