mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-30 01:38:55 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7c5d0175d | |||
| 64a7bc78c6 |
@@ -17,7 +17,9 @@ describe('PhoneSetupAssistant contract', () => {
|
|||||||
expect(source).toContain('WALLPAPER_IDS')
|
expect(source).toContain('WALLPAPER_IDS')
|
||||||
expect(source).toContain('setAllAppNotifications')
|
expect(source).toContain('setAllAppNotifications')
|
||||||
expect(source).toContain('appStore.claimApp')
|
expect(source).toContain('appStore.claimApp')
|
||||||
expect(source).toContain('phone.completeSetup()')
|
expect(source).toContain('await phone.completeSetup()')
|
||||||
|
expect(source).toContain(':disabled="setupCompleteBusy"')
|
||||||
|
expect(source).toContain("phone.t('Setup.ready.saveFailed')")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('persists progress and supports resuming or moving backward', () => {
|
it('persists progress and supports resuming or moving backward', () => {
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ const passcodeLength = ref<4 | 6>(phone.security.length === 4 ? 4 : 6)
|
|||||||
const notificationsEnabled = ref(true)
|
const notificationsEnabled = ref(true)
|
||||||
const notificationSounds = ref(true)
|
const notificationSounds = ref(true)
|
||||||
const selectedApps = ref<BuiltinPhoneAppId[]>(['banking', 'garage', 'skyride'])
|
const selectedApps = ref<BuiltinPhoneAppId[]>(['banking', 'garage', 'skyride'])
|
||||||
|
const setupCompleteBusy = ref(false)
|
||||||
|
const setupCompleteError = ref('')
|
||||||
|
|
||||||
const setupApps = (
|
const setupApps = (
|
||||||
['banking', 'garage', 'skyride', 'citymarkt', 'picstagram', 'snake'] as const
|
['banking', 'garage', 'skyride', 'citymarkt', 'picstagram', 'snake'] as const
|
||||||
@@ -204,8 +206,16 @@ function choosePasscodeLength(length: 4 | 6): void {
|
|||||||
passcodeResetKey.value += 1
|
passcodeResetKey.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function finish(): void {
|
async function finish(): Promise<void> {
|
||||||
phone.completeSetup()
|
if (setupCompleteBusy.value) return
|
||||||
|
setupCompleteBusy.value = true
|
||||||
|
setupCompleteError.value = ''
|
||||||
|
const completed = await phone.completeSetup()
|
||||||
|
setupCompleteBusy.value = false
|
||||||
|
if (!completed) {
|
||||||
|
setupCompleteError.value = phone.t('Setup.ready.saveFailed')
|
||||||
|
return
|
||||||
|
}
|
||||||
emit('complete')
|
emit('complete')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,12 +804,29 @@ function skipSetupForDevelopment(): void {
|
|||||||
}}</b></span
|
}}</b></span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<SkyButton class="setup-assistant__primary" @click="finish">{{
|
<SkyButton
|
||||||
phone.t('Setup.ready.enter')
|
class="setup-assistant__primary"
|
||||||
}}</SkyButton>
|
:disabled="setupCompleteBusy"
|
||||||
|
@click="finish"
|
||||||
|
>
|
||||||
|
<SkySpinner
|
||||||
|
v-if="setupCompleteBusy"
|
||||||
|
:label="phone.t('Setup.ready.saving')"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ phone.t('Setup.ready.enter') }}</span>
|
||||||
|
</SkyButton>
|
||||||
|
<p
|
||||||
|
v-if="setupCompleteError"
|
||||||
|
class="setup-assistant__error"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
{{ setupCompleteError }}
|
||||||
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="setup-assistant__later"
|
class="setup-assistant__later"
|
||||||
|
:disabled="setupCompleteBusy"
|
||||||
@click="moveTo(0)"
|
@click="moveTo(0)"
|
||||||
>
|
>
|
||||||
{{ phone.t('Setup.ready.review') }}
|
{{ phone.t('Setup.ready.review') }}
|
||||||
|
|||||||
@@ -85,4 +85,16 @@ describe('phone inventory contracts', () => {
|
|||||||
)
|
)
|
||||||
expect(phoneServer).toContain('preferred_device_imeis[source] = imei')
|
expect(phoneServer).toContain('preferred_device_imeis[source] = imei')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps non-unique phones bound to one persistent device per character', () => {
|
||||||
|
const phoneServer = readResourceFile('source/server/phone.lua')
|
||||||
|
const migration = readResourceFile('source/server/db_migrate.lua')
|
||||||
|
|
||||||
|
expect(phoneServer).toContain('if not unique_phones then')
|
||||||
|
expect(phoneServer).toContain('return map_character_device(source, slot)')
|
||||||
|
expect(phoneServer).toContain('FROM `sky_phone_character_devices`')
|
||||||
|
expect(phoneServer).toContain('WHERE `owner_identifier` = ?')
|
||||||
|
expect(migration).toContain('name = "sky_phone_character_devices"')
|
||||||
|
expect(migration).toContain('primaryKey = "owner_identifier"')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -167,4 +167,66 @@ describe('phone device persistence scope', () => {
|
|||||||
expect(mockNuiCall).toHaveBeenCalledTimes(2)
|
expect(mockNuiCall).toHaveBeenCalledTimes(2)
|
||||||
expect(phone.deviceRevisions.widgets).toBe(1)
|
expect(phone.deviceRevisions.widgets).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('marks setup complete only after the settings save is acknowledged', async () => {
|
||||||
|
const completion = deferredResponse<{ revision: number }>()
|
||||||
|
mockNuiCall.mockReturnValueOnce(completion.promise)
|
||||||
|
const phone = usePhoneStore()
|
||||||
|
phone.open({
|
||||||
|
device: {
|
||||||
|
data: {
|
||||||
|
settings: {
|
||||||
|
payload: {
|
||||||
|
settings: { setupCompleted: false, setupStep: 9 },
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
revision: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
imei: '111',
|
||||||
|
name: 'Phone 111',
|
||||||
|
sim: null,
|
||||||
|
},
|
||||||
|
token: 'session-a',
|
||||||
|
})
|
||||||
|
|
||||||
|
const completed = phone.completeSetup()
|
||||||
|
await Promise.resolve()
|
||||||
|
|
||||||
|
expect(phone.preferences.settings.setupCompleted).toBe(false)
|
||||||
|
completion.resolve({ data: { revision: 4 }, success: true })
|
||||||
|
|
||||||
|
await expect(completed).resolves.toBe(true)
|
||||||
|
expect(phone.preferences.settings.setupCompleted).toBe(true)
|
||||||
|
expect(phone.deviceRevisions.settings).toBe(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps setup open when the completion save is rejected', async () => {
|
||||||
|
mockNuiCall.mockResolvedValueOnce({
|
||||||
|
error: 'request_failed',
|
||||||
|
success: false,
|
||||||
|
})
|
||||||
|
const phone = usePhoneStore()
|
||||||
|
phone.open({
|
||||||
|
device: {
|
||||||
|
data: {
|
||||||
|
settings: {
|
||||||
|
payload: {
|
||||||
|
settings: { setupCompleted: false, setupStep: 9 },
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
revision: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
imei: '111',
|
||||||
|
name: 'Phone 111',
|
||||||
|
sim: null,
|
||||||
|
},
|
||||||
|
token: 'session-a',
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(phone.completeSetup()).resolves.toBe(false)
|
||||||
|
expect(phone.preferences.settings.setupCompleted).toBe(false)
|
||||||
|
expect(phone.deviceRevisions.settings).toBe(3)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export type PhoneOpenPayload = {
|
|||||||
token?: string
|
token?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const namespaceQueues = new Map<string, Promise<void>>()
|
const namespaceQueues = new Map<string, Promise<boolean>>()
|
||||||
let nextPersistenceSession = 0
|
let nextPersistenceSession = 0
|
||||||
|
|
||||||
const companiesFallbackLocales = {
|
const companiesFallbackLocales = {
|
||||||
@@ -4949,6 +4949,8 @@ const defaultLocales: LocaleTree = {
|
|||||||
localOnly: 'Stored on this phone',
|
localOnly: 'Stored on this phone',
|
||||||
enter: 'Enter Sky Phone',
|
enter: 'Enter Sky Phone',
|
||||||
review: 'Review Setup',
|
review: 'Review Setup',
|
||||||
|
saveFailed: 'Setup could not be saved. Try again.',
|
||||||
|
saving: 'Saving setup',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Common: {
|
Common: {
|
||||||
@@ -5223,13 +5225,13 @@ export const usePhoneStore = defineStore('phone', {
|
|||||||
JSON.stringify(device.data.settings?.payload ?? null),
|
JSON.stringify(device.data.settings?.payload ?? null),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
saveDeviceNamespace(namespace: string, payload: unknown): void {
|
saveDeviceNamespace(namespace: string, payload: unknown): Promise<boolean> {
|
||||||
const imei = this.device?.imei
|
const imei = this.device?.imei
|
||||||
if (!imei) {
|
if (!imei) {
|
||||||
console.error(
|
console.error(
|
||||||
`[Phone persistence] Could not save ${namespace} without an active device.`,
|
`[Phone persistence] Could not save ${namespace} without an active device.`,
|
||||||
)
|
)
|
||||||
return
|
return Promise.resolve(false)
|
||||||
}
|
}
|
||||||
const generation = this.persistenceGeneration
|
const generation = this.persistenceGeneration
|
||||||
const session = this.persistenceSession
|
const session = this.persistenceSession
|
||||||
@@ -5243,7 +5245,7 @@ export const usePhoneStore = defineStore('phone', {
|
|||||||
this.deviceSessionToken === token
|
this.deviceSessionToken === token
|
||||||
const previous = namespaceQueues.get(queueKey) ?? Promise.resolve()
|
const previous = namespaceQueues.get(queueKey) ?? Promise.resolve()
|
||||||
const queued = previous.then(async () => {
|
const queued = previous.then(async () => {
|
||||||
if (!isCurrentScope()) return
|
if (!isCurrentScope()) return false
|
||||||
const response = await nuiCall<{ revision: number }>('device:save', {
|
const response = await nuiCall<{ revision: number }>('device:save', {
|
||||||
imei,
|
imei,
|
||||||
namespace,
|
namespace,
|
||||||
@@ -5258,13 +5260,21 @@ export const usePhoneStore = defineStore('phone', {
|
|||||||
Number(response.data?.revision) >= 0
|
Number(response.data?.revision) >= 0
|
||||||
) {
|
) {
|
||||||
this.deviceRevisions[namespace] = Number(response.data?.revision)
|
this.deviceRevisions[namespace] = Number(response.data?.revision)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
if (isCurrentScope()) {
|
||||||
|
console.error(
|
||||||
|
`[Phone persistence] Could not save ${namespace}: ${response.error ?? 'request_failed'}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
const tracked = queued.finally(() => {
|
const tracked = queued.finally(() => {
|
||||||
if (namespaceQueues.get(queueKey) === tracked)
|
if (namespaceQueues.get(queueKey) === tracked)
|
||||||
namespaceQueues.delete(queueKey)
|
namespaceQueues.delete(queueKey)
|
||||||
})
|
})
|
||||||
namespaceQueues.set(queueKey, tracked)
|
namespaceQueues.set(queueKey, tracked)
|
||||||
|
return tracked
|
||||||
},
|
},
|
||||||
async flushDevicePersistence(): Promise<void> {
|
async flushDevicePersistence(): Promise<void> {
|
||||||
const imei = this.device?.imei
|
const imei = this.device?.imei
|
||||||
@@ -5329,10 +5339,16 @@ export const usePhoneStore = defineStore('phone', {
|
|||||||
)
|
)
|
||||||
this.saveDeviceNamespace('settings', this.preferences)
|
this.saveDeviceNamespace('settings', this.preferences)
|
||||||
},
|
},
|
||||||
completeSetup(): void {
|
async completeSetup(): Promise<boolean> {
|
||||||
this.preferences.settings.setupCompleted = true
|
const completedPreferences = cloneJsonData(this.preferences)
|
||||||
this.preferences.settings.setupStep = PHONE_SETUP_LAST_STEP
|
completedPreferences.settings.setupCompleted = true
|
||||||
this.saveDeviceNamespace('settings', this.preferences)
|
completedPreferences.settings.setupStep = PHONE_SETUP_LAST_STEP
|
||||||
|
const saved = await this.saveDeviceNamespace(
|
||||||
|
'settings',
|
||||||
|
completedPreferences,
|
||||||
|
)
|
||||||
|
if (saved) this.preferences = completedPreferences
|
||||||
|
return saved
|
||||||
},
|
},
|
||||||
resetAfterFactoryReset(): void {
|
resetAfterFactoryReset(): void {
|
||||||
this.persistenceGeneration += 1
|
this.persistenceGeneration += 1
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
const readResourceFile = (path: string) =>
|
||||||
|
readFileSync(new URL(`../../sky_phone/${path}`, import.meta.url), 'utf8')
|
||||||
|
|
||||||
|
const config = readResourceFile('config/config.lua')
|
||||||
|
const testData = readResourceFile('source/server/testdata.lua')
|
||||||
|
|
||||||
|
describe('test data seeding contracts', () => {
|
||||||
|
it('keeps test data disabled while retaining the development phone command', () => {
|
||||||
|
expect(config).toContain('DevelopmentCommand = true')
|
||||||
|
expect(config).toContain(
|
||||||
|
'Enabled = false, -- development/test servers only',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns before registering the test-data command when disabled', () => {
|
||||||
|
expect(
|
||||||
|
testData.indexOf('if not Config.TestData.Enabled then'),
|
||||||
|
).toBeLessThan(testData.indexOf('RegisterCommand(Config.TestData.Command'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('moves an existing player SIM before attaching it to the selected phone', () => {
|
||||||
|
expect(testData).toContain('local function move_sim_to_device')
|
||||||
|
expect(testData).toContain(
|
||||||
|
'SET `sim_id` = NULL WHERE `sim_id` = ? AND `imei` <> ?',
|
||||||
|
)
|
||||||
|
expect(testData).toContain(
|
||||||
|
'SET `sim_id` = ? WHERE `imei` = ? AND `sim_id` IS NULL',
|
||||||
|
)
|
||||||
|
expect(testData).toContain(
|
||||||
|
'local previous_imei = move_sim_to_device(sim.id, imei)',
|
||||||
|
)
|
||||||
|
expect(testData).toContain(
|
||||||
|
'restore_sim_attachment(sim.id, imei, previous_imei)',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('derives distinct DarkChat identifiers from each account', () => {
|
||||||
|
expect(testData).toContain(
|
||||||
|
'local function darkchat_identifiers(account_id)',
|
||||||
|
)
|
||||||
|
expect(testData).toContain(
|
||||||
|
'local user_dark_id, user_invite_code = darkchat_identifiers(account_id)',
|
||||||
|
)
|
||||||
|
expect(testData).toContain(
|
||||||
|
'local bot_dark_id, bot_invite_code = darkchat_identifiers(bot_id)',
|
||||||
|
)
|
||||||
|
expect(testData).not.toContain("'DARK0000000001'")
|
||||||
|
expect(testData).not.toContain("'INV00000001'")
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loads the persisted Flare match before inserting its test message', () => {
|
||||||
|
const matchLookup = testData.indexOf(
|
||||||
|
'SELECT `id` FROM `sky_phone_flare_matches`',
|
||||||
|
)
|
||||||
|
const messageInsert = testData.indexOf(
|
||||||
|
'INSERT INTO `sky_phone_flare_messages`',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(matchLookup).toBeGreaterThan(-1)
|
||||||
|
expect(messageInsert).toBeGreaterThan(matchLookup)
|
||||||
|
expect(testData).toContain(
|
||||||
|
'stable_uuid("sky_phone:testdata:flare:message:" .. match_id)',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -34,7 +34,7 @@ Config.Phone = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Config.TestData = {
|
Config.TestData = {
|
||||||
Enabled = true,
|
Enabled = false, -- development/test servers only; keep disabled in production
|
||||||
Command = "phonetestdata",
|
Command = "phonetestdata",
|
||||||
AdminOnly = false, -- enable only on development servers; every run is scoped to the executing player's phone
|
AdminOnly = false, -- enable only on development servers; every run is scoped to the executing player's phone
|
||||||
AdminGroups = { "admin", "superadmin" },
|
AdminGroups = { "admin", "superadmin" },
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ Locales["de"] = {
|
|||||||
ready = {
|
ready = {
|
||||||
eyebrow = "Einrichtung abgeschlossen", title = "Willkommen, {name}", body = "Dein Sky Phone ist eingerichtet und bereit. Du kannst alles später in den Einstellungen anpassen.",
|
eyebrow = "Einrichtung abgeschlossen", title = "Willkommen, {name}", body = "Dein Sky Phone ist eingerichtet und bereit. Du kannst alles später in den Einstellungen anpassen.",
|
||||||
localOnly = "Auf diesem Handy gespeichert", enter = "Sky Phone öffnen", review = "Einrichtung prüfen",
|
localOnly = "Auf diesem Handy gespeichert", enter = "Sky Phone öffnen", review = "Einrichtung prüfen",
|
||||||
|
saving = "Einrichtung wird gespeichert", saveFailed = "Die Einrichtung konnte nicht gespeichert werden. Versuche es erneut.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Common = {
|
Common = {
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ Locales["en"] = {
|
|||||||
ready = {
|
ready = {
|
||||||
eyebrow = "Setup Complete", title = "Welcome, {name}", body = "Your Sky Phone is configured and ready. Your choices can always be refined in Settings.",
|
eyebrow = "Setup Complete", title = "Welcome, {name}", body = "Your Sky Phone is configured and ready. Your choices can always be refined in Settings.",
|
||||||
localOnly = "Stored on this phone", enter = "Enter Sky Phone", review = "Review Setup",
|
localOnly = "Stored on this phone", enter = "Enter Sky Phone", review = "Review Setup",
|
||||||
|
saving = "Saving setup", saveFailed = "Setup could not be saved. Try again.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Common = {
|
Common = {
|
||||||
|
|||||||
@@ -43,6 +43,13 @@ local function seed_hash(seed)
|
|||||||
return value
|
return value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function darkchat_identifiers(account_id)
|
||||||
|
local account_key = tostring(account_id)
|
||||||
|
local dark_id = "DC" .. seed_hash("darkchat:id:" .. account_key):upper()
|
||||||
|
local invite_code = "I" .. seed_hash("darkchat:invite:" .. account_key):sub(1, 10):upper()
|
||||||
|
return dark_id, invite_code
|
||||||
|
end
|
||||||
|
|
||||||
local function database_uuid()
|
local function database_uuid()
|
||||||
local rows = Bridge.Database.Query("SELECT UUID() AS `id`", {})
|
local rows = Bridge.Database.Query("SELECT UUID() AS `id`", {})
|
||||||
local id = rows[1] and rows[1].id
|
local id = rows[1] and rows[1].id
|
||||||
@@ -127,6 +134,71 @@ local function reserve_sim(owner_identifier, firstname, lastname)
|
|||||||
return { id = sim_id, phone_number = number, sim_type = "registered" }
|
return { id = sim_id, phone_number = number, sim_type = "registered" }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function restore_sim_attachment(sim_id, current_imei, previous_imei)
|
||||||
|
local statements = {
|
||||||
|
{
|
||||||
|
query = "UPDATE `sky_phone_devices` SET `sim_id` = NULL WHERE `imei` = ? AND `sim_id` = ?",
|
||||||
|
params = { current_imei, sim_id },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if previous_imei then
|
||||||
|
statements[#statements + 1] = {
|
||||||
|
query = "UPDATE `sky_phone_devices` SET `sim_id` = ? WHERE `imei` = ? AND `sim_id` IS NULL",
|
||||||
|
params = { sim_id, previous_imei },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if not Bridge.Database.Transaction(statements) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local rows = Bridge.Database.Query(
|
||||||
|
"SELECT `imei` FROM `sky_phone_devices` WHERE `sim_id` = ? LIMIT 1",
|
||||||
|
{ sim_id }
|
||||||
|
)
|
||||||
|
if previous_imei then
|
||||||
|
return rows[1] and rows[1].imei == previous_imei
|
||||||
|
end
|
||||||
|
return rows[1] == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function move_sim_to_device(sim_id, imei)
|
||||||
|
local rows = Bridge.Database.Query(
|
||||||
|
"SELECT `imei` FROM `sky_phone_devices` WHERE `sim_id` = ? LIMIT 1",
|
||||||
|
{ sim_id }
|
||||||
|
)
|
||||||
|
local previous_imei = rows[1] and rows[1].imei or nil
|
||||||
|
if previous_imei == imei then
|
||||||
|
return previous_imei
|
||||||
|
end
|
||||||
|
|
||||||
|
local moved = Bridge.Database.Transaction({
|
||||||
|
{
|
||||||
|
query = "UPDATE `sky_phone_devices` SET `sim_id` = NULL WHERE `sim_id` = ? AND `imei` <> ?",
|
||||||
|
params = { sim_id, imei },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query = "UPDATE `sky_phone_devices` SET `sim_id` = ? WHERE `imei` = ? AND `sim_id` IS NULL",
|
||||||
|
params = { sim_id, imei },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if not moved then
|
||||||
|
error("[sky_phone] Test data could not move the player's SIM to the selected phone.")
|
||||||
|
end
|
||||||
|
|
||||||
|
rows = Bridge.Database.Query(
|
||||||
|
"SELECT `sim_id` FROM `sky_phone_devices` WHERE `imei` = ? LIMIT 1",
|
||||||
|
{ imei }
|
||||||
|
)
|
||||||
|
if not rows[1] or rows[1].sim_id ~= sim_id then
|
||||||
|
if not restore_sim_attachment(sim_id, imei, previous_imei) then
|
||||||
|
error("[sky_phone] Test data could not verify the SIM move or restore its previous device.")
|
||||||
|
end
|
||||||
|
error("[sky_phone] Test data could not verify the SIM move.")
|
||||||
|
end
|
||||||
|
|
||||||
|
return previous_imei
|
||||||
|
end
|
||||||
|
|
||||||
local function ensure_bot(label, email_local, imei, firstname, lastname)
|
local function ensure_bot(label, email_local, imei, firstname, lastname)
|
||||||
local account = ensure_account(email_local .. "@" .. Config.Mail.Domain)
|
local account = ensure_account(email_local .. "@" .. Config.Mail.Domain)
|
||||||
local sim = reserve_sim("sky_phone:testbot:" .. label, firstname, lastname)
|
local sim = reserve_sim("sky_phone:testbot:" .. label, firstname, lastname)
|
||||||
@@ -667,36 +739,57 @@ local function seed_social_apps(context)
|
|||||||
INSERT IGNORE INTO `sky_phone_flare_profile_photos` (`profile_id`, `media_id`, `sort_order`)
|
INSERT IGNORE INTO `sky_phone_flare_profile_photos` (`profile_id`, `media_id`, `sort_order`)
|
||||||
VALUES (?, ?, 1), (?, ?, 1)
|
VALUES (?, ?, 1), (?, ?, 1)
|
||||||
]], { flare_user, context.media.user_portrait, flare_bot, context.media.bot_two_portrait })
|
]], { flare_user, context.media.user_portrait, flare_bot, context.media.bot_two_portrait })
|
||||||
local match_id = stable_uuid(context.key .. ":flare:match")
|
|
||||||
local account_a = math.min(account_id, bot_two_id)
|
local account_a = math.min(account_id, bot_two_id)
|
||||||
local account_b = math.max(account_id, bot_two_id)
|
local account_b = math.max(account_id, bot_two_id)
|
||||||
|
local proposed_match_id = stable_uuid(
|
||||||
|
("sky_phone:testdata:flare:match:%s:%s"):format(account_a, account_b)
|
||||||
|
)
|
||||||
Bridge.Database.Query([[
|
Bridge.Database.Query([[
|
||||||
INSERT INTO `sky_phone_flare_matches` (`id`, `account_a_id`, `account_b_id`)
|
INSERT INTO `sky_phone_flare_matches` (`id`, `account_a_id`, `account_b_id`)
|
||||||
VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `created_at` = `created_at`
|
VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `created_at` = `created_at`
|
||||||
]], { match_id, account_a, account_b })
|
]], { proposed_match_id, account_a, account_b })
|
||||||
local flare_message = stable_uuid(context.key .. ":flare:message")
|
local match_rows = Bridge.Database.Query([[
|
||||||
|
SELECT `id` FROM `sky_phone_flare_matches`
|
||||||
|
WHERE `account_a_id` = ? AND `account_b_id` = ? LIMIT 1
|
||||||
|
]], { account_a, account_b })
|
||||||
|
local match_id = match_rows[1] and match_rows[1].id or nil
|
||||||
|
if type(match_id) ~= "string" then
|
||||||
|
error("[sky_phone] Test data Flare match could not be loaded.")
|
||||||
|
end
|
||||||
|
local flare_body = "Hey! Bereit für einen vollständigen App-Test?"
|
||||||
|
local message_rows = Bridge.Database.Query([[
|
||||||
|
SELECT `id` FROM `sky_phone_flare_messages`
|
||||||
|
WHERE `match_id` = ? AND `sender_account_id` = ? AND `body` = ?
|
||||||
|
ORDER BY `created_at`, `id` LIMIT 1
|
||||||
|
]], { match_id, bot_two_id, flare_body })
|
||||||
|
local flare_message = message_rows[1] and message_rows[1].id
|
||||||
|
or stable_uuid("sky_phone:testdata:flare:message:" .. match_id)
|
||||||
Bridge.Database.Query([[
|
Bridge.Database.Query([[
|
||||||
INSERT INTO `sky_phone_flare_messages` (`id`, `match_id`, `sender_account_id`, `body`, `read_at`)
|
INSERT INTO `sky_phone_flare_messages` (`id`, `match_id`, `sender_account_id`, `body`, `read_at`)
|
||||||
VALUES (?, ?, ?, 'Hey! Bereit für einen vollständigen App-Test?', NULL)
|
VALUES (?, ?, ?, ?, NULL)
|
||||||
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `read_at` = NULL
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `read_at` = NULL
|
||||||
]], { flare_message, match_id, bot_two_id })
|
]], { flare_message, match_id, bot_two_id, flare_body })
|
||||||
end
|
end
|
||||||
|
|
||||||
local function seed_private_and_services(context)
|
local function seed_private_and_services(context)
|
||||||
local account_id = context.account.id
|
local account_id = context.account.id
|
||||||
local bot_id = context.bot_one.account.id
|
local bot_id = context.bot_one.account.id
|
||||||
|
local user_dark_id, user_invite_code = darkchat_identifiers(account_id)
|
||||||
|
local bot_dark_id, bot_invite_code = darkchat_identifiers(bot_id)
|
||||||
Bridge.Database.Query([[
|
Bridge.Database.Query([[
|
||||||
INSERT INTO `sky_phone_darkchat_profiles`
|
INSERT INTO `sky_phone_darkchat_profiles`
|
||||||
(`account_id`, `dark_id`, `invite_code`, `alias`, `avatar_seed`, `notification_mode`, `activity_visible`)
|
(`account_id`, `dark_id`, `invite_code`, `alias`, `avatar_seed`, `notification_mode`, `activity_visible`)
|
||||||
VALUES (?, ?, ?, 'NightTester', 42, 'private', 1)
|
VALUES (?, ?, ?, 'NightTester', 42, 'private', 1)
|
||||||
ON DUPLICATE KEY UPDATE `alias` = VALUES(`alias`), `notification_mode` = VALUES(`notification_mode`)
|
ON DUPLICATE KEY UPDATE `dark_id` = VALUES(`dark_id`), `invite_code` = VALUES(`invite_code`),
|
||||||
]], { account_id, ("DARK%010d"):format(account_id % 10000000000), ("INV%08d"):format(account_id % 100000000) })
|
`alias` = VALUES(`alias`), `notification_mode` = VALUES(`notification_mode`)
|
||||||
|
]], { account_id, user_dark_id, user_invite_code })
|
||||||
Bridge.Database.Query([[
|
Bridge.Database.Query([[
|
||||||
INSERT INTO `sky_phone_darkchat_profiles`
|
INSERT INTO `sky_phone_darkchat_profiles`
|
||||||
(`account_id`, `dark_id`, `invite_code`, `alias`, `avatar_seed`, `notification_mode`, `activity_visible`)
|
(`account_id`, `dark_id`, `invite_code`, `alias`, `avatar_seed`, `notification_mode`, `activity_visible`)
|
||||||
VALUES (?, 'DARK0000000001', 'INV00000001', 'GhostAlex', 17, 'full', 1)
|
VALUES (?, ?, ?, 'GhostAlex', 17, 'full', 1)
|
||||||
ON DUPLICATE KEY UPDATE `alias` = VALUES(`alias`)
|
ON DUPLICATE KEY UPDATE `dark_id` = VALUES(`dark_id`), `invite_code` = VALUES(`invite_code`),
|
||||||
]], { bot_id })
|
`alias` = VALUES(`alias`)
|
||||||
|
]], { bot_id, bot_dark_id, bot_invite_code })
|
||||||
local dark_user = ensure_numeric_profile("sky_phone_darkchat_profiles", account_id)
|
local dark_user = ensure_numeric_profile("sky_phone_darkchat_profiles", account_id)
|
||||||
local dark_bot = ensure_numeric_profile("sky_phone_darkchat_profiles", bot_id)
|
local dark_bot = ensure_numeric_profile("sky_phone_darkchat_profiles", bot_id)
|
||||||
Bridge.Database.Query([[
|
Bridge.Database.Query([[
|
||||||
@@ -897,7 +990,7 @@ local function seed_for_source(source)
|
|||||||
sim = rows[1]
|
sim = rows[1]
|
||||||
else
|
else
|
||||||
sim = reserve_sim(identifier, Bridge.Framework.GetFirstname(source), Bridge.Framework.GetLastname(source))
|
sim = reserve_sim(identifier, Bridge.Framework.GetFirstname(source), Bridge.Framework.GetLastname(source))
|
||||||
Bridge.Database.Query("UPDATE `sky_phone_devices` SET `sim_id` = ? WHERE `imei` = ?", { sim.id, imei })
|
local previous_imei = move_sim_to_device(sim.id, imei)
|
||||||
local metadata = phone_slot.metadata or {}
|
local metadata = phone_slot.metadata or {}
|
||||||
metadata.sim_id = sim.id
|
metadata.sim_id = sim.id
|
||||||
metadata.phone_number = sim.phone_number
|
metadata.phone_number = sim.phone_number
|
||||||
@@ -908,6 +1001,11 @@ local function seed_for_source(source)
|
|||||||
Config.Sim.NumberPrefix
|
Config.Sim.NumberPrefix
|
||||||
)
|
)
|
||||||
if not Bridge.Inventory.SetSlotMetadata(source, phone_slot.slot, metadata) then
|
if not Bridge.Inventory.SetSlotMetadata(source, phone_slot.slot, metadata) then
|
||||||
|
if not restore_sim_attachment(sim.id, imei, previous_imei) then
|
||||||
|
error(
|
||||||
|
"[sky_phone] Test data could not update the phone item's SIM metadata or restore its previous device."
|
||||||
|
)
|
||||||
|
end
|
||||||
error("[sky_phone] Test data could not update the phone item's SIM metadata.")
|
error("[sky_phone] Test data could not update the phone item's SIM metadata.")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user