mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-09-01 12:26:08 +08:00
Revert "Add devices table for deduplication (#685)"
This reverts commit 56a96233d8
.
# Conflicts:
# apps/platform/src/providers/push/PushChannel.ts
This commit is contained in:
parent
6dd493b735
commit
c2cd905e6a
12 changed files with 108 additions and 286 deletions
|
@ -1,41 +0,0 @@
|
|||
exports.up = async function(knex) {
|
||||
await knex.schema
|
||||
.createTable('devices', function(table) {
|
||||
table.increments()
|
||||
table.integer('project_id')
|
||||
.unsigned()
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('projects')
|
||||
.onDelete('CASCADE')
|
||||
table.integer('user_id')
|
||||
.unsigned()
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('users')
|
||||
.onDelete('CASCADE')
|
||||
table.string('device_id', 255).notNullable()
|
||||
table.string('token', 255)
|
||||
table.string('os')
|
||||
table.string('os_version')
|
||||
table.string('model')
|
||||
table.string('app_version')
|
||||
table.string('app_build')
|
||||
table.timestamp('created_at').defaultTo(knex.fn.now())
|
||||
table.timestamp('updated_at').defaultTo(knex.fn.now())
|
||||
|
||||
table.unique(['project_id', 'token'])
|
||||
table.unique(['project_id', 'device_id'])
|
||||
})
|
||||
|
||||
await knex.schema.table('users', function(table) {
|
||||
table.boolean('has_push_device').defaultTo(0)
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = async function(knex) {
|
||||
await knex.schema.dropTable('devices')
|
||||
await knex.schema.table('users', function(table) {
|
||||
table.dropColumn('has_push_device')
|
||||
})
|
||||
}
|
|
@ -450,7 +450,7 @@ const recipientClickhouseQuery = async (campaign: Campaign) => {
|
|||
} else if (campaign.channel === 'text') {
|
||||
return "(users.phone != '' AND users.phone IS NOT NULL)"
|
||||
} else if (campaign.channel === 'push') {
|
||||
return '((users.devices IS NOT NULL AND NOT empty(users.devices)) OR users.has_push_device = 1)'
|
||||
return '(users.devices IS NOT NULL AND NOT empty(users.devices))'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { JSONSchemaType, validate } from '../core/validate'
|
|||
import { ClientIdentifyParams, ClientIdentityKeys, ClientPostEventsRequest } from './Client'
|
||||
import { ProjectState } from '../auth/AuthMiddleware'
|
||||
import { projectMiddleware } from '../projects/ProjectController'
|
||||
import { DeviceParams } from '../users/Device'
|
||||
import { DeviceParams } from '../users/User'
|
||||
import UserPatchJob from '../users/UserPatchJob'
|
||||
import UserDeviceJob from '../users/UserDeviceJob'
|
||||
import UserAliasJob from '../users/UserAliasJob'
|
||||
|
|
|
@ -2,7 +2,6 @@ import { PushTemplate } from '../../render/Template'
|
|||
import { Variables } from '../../render'
|
||||
import { PushProvider } from './PushProvider'
|
||||
import { PushResponse } from './Push'
|
||||
import { PushDevice } from '../../users/Device'
|
||||
|
||||
export default class PushChannel {
|
||||
readonly provider: PushProvider
|
||||
|
@ -15,15 +14,10 @@ export default class PushChannel {
|
|||
}
|
||||
}
|
||||
|
||||
async send(template: PushTemplate, devices: PushDevice[], variables: Variables): Promise<PushResponse | undefined> {
|
||||
async send(template: PushTemplate, variables: Variables): Promise<PushResponse | undefined> {
|
||||
|
||||
// Find tokens from active devices with push enabled
|
||||
// Temporarily include the old table
|
||||
const oldDevices = variables.user?.devices?.filter(device => device.token != null) as PushDevice[] ?? []
|
||||
const tokens: string[] = [...new Set([
|
||||
...devices.map(device => device.token),
|
||||
...oldDevices.map(device => device.token),
|
||||
])]
|
||||
const tokens = variables.user.pushEnabledDevices.map(device => device.token)
|
||||
|
||||
const push = {
|
||||
tokens,
|
||||
|
|
|
@ -9,7 +9,6 @@ import { loadPushChannel } from '.'
|
|||
import App from '../../app'
|
||||
import { releaseLock } from '../../core/Lock'
|
||||
import { EventPostJob } from '../../jobs'
|
||||
import { getPushDevicesForUser } from '../../users/DeviceRepository'
|
||||
|
||||
export default class PushJob extends Job {
|
||||
static $name = 'push'
|
||||
|
@ -23,7 +22,6 @@ export default class PushJob extends Job {
|
|||
if (!data) return
|
||||
|
||||
const { campaign, template, user, project, context } = data
|
||||
const devices = await getPushDevicesForUser(project.id, user.id)
|
||||
|
||||
try {
|
||||
// Load email channel so its ready to send
|
||||
|
@ -43,7 +41,7 @@ export default class PushJob extends Job {
|
|||
if (!isReady) return
|
||||
|
||||
// Send the push and update the send record
|
||||
const result = await channel.send(template, devices, data)
|
||||
const result = await channel.send(template, data)
|
||||
if (result) {
|
||||
await finalizeSend(data, result)
|
||||
|
||||
|
@ -51,7 +49,7 @@ export default class PushJob extends Job {
|
|||
// may have failed even though the push was
|
||||
// successful. We need to check for those and
|
||||
// disable them
|
||||
if (result.invalidTokens.length) await disableNotifications(user, result.invalidTokens)
|
||||
if (result.invalidTokens.length) await disableNotifications(user.id, result.invalidTokens)
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
|
@ -59,7 +57,7 @@ export default class PushJob extends Job {
|
|||
|
||||
// If the push is unable to send, find invalidated tokens
|
||||
// and disable those devices
|
||||
await disableNotifications(user, error.invalidTokens)
|
||||
await disableNotifications(user.id, error.invalidTokens)
|
||||
|
||||
// Update send record
|
||||
await updateSendState({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PageParams } from '../core/searchParams'
|
||||
import Template, { EmailTemplate, PushTemplate, TemplateParams, TemplateType, TemplateUpdateParams, TextTemplate, WebhookTemplate } from './Template'
|
||||
import Template, { TemplateParams, TemplateType, TemplateUpdateParams } from './Template'
|
||||
import { partialMatchLocale, pick, prune } from '../utilities'
|
||||
import { Variables } from '.'
|
||||
import { loadEmailChannel } from '../providers/email'
|
||||
|
@ -16,8 +16,6 @@ import Project from '../projects/Project'
|
|||
import { getProject } from '../projects/ProjectService'
|
||||
import { logger } from '../config/logger'
|
||||
import EventPostJob from '../client/EventPostJob'
|
||||
import { getPushDevicesForUser } from '../users/DeviceRepository'
|
||||
import Campaign from '../campaigns/Campaign'
|
||||
|
||||
export const pagedTemplates = async (params: PageParams, projectId: number) => {
|
||||
return await Template.search(
|
||||
|
@ -113,13 +111,26 @@ export const sendProof = async (template: TemplateType, variables: Variables, re
|
|||
|
||||
let response: any
|
||||
if (template.type === 'email') {
|
||||
response = await sendEmailProof(campaign, template, variables)
|
||||
const channel = await loadEmailChannel(campaign.provider_id, project.id)
|
||||
response = await channel?.send(template, variables)
|
||||
logger.info(response, 'template:proof:email:result')
|
||||
} else if (template.type === 'text') {
|
||||
response = await sendTextProof(campaign, template, variables)
|
||||
const channel = await loadTextChannel(campaign.provider_id, project.id)
|
||||
response = await channel?.send(template, variables)
|
||||
logger.info(response, 'template:proof:text:result')
|
||||
} else if (template.type === 'push') {
|
||||
response = await sendPushProof(campaign, template, variables)
|
||||
const channel = await loadPushChannel(campaign.provider_id, project.id)
|
||||
if (!user.id) throw new RequestError('Unable to find a user matching the criteria.')
|
||||
response = await channel?.send(template, variables)
|
||||
|
||||
// Disable any tokens that we've discovered are invalid
|
||||
if (response.invalidTokens.length) {
|
||||
await disableNotifications(user.id, response.invalidTokens)
|
||||
}
|
||||
logger.info(response, 'template:proof:push:result')
|
||||
} else if (template.type === 'webhook') {
|
||||
response = await sendWebhookProof(campaign, template, variables)
|
||||
const channel = await loadWebhookChannel(campaign.provider_id, project.id)
|
||||
response = await channel?.send(template, variables)
|
||||
} else {
|
||||
throw new RequestError('Sending template proofs is only supported for email and text message types as this time.')
|
||||
}
|
||||
|
@ -140,43 +151,6 @@ export const sendProof = async (template: TemplateType, variables: Variables, re
|
|||
return response
|
||||
}
|
||||
|
||||
const sendEmailProof = async (campaign: Campaign, template: EmailTemplate, variables: Variables) => {
|
||||
if (variables.user.unsubscribe_ids?.includes(campaign.subscription_id)) {
|
||||
throw new RequestError('This template cannot be sent to this user as they have unsubscribed from emails.')
|
||||
}
|
||||
const channel = await loadEmailChannel(campaign.provider_id, variables.project.id)
|
||||
const response = await channel?.send(template, variables)
|
||||
logger.info(response, 'template:proof:email:result')
|
||||
return response
|
||||
}
|
||||
|
||||
const sendTextProof = async (campaign: Campaign, template: TextTemplate, variables: Variables) => {
|
||||
const channel = await loadTextChannel(campaign.provider_id, variables.project.id)
|
||||
const response = await channel?.send(template, variables)
|
||||
logger.info(response, 'template:proof:text:result')
|
||||
return response
|
||||
}
|
||||
|
||||
const sendPushProof = async (campaign: Campaign, template: PushTemplate, variables: Variables) => {
|
||||
const { user, project } = variables
|
||||
const devices = await getPushDevicesForUser(project.id, user.id)
|
||||
const channel = await loadPushChannel(campaign.provider_id, project.id)
|
||||
if (!user.id) throw new RequestError('Unable to find a user matching the criteria.')
|
||||
const response = await channel?.send(template, devices, variables)
|
||||
|
||||
// Disable any tokens that we've discovered are invalid
|
||||
if (response?.invalidTokens.length) {
|
||||
await disableNotifications(user, response.invalidTokens)
|
||||
}
|
||||
logger.info(response, 'template:proof:push:result')
|
||||
return response
|
||||
}
|
||||
|
||||
const sendWebhookProof = async (campaign: Campaign, template: WebhookTemplate, variables: Variables) => {
|
||||
const channel = await loadWebhookChannel(campaign.provider_id, variables.project.id)
|
||||
return await channel?.send(template, variables)
|
||||
}
|
||||
|
||||
// Determine what template to send to the user based on the following:
|
||||
// - Find an exact match of users locale with a template
|
||||
// - Find a partial match (same root locale i.e. `en` vs `en-US`)
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import { ClientIdentity } from '../client/Client'
|
||||
import Model, { ModelParams } from '../core/Model'
|
||||
|
||||
export class Device extends Model {
|
||||
project_id!: number
|
||||
user_id!: number
|
||||
device_id!: string
|
||||
token?: string | null
|
||||
os?: string
|
||||
os_version?: string
|
||||
model?: string
|
||||
app_build?: string
|
||||
app_version?: string
|
||||
}
|
||||
|
||||
export type DeviceParams = Omit<Device, ModelParams | 'user_id'> & ClientIdentity
|
||||
|
||||
export type PushDevice = Device & { token: string }
|
|
@ -1,35 +0,0 @@
|
|||
import { Transaction } from '../core/Model'
|
||||
import { Device, PushDevice } from './Device'
|
||||
|
||||
export const getDeviceFromIdOrToken = async (projectId: number, deviceId: string, token: string | null | undefined, trx?: Transaction): Promise<Device | undefined> => {
|
||||
if (!deviceId && !token) return undefined
|
||||
return await Device.first(qb => qb.where('project_id', projectId)
|
||||
.where(sqb => {
|
||||
if (deviceId) {
|
||||
sqb.where('device_id', deviceId)
|
||||
}
|
||||
if (token) {
|
||||
sqb.orWhere('token', token)
|
||||
}
|
||||
return sqb
|
||||
}), trx)
|
||||
}
|
||||
|
||||
export const markDevicesAsPushDisabled = async (projectId: number, tokens: string[], trx?: Transaction): Promise<void> => {
|
||||
await Device.update(qb => qb.whereIn('token', tokens).where('project_id', projectId), { token: null }, trx)
|
||||
}
|
||||
|
||||
export const userHasPushDevice = async (projectId: number, userId: number, trx?: Transaction): Promise<boolean> => {
|
||||
return await Device.exists(qb =>
|
||||
qb.where('project_id', projectId)
|
||||
.where('user_id', userId)
|
||||
.whereNotNull('token'),
|
||||
trx,
|
||||
)
|
||||
}
|
||||
|
||||
export const getPushDevicesForUser = async (projectId: number, userId: number, trx?: Transaction): Promise<PushDevice[]> => {
|
||||
return await Device.all(qb => qb.where('project_id', projectId)
|
||||
.where('user_id', userId)
|
||||
.whereNotNull('token'), trx) as PushDevice[]
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
import { ClientIdentity } from '../client/Client'
|
||||
import { UniversalModel } from '../core/Model'
|
||||
import { ModelParams, UniversalModel } from '../core/Model'
|
||||
import parsePhoneNumber from 'libphonenumber-js'
|
||||
import { SubscriptionState } from '../subscriptions/Subscription'
|
||||
import { Device } from './Device'
|
||||
|
||||
export interface TemplateUser extends Record<string, any> {
|
||||
id: string
|
||||
|
@ -17,6 +16,22 @@ export interface UserAttribute {
|
|||
value: any
|
||||
}
|
||||
|
||||
export interface Device {
|
||||
device_id: string
|
||||
token?: string
|
||||
os?: string
|
||||
os_version?: string
|
||||
model?: string
|
||||
app_build?: string
|
||||
app_version?: string
|
||||
}
|
||||
|
||||
export type DeviceParams = Omit<Device, ModelParams> & ClientIdentity
|
||||
|
||||
interface PushEnabledDevice extends Device {
|
||||
token: string
|
||||
}
|
||||
|
||||
export class User extends UniversalModel {
|
||||
project_id!: number
|
||||
anonymous_id!: string
|
||||
|
@ -24,7 +39,6 @@ export class User extends UniversalModel {
|
|||
email?: string
|
||||
phone?: string
|
||||
devices?: Device[]
|
||||
has_push_device!: boolean
|
||||
data!: Record<string, any> // first_name, last_name live in data
|
||||
unsubscribe_ids?: number[]
|
||||
timezone?: string
|
||||
|
@ -50,6 +64,10 @@ export class User extends UniversalModel {
|
|||
}
|
||||
}
|
||||
|
||||
get pushEnabledDevices(): PushEnabledDevice[] {
|
||||
return this.devices?.filter(device => device.token != null) as PushEnabledDevice[] ?? []
|
||||
}
|
||||
|
||||
get fullName() {
|
||||
// Handle case were user has a full name attribute in data
|
||||
const fullName = this.data.full_name ?? this.data.fullName
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Job } from '../queue'
|
||||
import { saveDevice } from './UserRepository'
|
||||
import { DeviceParams } from './User'
|
||||
import App from '../app'
|
||||
import { DeviceParams } from './Device'
|
||||
|
||||
type UserDeviceTrigger = DeviceParams & {
|
||||
project_id: number
|
||||
|
|
|
@ -2,7 +2,7 @@ import { RuleTree } from '../rules/Rule'
|
|||
import { ClientAliasParams, ClientIdentity } from '../client/Client'
|
||||
import { PageParams } from '../core/searchParams'
|
||||
import { RetryError } from '../queue/Job'
|
||||
import { User, UserInternalParams } from '../users/User'
|
||||
import { Device, DeviceParams, User, UserInternalParams } from '../users/User'
|
||||
import { deepEqual, pick, uuid } from '../utilities'
|
||||
import { getRuleEventParams } from '../rules/RuleHelpers'
|
||||
import { UserEvent } from './UserEvent'
|
||||
|
@ -10,16 +10,14 @@ import { Context } from 'koa'
|
|||
import { EventPostJob } from '../jobs'
|
||||
import { Transaction } from '../core/Model'
|
||||
import App from '../app'
|
||||
import { Device, DeviceParams } from './Device'
|
||||
import { getDeviceFromIdOrToken, markDevicesAsPushDisabled, userHasPushDevice } from './DeviceRepository'
|
||||
|
||||
export const getUser = async (id: number, projectId?: number, trx?: Transaction): Promise<User | undefined> => {
|
||||
export const getUser = async (id: number, projectId?: number): Promise<User | undefined> => {
|
||||
return await User.find(id, qb => {
|
||||
if (projectId) {
|
||||
qb.where('project_id', projectId)
|
||||
}
|
||||
return qb
|
||||
}, trx)
|
||||
})
|
||||
}
|
||||
|
||||
export const getUserFromContext = async (ctx: Context): Promise<User | undefined> => {
|
||||
|
@ -189,87 +187,64 @@ export const deleteUser = async (projectId: number, externalId: string): Promise
|
|||
})
|
||||
}
|
||||
|
||||
export const saveDevice = async (projectId: number, { external_id, anonymous_id, ...params }: DeviceParams, trx?: Transaction): Promise<number | undefined> => {
|
||||
export const saveDevice = async (projectId: number, { external_id, anonymous_id, ...params }: DeviceParams, trx?: Transaction): Promise<Device | undefined> => {
|
||||
|
||||
const user = await getUserFromClientId(projectId, { external_id, anonymous_id } as ClientIdentity, trx)
|
||||
if (!user) throw new RetryError()
|
||||
|
||||
const { device_id, token } = params
|
||||
const device = await getDeviceFromIdOrToken(projectId, device_id, token, trx)
|
||||
|
||||
// If we have a device, move it to the new user and update both users
|
||||
// in the DB to reflect their current push state
|
||||
if (device) {
|
||||
|
||||
const oldParams = pick(device, ['os', 'os_version', 'model', 'app_build', 'app_version', 'token'])
|
||||
const newParams = pick(params, ['os', 'os_version', 'model', 'app_build', 'app_version', 'token'])
|
||||
|
||||
// If nothing has changed on the device, just return the ID
|
||||
const isDirty = !deepEqual(oldParams, newParams) || device.user_id !== user.id
|
||||
if (!isDirty) return device.id
|
||||
|
||||
// Update the device, combining the old and new params
|
||||
await Device.update(qb => qb.where('id', device.id), {
|
||||
...oldParams,
|
||||
...newParams,
|
||||
user_id: user.id,
|
||||
}, trx)
|
||||
|
||||
// If this user had no previous push device, update the db
|
||||
if (!user.has_push_device && !!token) {
|
||||
await updateUserDeviceState(user, true, trx)
|
||||
}
|
||||
|
||||
// Update the user we stole the device from
|
||||
const previousUser = await getUser(device.user_id, projectId, trx)
|
||||
const hasPushDevice = await userHasPushDevice(user.project_id, device.user_id, trx)
|
||||
if (previousUser) {
|
||||
await updateUserDeviceState(previousUser, hasPushDevice, trx)
|
||||
}
|
||||
|
||||
return device.id
|
||||
} else {
|
||||
// If no device found, create a new one
|
||||
const newDevice = {
|
||||
...params,
|
||||
project_id: projectId,
|
||||
device_id: device_id ?? uuid(),
|
||||
token,
|
||||
user_id: user.id,
|
||||
}
|
||||
const deviceId = await Device.insert(newDevice, trx)
|
||||
|
||||
// If user previously had another device, no need to update
|
||||
if (user.has_push_device || !token) return deviceId
|
||||
await updateUserDeviceState(user, true, trx)
|
||||
|
||||
return deviceId
|
||||
}
|
||||
}
|
||||
|
||||
export const disableNotifications = async (user: User, tokens: string[]): Promise<boolean> => {
|
||||
|
||||
await App.main.db.transaction(async (trx) => {
|
||||
|
||||
// Wipe the token from all devices provided
|
||||
await markDevicesAsPushDisabled(user.project_id, tokens, trx)
|
||||
|
||||
// Check if the user has any push devices left
|
||||
const hasPushDevice = await userHasPushDevice(user.project_id, user.id, trx)
|
||||
|
||||
// If the push state has changed for a user, update the record
|
||||
if (hasPushDevice === user.has_push_device) return
|
||||
await updateUserDeviceState(user, hasPushDevice, trx)
|
||||
const devices = user.devices ? [...user.devices] : []
|
||||
let device = devices?.find(device => {
|
||||
return device.device_id === params.device_id
|
||||
|| (device.token === params.token && device.token != null)
|
||||
})
|
||||
return true
|
||||
}
|
||||
if (device) {
|
||||
const newDevice = { ...device, ...params }
|
||||
const isDirty = !deepEqual(device, newDevice)
|
||||
if (!isDirty) return device
|
||||
Object.assign(device, params)
|
||||
} else {
|
||||
device = {
|
||||
...params,
|
||||
device_id: params.device_id,
|
||||
}
|
||||
devices.push(device)
|
||||
}
|
||||
|
||||
const updateUserDeviceState = async (user: User, hasPushDevice: boolean, trx?: Transaction): Promise<void> => {
|
||||
const after = await User.updateAndFetch(user.id, {
|
||||
has_push_device: hasPushDevice,
|
||||
devices,
|
||||
version: Date.now(),
|
||||
}, trx)
|
||||
await User.clickhouse().upsert(after, user)
|
||||
|
||||
return device
|
||||
}
|
||||
|
||||
export const disableNotifications = async (userId: number, tokens: string[]): Promise<boolean> => {
|
||||
|
||||
await App.main.db.transaction(async (trx) => {
|
||||
const user = await User.find(userId, undefined, trx)
|
||||
if (!user) return false
|
||||
if (!user.devices?.length) return false
|
||||
|
||||
const devices = []
|
||||
for (const { token, ...device } of user.devices) {
|
||||
if (token && tokens.includes(token)) {
|
||||
devices.push(device)
|
||||
} else {
|
||||
devices.push({
|
||||
...device,
|
||||
token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const after = await User.updateAndFetch(user.id, {
|
||||
devices,
|
||||
version: Date.now(),
|
||||
}, trx)
|
||||
await User.clickhouse().upsert(after, user)
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
export const getUserEventsForRules = async (
|
||||
|
|
|
@ -6,7 +6,6 @@ import { uuid } from '../../utilities'
|
|||
import { User } from '../User'
|
||||
import { UserEvent } from '../UserEvent'
|
||||
import { createEvent } from '../UserEventRepository'
|
||||
import { Device } from '../Device'
|
||||
|
||||
describe('UserRepository', () => {
|
||||
describe('getUserFromClientId', () => {
|
||||
|
@ -55,10 +54,9 @@ describe('UserRepository', () => {
|
|||
external_id: uuid(),
|
||||
})
|
||||
|
||||
const deviceUuid = uuid()
|
||||
const deviceId = await saveDevice(project.id, {
|
||||
const device = await saveDevice(project.id, {
|
||||
external_id: user.external_id,
|
||||
device_id: deviceUuid,
|
||||
device_id: uuid(),
|
||||
token: uuid(),
|
||||
os: 'ios',
|
||||
model: 'iPhone',
|
||||
|
@ -66,11 +64,9 @@ describe('UserRepository', () => {
|
|||
app_version: '1.0',
|
||||
})
|
||||
|
||||
const userDb = await User.find(user.id)
|
||||
const deviceDb = await Device.find(deviceId)
|
||||
expect(userDb?.has_push_device).toEqual(true)
|
||||
expect(deviceDb?.user_id).toEqual(userDb?.id)
|
||||
expect(deviceDb?.device_id).toEqual(deviceUuid)
|
||||
const freshUser = await User.find(user.id)
|
||||
expect(freshUser?.devices?.length).toEqual(1)
|
||||
expect(freshUser?.devices?.[0].device_id).toEqual(device?.device_id)
|
||||
})
|
||||
|
||||
test('update a device for a user', async () => {
|
||||
|
@ -99,50 +95,11 @@ describe('UserRepository', () => {
|
|||
app_version: '1.1',
|
||||
})
|
||||
|
||||
const devices = await Device.all(qb => qb.where('user_id', user.id))
|
||||
expect(devices.length).toEqual(1)
|
||||
expect(devices[0].device_id).toEqual(deviceId)
|
||||
expect(devices[0].token).toEqual(token)
|
||||
expect(devices[0].app_build).toEqual('2')
|
||||
})
|
||||
|
||||
test('changing a devices user moves it', async () => {
|
||||
const project = await createTestProject()
|
||||
const deviceId = uuid()
|
||||
const token = uuid()
|
||||
const user = await createUser(project.id, {
|
||||
external_id: uuid(),
|
||||
})
|
||||
const user2 = await createUser(project.id, {
|
||||
external_id: uuid(),
|
||||
})
|
||||
await saveDevice(project.id, {
|
||||
external_id: user.external_id,
|
||||
device_id: deviceId,
|
||||
token: uuid(),
|
||||
os: 'ios',
|
||||
model: 'iPhone',
|
||||
app_build: '1',
|
||||
app_version: '1.0',
|
||||
})
|
||||
await saveDevice(project.id, {
|
||||
external_id: user2.external_id,
|
||||
device_id: deviceId,
|
||||
token,
|
||||
os: 'ios',
|
||||
model: 'iPhone',
|
||||
app_build: '2',
|
||||
app_version: '1.1',
|
||||
})
|
||||
|
||||
const devices = await Device.all(qb => qb.where('user_id', user.id))
|
||||
const devices2 = await Device.all(qb => qb.where('user_id', user2.id))
|
||||
const userDb1 = await User.find(user.id)
|
||||
const userDb2 = await User.find(user2.id)
|
||||
expect(devices.length).toEqual(0)
|
||||
expect(devices2.length).toEqual(1)
|
||||
expect(userDb1?.has_push_device).toEqual(false)
|
||||
expect(userDb2?.has_push_device).toEqual(true)
|
||||
const freshUser = await User.find(user.id)
|
||||
expect(freshUser?.devices?.length).toEqual(1)
|
||||
expect(freshUser?.devices?.[0].device_id).toEqual(deviceId)
|
||||
expect(freshUser?.devices?.[0].token).toEqual(token)
|
||||
expect(freshUser?.devices?.[0].app_build).toEqual('2')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue