mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 14:01:35 +00:00
4fa4bf3c78
* ENH - centralize phone configuration defaults * FIX - enforce fixed phone permissions
1102 lines
57 KiB
Lua
1102 lines
57 KiB
Lua
Bridge.Database.AfterMigration("sky_phone", function()
|
|
|
|
local photo_urls = {
|
|
city = "https://images.unsplash.com/photo-1519501025264-65ba15a82390?auto=format&fit=crop&w=1200&q=80",
|
|
car = "https://images.unsplash.com/photo-1493238792000-8113da705763?auto=format&fit=crop&w=1200&q=80",
|
|
beach = "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=1200&q=80",
|
|
portrait = "https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=800&q=80",
|
|
}
|
|
local video_url = "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
|
|
local seed_attempts = {}
|
|
|
|
local function affected_rows(result)
|
|
if type(result) == "number" then
|
|
return result
|
|
end
|
|
return type(result) == "table" and tonumber(result.affectedRows) or 0
|
|
end
|
|
|
|
local function stable_uuid(seed)
|
|
local rows = Bridge.Database.Query([[
|
|
SELECT LOWER(CONCAT(
|
|
SUBSTR(MD5(?), 1, 8), '-', SUBSTR(MD5(?), 9, 4), '-',
|
|
SUBSTR(MD5(?), 13, 4), '-', SUBSTR(MD5(?), 17, 4), '-', SUBSTR(MD5(?), 21, 12)
|
|
)) AS `id`
|
|
]], { seed, seed, seed, seed, seed })
|
|
local id = rows[1] and rows[1].id
|
|
if type(id) ~= "string" or #id ~= 36 then
|
|
error("[sky_phone] Test data could not generate a stable UUID.")
|
|
end
|
|
return id
|
|
end
|
|
|
|
local function seed_hash(seed)
|
|
local rows = Bridge.Database.Query("SELECT LEFT(MD5(?), 12) AS `value`", { seed })
|
|
local value = rows[1] and rows[1].value
|
|
if type(value) ~= "string" or #value ~= 12 then
|
|
error("[sky_phone] Test data could not generate a stable account suffix.")
|
|
end
|
|
return value
|
|
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 rows = Bridge.Database.Query("SELECT UUID() AS `id`", {})
|
|
local id = rows[1] and rows[1].id
|
|
if type(id) ~= "string" then
|
|
error("[sky_phone] Test data could not generate a database UUID.")
|
|
end
|
|
return id
|
|
end
|
|
|
|
local function ensure_account(email)
|
|
Bridge.Database.Query(
|
|
"INSERT IGNORE INTO `sky_phone_accounts` (`email`, `password`) VALUES (?, ?)",
|
|
{ email, "sky-phone-test" }
|
|
)
|
|
local rows = Bridge.Database.Query(
|
|
"SELECT `id`, `email` FROM `sky_phone_accounts` WHERE `email` = ? LIMIT 1",
|
|
{ email }
|
|
)
|
|
if not rows[1] then
|
|
error(("[sky_phone] Test data account '%s' could not be loaded."):format(email))
|
|
end
|
|
rows[1].id = tonumber(rows[1].id)
|
|
return rows[1]
|
|
end
|
|
|
|
local function ensure_media(account_id, remote_id, url, media_type)
|
|
local rows = Bridge.Database.Query([[
|
|
SELECT `id` FROM `sky_phone_media`
|
|
WHERE `account_id` = ? AND `remote_id` = ?
|
|
ORDER BY `id` LIMIT 1
|
|
]], { account_id, remote_id })
|
|
if rows[1] then
|
|
Bridge.Database.Query(
|
|
"UPDATE `sky_phone_media` SET `url` = ?, `media_type` = ? WHERE `id` = ?",
|
|
{ url, media_type, rows[1].id }
|
|
)
|
|
return tonumber(rows[1].id)
|
|
end
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_media` (`account_id`, `url`, `remote_id`, `media_type`)
|
|
VALUES (?, ?, ?, ?)
|
|
]], { account_id, url, remote_id, media_type })
|
|
rows = Bridge.Database.Query([[
|
|
SELECT `id` FROM `sky_phone_media`
|
|
WHERE `account_id` = ? AND `remote_id` = ?
|
|
ORDER BY `id` DESC LIMIT 1
|
|
]], { account_id, remote_id })
|
|
if not rows[1] then
|
|
error("[sky_phone] Test media could not be created.")
|
|
end
|
|
return tonumber(rows[1].id)
|
|
end
|
|
|
|
local function reserve_sim(owner_identifier, firstname, lastname)
|
|
local rows = Bridge.Database.Query([[
|
|
SELECT `id`, `phone_number`, `sim_type`
|
|
FROM `sky_phone_sims`
|
|
WHERE `owner_identifier` = ? AND `is_virtual` = 0
|
|
ORDER BY `created_at` LIMIT 1
|
|
]], { owner_identifier })
|
|
if rows[1] then
|
|
return rows[1]
|
|
end
|
|
|
|
local sim_id
|
|
local number = SkyPhoneSimNumber.Reserve(database_uuid, function(candidate)
|
|
if SkyPhoneCompanies.IsServiceNumber(candidate) then
|
|
return false
|
|
end
|
|
sim_id = database_uuid()
|
|
local result = Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_sims`
|
|
(`id`, `phone_number`, `sim_type`, `is_virtual`, `owner_identifier`,
|
|
`owner_firstname`, `owner_lastname`, `registered_at`)
|
|
VALUES (?, ?, 'registered', 0, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
]], { sim_id, candidate, owner_identifier, firstname, lastname })
|
|
return affected_rows(result) == 1
|
|
end, Config.Sim.NumberLength, Config.Sim.NumberPrefix)
|
|
if not number then
|
|
error("[sky_phone] Test data could not reserve a SIM number.")
|
|
end
|
|
return { id = sim_id, phone_number = number, sim_type = "registered" }
|
|
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 account = ensure_account(email_local .. "@" .. Config.Mail.Domain)
|
|
local sim = reserve_sim("sky_phone:testbot:" .. label, firstname, lastname)
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_devices` (`imei`, `account_id`, `sim_id`, `device_name`)
|
|
VALUES (?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE `account_id` = VALUES(`account_id`), `sim_id` = VALUES(`sim_id`)
|
|
]], { imei, account.id, sim.id, firstname .. "'s iFruit" })
|
|
return {
|
|
account = account,
|
|
sim = sim,
|
|
imei = imei,
|
|
name = firstname .. " " .. lastname,
|
|
}
|
|
end
|
|
|
|
local function ensure_numeric_profile(table_name, account_id)
|
|
local rows = Bridge.Database.Query(
|
|
("SELECT `id` FROM `%s` WHERE `account_id` = ? LIMIT 1"):format(table_name),
|
|
{ account_id }
|
|
)
|
|
if not rows[1] then
|
|
error(("[sky_phone] Test profile in '%s' could not be loaded."):format(table_name))
|
|
end
|
|
return tonumber(rows[1].id)
|
|
end
|
|
|
|
local function ensure_string_profile(table_name, account_id)
|
|
local rows = Bridge.Database.Query(
|
|
("SELECT `id` FROM `%s` WHERE `account_id` = ? LIMIT 1"):format(table_name),
|
|
{ account_id }
|
|
)
|
|
if not rows[1] or type(rows[1].id) ~= "string" then
|
|
error(("[sky_phone] Test profile in '%s' could not be loaded."):format(table_name))
|
|
end
|
|
return rows[1].id
|
|
end
|
|
|
|
local function seed_core(context)
|
|
local account_id = context.account.id
|
|
local bot = context.bot_one
|
|
local alarms = {
|
|
{
|
|
id = "test-weekday",
|
|
enabled = true,
|
|
time = "07:30",
|
|
note = "Phone QA",
|
|
sound = "radar",
|
|
weekdays = { 1, 2, 3, 4, 5 },
|
|
lastTriggeredMinute = nil,
|
|
},
|
|
{
|
|
id = "test-weekend",
|
|
enabled = false,
|
|
time = "10:00",
|
|
note = "Car Meet",
|
|
sound = "chimes",
|
|
weekdays = { 0, 6 },
|
|
lastTriggeredMinute = nil,
|
|
},
|
|
}
|
|
local games = {
|
|
snake = { highScore = 42, speed = "fast" },
|
|
memory = {
|
|
best = {
|
|
small = { moves = 12, timeMs = 42000 },
|
|
medium = { moves = 28, timeMs = 96000 },
|
|
},
|
|
soundEnabled = true,
|
|
},
|
|
minesweeper = {
|
|
best = { quick = { timeMs = 31000 }, classic = { timeMs = 124000 } },
|
|
elapsedMs = 0,
|
|
game = nil,
|
|
soundEnabled = true,
|
|
},
|
|
["number-merge"] = { bestScore = 8192, game = nil, highestTile = 1024, soundEnabled = true },
|
|
["tower-stack"] = { highHeight = 23, highScore = 4750, soundEnabled = true },
|
|
["sky-flappy"] = { design = "neon", highScore = 18, soundEnabled = true },
|
|
["neon-drop"] = { bestLines = 14, bestScore = 12600, soundEnabled = true },
|
|
}
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_device_data` (`device_imei`, `namespace`, `payload`)
|
|
VALUES (?, 'alarms', ?)
|
|
ON DUPLICATE KEY UPDATE `payload` = VALUES(`payload`), `revision` = `revision` + 1
|
|
]], { context.imei, json.encode(alarms) })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_device_data` (`device_imei`, `namespace`, `payload`)
|
|
VALUES (?, 'games', ?)
|
|
ON DUPLICATE KEY UPDATE `payload` = VALUES(`payload`), `revision` = `revision` + 1
|
|
]], { context.imei, json.encode(games) })
|
|
|
|
local contact_id = stable_uuid(context.key .. ":contact:alex")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_contacts`
|
|
(`id`, `contact_id`, `account_id`, `name`, `notes`, `organization`, `phone_number`, `favorite`)
|
|
VALUES (?, ?, ?, 'Alex Rivera', 'Test contact for calls and messages.', 'Downtown Cab Co.', ?, 1)
|
|
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `notes` = VALUES(`notes`),
|
|
`organization` = VALUES(`organization`), `phone_number` = VALUES(`phone_number`), `favorite` = 1
|
|
]], { contact_id, contact_id, account_id, bot.sim.phone_number })
|
|
|
|
local sms_one = stable_uuid(context.key .. ":sms:one")
|
|
local sms_two = stable_uuid(context.key .. ":sms:two")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_sms_messages`
|
|
(`id`, `sender_sim_id`, `recipient_sim_id`, `sender_number`, `recipient_number`, `body`, `read_at`, `created_at`)
|
|
VALUES (?, ?, ?, ?, ?, 'Willkommen auf dem Testserver! Alle Apps sind jetzt befüllt.', NULL,
|
|
DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 8 MINUTE))
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `read_at` = NULL
|
|
]], { sms_one, bot.sim.id, context.sim.id, bot.sim.phone_number, context.sim.phone_number })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_sms_messages`
|
|
(`id`, `sender_sim_id`, `recipient_sim_id`, `sender_number`, `recipient_number`, `body`, `read_at`, `created_at`)
|
|
VALUES (?, ?, ?, ?, ?, 'Perfekt, ich teste gerade Nachrichten und Kontakte.', CURRENT_TIMESTAMP,
|
|
DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 5 MINUTE))
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `read_at` = CURRENT_TIMESTAMP
|
|
]], { sms_two, context.sim.id, bot.sim.id, context.sim.phone_number, bot.sim.phone_number })
|
|
|
|
local call_id = stable_uuid(context.key .. ":call:missed")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_calls`
|
|
(`id`, `caller_sim_id`, `callee_sim_id`, `caller_number`, `callee_number`, `status`,
|
|
`started_at`, `ended_at`, `duration_seconds`)
|
|
VALUES (?, ?, ?, ?, ?, 'missed', DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 2 HOUR),
|
|
DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 2 HOUR), 0)
|
|
ON DUPLICATE KEY UPDATE `status` = 'missed', `duration_seconds` = 0
|
|
]], { call_id, bot.sim.id, context.sim.id, bot.sim.phone_number, context.sim.phone_number })
|
|
Bridge.Database.Query("DELETE FROM `sky_phone_call_entries` WHERE `call_id` = ? AND `account_id` = ?", { call_id, account_id })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_call_entries`
|
|
(`call_id`, `account_id`, `direction`, `status`, `other_number`, `created_at`)
|
|
VALUES (?, ?, 'incoming', 'missed', ?, DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 2 HOUR))
|
|
]], { call_id, account_id, bot.sim.phone_number })
|
|
|
|
local note_one = stable_uuid(context.key .. ":note:checklist")
|
|
local note_two = stable_uuid(context.key .. ":note:ideas")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_notes` (`id`, `account_id`, `title`, `body`, `pinned`)
|
|
VALUES (?, ?, 'Phone Test-Checkliste', 'Kontakte\nNachrichten\nAnrufe\nSocial Apps\nMarktplatz\nFirmen', 1)
|
|
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`), `body` = VALUES(`body`), `pinned` = 1
|
|
]], { note_one, account_id })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_notes` (`id`, `account_id`, `title`, `body`, `pinned`)
|
|
VALUES (?, ?, 'Ideen für später', 'DarkChat testen und eine SkyRide-Fahrt bewerten.', 0)
|
|
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`), `body` = VALUES(`body`)
|
|
]], { note_two, account_id })
|
|
|
|
local mail_id = stable_uuid(context.key .. ":mail:welcome")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_mail_messages`
|
|
(`id`, `sender_account_id`, `recipients`, `subject`, `body`, `created_at`)
|
|
VALUES (?, ?, ?, 'Willkommen beim iFruit-Test',
|
|
'Hallo! Diese Nachricht gehört zu deinem reproduzierbaren Ingame-Testdatensatz.',
|
|
DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 HOUR))
|
|
ON DUPLICATE KEY UPDATE `recipients` = VALUES(`recipients`), `subject` = VALUES(`subject`), `body` = VALUES(`body`)
|
|
]], { mail_id, bot.account.id, json.encode({ context.account.email }) })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_mail_entries` (`message_id`, `account_id`, `folder`)
|
|
VALUES (?, ?, 'inbox')
|
|
]], { mail_id, account_id })
|
|
local draft_id = stable_uuid(context.key .. ":mail:draft")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_mail_drafts` (`id`, `account_id`, `recipients`, `subject`, `body`)
|
|
VALUES (?, ?, ?, 'Entwurf: Testfeedback', 'Hier kann ich später mein Testfeedback ergänzen.')
|
|
ON DUPLICATE KEY UPDATE `recipients` = VALUES(`recipients`), `subject` = VALUES(`subject`), `body` = VALUES(`body`)
|
|
]], { draft_id, account_id, json.encode({ bot.account.email }) })
|
|
|
|
Bridge.Database.Query(
|
|
"DELETE FROM `sky_phone_bank_transactions` WHERE `owner_identifier` = ? AND `reference` LIKE 'sky-phone-test:%'",
|
|
{ context.identifier }
|
|
)
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_bank_transactions` (`owner_identifier`, `kind`, `amount`, `label`, `reference`, `created_at`)
|
|
VALUES (?, 'deposit', 2500, 'Test paycheck', 'sky-phone-test:paycheck', DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 DAY)),
|
|
(?, 'withdrawal', 85, 'Los Santos Customs', 'sky-phone-test:repair', DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 3 HOUR)),
|
|
(?, 'transfer_in', 420, 'Alex Rivera', 'sky-phone-test:transfer', DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 30 MINUTE))
|
|
]], { context.identifier, context.identifier, context.identifier })
|
|
|
|
local invoice_id = stable_uuid(context.key .. ":invoice:repair")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_billing_invoices`
|
|
(`id`, `recipient_identifier`, `issuer_identifier`, `issuer_account`, `issuer_label`,
|
|
`title`, `description`, `amount`, `currency`, `status`, `due_at`)
|
|
VALUES (?, ?, 'sky_phone:testbot:mechanic', 'society_mechanic', 'Los Santos Customs',
|
|
'Vehicle inspection', 'Test invoice for the Billing app.', 750, '$', 'open',
|
|
DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 3 DAY))
|
|
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`), `description` = VALUES(`description`),
|
|
`amount` = VALUES(`amount`), `status` = 'open', `read_at` = NULL
|
|
]], { invoice_id, context.identifier })
|
|
Bridge.Database.Query(
|
|
"DELETE FROM `sky_phone_billing_events` WHERE `invoice_id` = ? AND `note` = 'Generated by the phone test data command.'",
|
|
{ invoice_id }
|
|
)
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_billing_events` (`invoice_id`, `event`, `actor_identifier`, `note`)
|
|
VALUES (?, 'created', 'sky_phone:testbot:mechanic', 'Generated by the phone test data command.')
|
|
]], { invoice_id })
|
|
|
|
local event_id = stable_uuid(context.key .. ":calendar:meeting")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_calendar_events`
|
|
(`id`, `account_id`, `title`, `note`, `starts_at`, `ends_at`, `reminder_minutes`)
|
|
VALUES (?, ?, 'Phone QA Session', 'Alle Apps im Spiel durchtesten.',
|
|
DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 DAY), DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 25 HOUR), 30)
|
|
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`), `note` = VALUES(`note`),
|
|
`starts_at` = VALUES(`starts_at`), `ends_at` = VALUES(`ends_at`), `reminder_minutes` = 30,
|
|
`reminded_at` = NULL
|
|
]], { event_id, account_id })
|
|
|
|
local marker_one = stable_uuid(context.key .. ":marker:lsc")
|
|
local marker_two = stable_uuid(context.key .. ":marker:pier")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_map_markers` (`id`, `device_imei`, `label`, `color`, `position_x`, `position_y`, `position_z`)
|
|
VALUES (?, ?, 'Los Santos Customs', 'orange', -337.3, -136.9, 39.0)
|
|
ON DUPLICATE KEY UPDATE `label` = VALUES(`label`), `color` = VALUES(`color`),
|
|
`position_x` = VALUES(`position_x`), `position_y` = VALUES(`position_y`), `position_z` = VALUES(`position_z`)
|
|
]], { marker_one, context.imei })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_map_markers` (`id`, `device_imei`, `label`, `color`, `position_x`, `position_y`, `position_z`)
|
|
VALUES (?, ?, 'Del Perro Pier', 'blue', -1649.7, -1078.3, 13.0)
|
|
ON DUPLICATE KEY UPDATE `label` = VALUES(`label`), `color` = VALUES(`color`),
|
|
`position_x` = VALUES(`position_x`), `position_y` = VALUES(`position_y`), `position_z` = VALUES(`position_z`)
|
|
]], { marker_two, context.imei })
|
|
|
|
local song_id = stable_uuid(context.key .. ":music:song")
|
|
local playlist_id = stable_uuid(context.key .. ":music:playlist")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_music_youtube_songs` (`id`, `account_id`, `video_id`, `title`, `artist`)
|
|
VALUES (?, ?, 'dQw4w9WgXcQ', 'Never Gonna Give You Up', 'Rick Astley')
|
|
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`), `artist` = VALUES(`artist`)
|
|
]], { song_id, account_id })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_music_playlists` (`id`, `account_id`, `name`)
|
|
VALUES (?, ?, 'Test Drive Mix')
|
|
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`)
|
|
]], { playlist_id, account_id })
|
|
Bridge.Database.Query("DELETE FROM `sky_phone_music_playlist_items` WHERE `playlist_id` = ?", { playlist_id })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_music_playlist_items` (`playlist_id`, `source`, `song_id`, `position`)
|
|
VALUES (?, 'youtube', ?, 1)
|
|
]], { playlist_id, song_id })
|
|
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_radio_profiles`
|
|
(`identifier`, `history`, `settings`, `primary_frequency`, `secondary_frequency`, `badge`, `display_name`)
|
|
VALUES (?, ?, ?, 100.1, 101.5, 'QA', ?)
|
|
ON DUPLICATE KEY UPDATE `history` = VALUES(`history`), `settings` = VALUES(`settings`),
|
|
`primary_frequency` = VALUES(`primary_frequency`), `secondary_frequency` = VALUES(`secondary_frequency`)
|
|
]], {
|
|
context.identifier,
|
|
json.encode({ 100.1, 101.5, 99.9 }),
|
|
json.encode({ volume = 65, notifications = true, autoRejoin = false }),
|
|
context.player_name,
|
|
})
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_easyshare_preferences` (`device_imei`, `visibility`)
|
|
VALUES (?, 'everyone') ON DUPLICATE KEY UPDATE `visibility` = 'everyone'
|
|
]], { context.imei })
|
|
local transfer_id = stable_uuid(context.key .. ":easyshare:transfer")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_easyshare_transfers`
|
|
(`id`, `sender_imei`, `recipient_imei`, `sender_name`, `recipient_name`, `content_type`,
|
|
`payload`, `status`, `progress`, `completed_at`)
|
|
VALUES (?, ?, ?, 'Alex Rivera', ?, 'contact', ?, 'completed', 100, CURRENT_TIMESTAMP)
|
|
ON DUPLICATE KEY UPDATE `payload` = VALUES(`payload`), `status` = 'completed',
|
|
`progress` = 100, `completed_at` = CURRENT_TIMESTAMP
|
|
]], {
|
|
transfer_id,
|
|
bot.imei,
|
|
context.imei,
|
|
context.player_name,
|
|
json.encode({ name = "Mia Chen", phoneNumber = context.bot_two.sim.phone_number }),
|
|
})
|
|
end
|
|
|
|
local function seed_marketplace_and_pages(context)
|
|
local account_id = context.account.id
|
|
local bot_id = context.bot_one.account.id
|
|
local own_handle = "tester" .. tostring(account_id)
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_marketplace_profiles` (`account_id`, `display_name`, `bio`, `avatar_media_id`)
|
|
VALUES (?, ?, 'QA profile generated in game.', ?)
|
|
ON DUPLICATE KEY UPDATE `display_name` = VALUES(`display_name`), `bio` = VALUES(`bio`),
|
|
`avatar_media_id` = VALUES(`avatar_media_id`)
|
|
]], { account_id, context.player_name, context.media.user_portrait })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_marketplace_profiles` (`account_id`, `display_name`, `bio`, `avatar_media_id`)
|
|
VALUES (?, 'Alex Rivera', 'Trusted test seller.', ?)
|
|
ON DUPLICATE KEY UPDATE `display_name` = VALUES(`display_name`), `bio` = VALUES(`bio`),
|
|
`avatar_media_id` = VALUES(`avatar_media_id`)
|
|
]], { bot_id, context.media.bot_portrait })
|
|
|
|
local bot_listing = stable_uuid(context.key .. ":market:bot-listing")
|
|
local own_listing = stable_uuid(context.key .. ":market:own-listing")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_marketplace_listings`
|
|
(`id`, `seller_account_id`, `title`, `description`, `category`, `item_condition`,
|
|
`price_type`, `price`, `district`, `show_phone`, `phone_number`, `status`, `expires_at`)
|
|
VALUES (?, ?, 'Sultan RS - Test Vehicle', 'Clean test listing with negotiable price.',
|
|
'vehicles', 'very_good', 'negotiable', 42000, 'los_santos', 1, ?, 'active', DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 30 DAY))
|
|
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`), `description` = VALUES(`description`),
|
|
`status` = 'active', `expires_at` = VALUES(`expires_at`)
|
|
]], { bot_listing, bot_id, context.bot_one.sim.phone_number })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_marketplace_listings`
|
|
(`id`, `seller_account_id`, `title`, `description`, `category`, `item_condition`,
|
|
`price_type`, `price`, `district`, `status`, `expires_at`)
|
|
VALUES (?, ?, 'QA Headset', 'A listing owned by the test user.', 'electronics', 'used',
|
|
'fixed', 350, 'los_santos', 'active', DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 30 DAY))
|
|
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`), `description` = VALUES(`description`),
|
|
`status` = 'active', `expires_at` = VALUES(`expires_at`)
|
|
]], { own_listing, account_id })
|
|
local car_gradient = ("url(%s)"):format(json.encode(photo_urls.car))
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_marketplace_images` (`listing_id`, `media_id`, `gradient`, `sort_order`)
|
|
VALUES (?, ?, ?, 1)
|
|
ON DUPLICATE KEY UPDATE `media_id` = VALUES(`media_id`), `gradient` = VALUES(`gradient`)
|
|
]], { bot_listing, tostring(context.media.bot_car), car_gradient })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_marketplace_favorites` (`account_id`, `listing_id`) VALUES (?, ?)
|
|
]], { account_id, bot_listing })
|
|
local inquiry_id = stable_uuid(context.key .. ":market:inquiry")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_marketplace_inquiries`
|
|
(`id`, `listing_id`, `seller_account_id`, `buyer_account_id`, `offer_amount`,
|
|
`offer_proposer_account_id`, `offer_status`, `offer_revision`)
|
|
VALUES (?, ?, ?, ?, 40000, ?, 'pending', 1)
|
|
ON DUPLICATE KEY UPDATE `offer_amount` = 40000, `offer_proposer_account_id` = VALUES(`offer_proposer_account_id`),
|
|
`offer_status` = 'pending', `offer_revision` = 1
|
|
]], { inquiry_id, bot_listing, bot_id, account_id, account_id })
|
|
Bridge.Database.Query("DELETE FROM `sky_phone_marketplace_messages` WHERE `inquiry_id` = ?", { inquiry_id })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_marketplace_messages` (`inquiry_id`, `sender_account_id`, `body`, `read_at`)
|
|
VALUES (?, ?, 'Ist der Sultan noch verfügbar?', CURRENT_TIMESTAMP),
|
|
(?, ?, 'Ja, gerne Probefahrt in Burton.', NULL)
|
|
]], { inquiry_id, account_id, inquiry_id, bot_id })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_marketplace_offers` (`inquiry_id`, `proposer_account_id`, `amount`)
|
|
VALUES (?, ?, 40000)
|
|
]], { inquiry_id, account_id })
|
|
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_pages_profiles` (`account_id`, `handle`, `bio`, `avatar_media_id`)
|
|
VALUES (?, ?, 'Lokale Tests, Events und Angebote.', ?)
|
|
ON DUPLICATE KEY UPDATE `handle` = VALUES(`handle`), `bio` = VALUES(`bio`),
|
|
`avatar_media_id` = VALUES(`avatar_media_id`)
|
|
]], { account_id, own_handle, context.media.user_portrait })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_pages_profiles` (`account_id`, `handle`, `bio`, `avatar_media_id`)
|
|
VALUES (?, 'alex.local', 'News aus Los Santos.', ?)
|
|
ON DUPLICATE KEY UPDATE `bio` = VALUES(`bio`), `avatar_media_id` = VALUES(`avatar_media_id`)
|
|
]], { bot_id, context.media.bot_portrait })
|
|
local page_post = stable_uuid(context.key .. ":pages:post")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_pages_posts`
|
|
(`id`, `account_id`, `source_type`, `title`, `body`, `category`, `district`)
|
|
VALUES (?, ?, 'personal', 'Car Meet am Pier', 'Heute Abend findet ein offenes Test-Car-Meet statt.',
|
|
'event', 'los_santos')
|
|
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`), `body` = VALUES(`body`)
|
|
]], { page_post, bot_id })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_pages_images` (`post_id`, `media_id`, `gradient`, `sort_order`)
|
|
VALUES (?, ?, ?, 1)
|
|
ON DUPLICATE KEY UPDATE `media_id` = VALUES(`media_id`), `gradient` = VALUES(`gradient`)
|
|
]], { page_post, tostring(context.media.bot_car), car_gradient })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_pages_reactions` (`post_id`, `account_id`, `kind`)
|
|
VALUES (?, ?, 'like'), (?, ?, 'save')
|
|
]], { page_post, account_id, page_post, account_id })
|
|
end
|
|
|
|
local function seed_social_apps(context)
|
|
local account_id = context.account.id
|
|
local bot_id = context.bot_one.account.id
|
|
local bot_two_id = context.bot_two.account.id
|
|
local suffix = tostring(account_id)
|
|
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_picstagram_profiles`
|
|
(`id`, `account_id`, `handle`, `display_name`, `bio`, `avatar_media_id`)
|
|
VALUES (?, ?, ?, ?, 'Ingame QA account', ?)
|
|
ON DUPLICATE KEY UPDATE `handle` = VALUES(`handle`), `display_name` = VALUES(`display_name`),
|
|
`bio` = VALUES(`bio`), `avatar_media_id` = VALUES(`avatar_media_id`)
|
|
]], {
|
|
stable_uuid(context.key .. ":pic:user"), account_id, "tester" .. suffix, context.player_name,
|
|
context.media.user_portrait,
|
|
})
|
|
local pic_user = ensure_string_profile("sky_phone_picstagram_profiles", account_id)
|
|
local pic_bot_seed = stable_uuid("sky_phone:testbot:pic:alex")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_picstagram_profiles`
|
|
(`id`, `account_id`, `handle`, `display_name`, `bio`, `avatar_media_id`, `verified`)
|
|
VALUES (?, ?, 'alex.rivera', 'Alex Rivera', 'Cars, city lights and test content.', ?, 1)
|
|
ON DUPLICATE KEY UPDATE `display_name` = VALUES(`display_name`), `bio` = VALUES(`bio`),
|
|
`avatar_media_id` = VALUES(`avatar_media_id`), `verified` = 1
|
|
]], { pic_bot_seed, bot_id, context.media.bot_portrait })
|
|
local pic_bot = ensure_string_profile("sky_phone_picstagram_profiles", bot_id)
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_picstagram_sessions` (`device_imei`, `profile_id`)
|
|
VALUES (?, ?) ON DUPLICATE KEY UPDATE `profile_id` = VALUES(`profile_id`)
|
|
]], { context.imei, pic_user })
|
|
local pic_post = stable_uuid(context.key .. ":pic:post")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_picstagram_posts` (`id`, `profile_id`, `caption`, `location`)
|
|
VALUES (?, ?, 'Night drive through Los Santos #test', 'Vinewood')
|
|
ON DUPLICATE KEY UPDATE `caption` = VALUES(`caption`), `location` = VALUES(`location`), `status` = 'published'
|
|
]], { pic_post, pic_bot })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_picstagram_post_media` (`post_id`, `media_id`, `position`)
|
|
VALUES (?, ?, 1) ON DUPLICATE KEY UPDATE `media_id` = VALUES(`media_id`)
|
|
]], { pic_post, context.media.bot_city })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_picstagram_follows` (`follower_id`, `following_id`, `status`)
|
|
VALUES (?, ?, 'accepted')
|
|
]], { pic_user, pic_bot })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_picstagram_reactions` (`post_id`, `profile_id`, `kind`)
|
|
VALUES (?, ?, 'like'), (?, ?, 'save')
|
|
]], { pic_post, pic_user, pic_post, pic_user })
|
|
local pic_comment = stable_uuid(context.key .. ":pic:comment")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_picstagram_comments` (`id`, `post_id`, `profile_id`, `body`)
|
|
VALUES (?, ?, ?, 'Sieht richtig gut aus!')
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `status` = 'visible'
|
|
]], { pic_comment, pic_post, pic_user })
|
|
local story_id = stable_uuid(context.key .. ":pic:story")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_picstagram_stories` (`id`, `profile_id`, `media_id`, `body`, `expires_at`)
|
|
VALUES (?, ?, ?, 'Test story live', DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 24 HOUR))
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `status` = 'active', `expires_at` = VALUES(`expires_at`)
|
|
]], { story_id, pic_bot, context.media.bot_city })
|
|
local pic_activity = stable_uuid(context.key .. ":pic:activity")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_picstagram_activities` (`id`, `recipient_id`, `actor_id`, `post_id`, `kind`)
|
|
VALUES (?, ?, ?, ?, 'like')
|
|
ON DUPLICATE KEY UPDATE `read_at` = NULL
|
|
]], { pic_activity, pic_user, pic_bot, pic_post })
|
|
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_fliptok_profiles` (`account_id`, `handle`, `display_name`, `bio`)
|
|
VALUES (?, ?, ?, 'Testing every FlipTok feature.')
|
|
ON DUPLICATE KEY UPDATE `handle` = VALUES(`handle`), `display_name` = VALUES(`display_name`), `bio` = VALUES(`bio`)
|
|
]], { account_id, "tester" .. suffix, context.player_name })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_fliptok_profiles`
|
|
(`account_id`, `handle`, `display_name`, `bio`, `account_type`, `verified`)
|
|
VALUES (?, 'mia.motion', 'Mia Chen', 'Short videos from Los Santos.', 'media', 1)
|
|
ON DUPLICATE KEY UPDATE `display_name` = VALUES(`display_name`), `bio` = VALUES(`bio`), `verified` = 1
|
|
]], { bot_two_id })
|
|
local flip_user = ensure_numeric_profile("sky_phone_fliptok_profiles", account_id)
|
|
local flip_bot = ensure_numeric_profile("sky_phone_fliptok_profiles", bot_two_id)
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_fliptok_sessions` (`device_imei`, `profile_id`)
|
|
VALUES (?, ?) ON DUPLICATE KEY UPDATE `profile_id` = VALUES(`profile_id`)
|
|
]], { context.imei, flip_user })
|
|
local flip_video = stable_uuid(context.key .. ":flip:video")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_fliptok_videos`
|
|
(`id`, `profile_id`, `media_id`, `caption`, `location`, `view_count`, `share_count`)
|
|
VALUES (?, ?, ?, 'Flowers in motion #fyp #test', 'Mirror Park', 1842, 37)
|
|
ON DUPLICATE KEY UPDATE `caption` = VALUES(`caption`), `view_count` = 1842,
|
|
`share_count` = 37, `status` = 'published'
|
|
]], { flip_video, flip_bot, context.media.bot_video })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_fliptok_follows` (`follower_id`, `following_id`) VALUES (?, ?)
|
|
]], { flip_user, flip_bot })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_fliptok_reactions` (`video_id`, `profile_id`, `kind`)
|
|
VALUES (?, ?, 'like'), (?, ?, 'save')
|
|
]], { flip_video, flip_user, flip_video, flip_user })
|
|
local flip_comment = stable_uuid(context.key .. ":flip:comment")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_fliptok_comments` (`id`, `video_id`, `profile_id`, `body`)
|
|
VALUES (?, ?, ?, 'Der Test-Clip läuft flüssig.')
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `status` = 'visible'
|
|
]], { flip_comment, flip_video, flip_user })
|
|
local flip_notification = stable_uuid(context.key .. ":flip:notification")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_fliptok_notifications` (`id`, `recipient_id`, `actor_id`, `video_id`, `kind`)
|
|
VALUES (?, ?, ?, ?, 'follow') ON DUPLICATE KEY UPDATE `read_at` = NULL
|
|
]], { flip_notification, flip_user, flip_bot, flip_video })
|
|
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_feather_profiles` (`account_id`, `handle`, `display_name`, `bio`, `avatar_media_id`)
|
|
VALUES (?, ?, ?, 'Testing Feather in game.', ?)
|
|
ON DUPLICATE KEY UPDATE `handle` = VALUES(`handle`), `display_name` = VALUES(`display_name`),
|
|
`bio` = VALUES(`bio`), `avatar_media_id` = VALUES(`avatar_media_id`)
|
|
]], { account_id, "tester" .. suffix, context.player_name, context.media.user_portrait })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_feather_profiles`
|
|
(`account_id`, `handle`, `display_name`, `bio`, `avatar_media_id`, `verified`)
|
|
VALUES (?, 'alex_updates', 'Alex Rivera', 'Los Santos live updates.', ?, 1)
|
|
ON DUPLICATE KEY UPDATE `display_name` = VALUES(`display_name`), `bio` = VALUES(`bio`),
|
|
`avatar_media_id` = VALUES(`avatar_media_id`), `verified` = 1
|
|
]], { bot_id, context.media.bot_portrait })
|
|
local feather_user = ensure_numeric_profile("sky_phone_feather_profiles", account_id)
|
|
local feather_bot = ensure_numeric_profile("sky_phone_feather_profiles", bot_id)
|
|
local feather_post = stable_uuid(context.key .. ":feather:post")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_feather_posts` (`id`, `profile_id`, `body`)
|
|
VALUES (?, ?, 'Der neue iFruit-Testdatensatz ist live. #LosSantos #PhoneQA')
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `status` = 'published'
|
|
]], { feather_post, feather_bot })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_feather_hashtags` (`post_id`, `tag`)
|
|
VALUES (?, 'lossantos'), (?, 'phoneqa')
|
|
]], { feather_post, feather_post })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_feather_follows` (`follower_id`, `following_id`) VALUES (?, ?)
|
|
]], { feather_user, feather_bot })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_feather_reactions` (`post_id`, `profile_id`, `kind`)
|
|
VALUES (?, ?, 'like'), (?, ?, 'bookmark')
|
|
]], { feather_post, feather_user, feather_post, feather_user })
|
|
local feather_notification = stable_uuid(context.key .. ":feather:notification")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_feather_notifications` (`id`, `recipient_id`, `actor_id`, `post_id`, `kind`)
|
|
VALUES (?, ?, ?, ?, 'follow') ON DUPLICATE KEY UPDATE `read_at` = NULL
|
|
]], { feather_notification, feather_user, feather_bot, feather_post })
|
|
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_flare_profiles`
|
|
(`account_id`, `name`, `age`, `bio`, `gender`, `interested_in`, `min_age`, `max_age`,
|
|
`avatar`, `interests`, `looking_for`)
|
|
VALUES (?, ?, 27, 'Testing Flare conversations.', 'nonbinary', 'everyone', 21, 40, 0, ?, 'friends')
|
|
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `bio` = VALUES(`bio`), `interests` = VALUES(`interests`)
|
|
]], { account_id, context.player_name:sub(1, 32), json.encode({ "cars", "music", "gaming" }) })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_flare_profiles`
|
|
(`account_id`, `name`, `age`, `bio`, `gender`, `interested_in`, `min_age`, `max_age`,
|
|
`avatar`, `interests`, `looking_for`)
|
|
VALUES (?, 'Mia', 26, 'Coffee, sunsets and good conversations.', 'woman', 'everyone', 21, 40, 1, ?, 'friends')
|
|
ON DUPLICATE KEY UPDATE `bio` = VALUES(`bio`), `interests` = VALUES(`interests`)
|
|
]], { bot_two_id, json.encode({ "coffee", "travel", "photography" }) })
|
|
local flare_user = ensure_numeric_profile("sky_phone_flare_profiles", account_id)
|
|
local flare_bot = ensure_numeric_profile("sky_phone_flare_profiles", bot_two_id)
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_flare_profile_photos` (`profile_id`, `media_id`, `sort_order`)
|
|
VALUES (?, ?, 1), (?, ?, 1)
|
|
]], { flare_user, context.media.user_portrait, flare_bot, context.media.bot_two_portrait })
|
|
local account_a = math.min(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([[
|
|
INSERT INTO `sky_phone_flare_matches` (`id`, `account_a_id`, `account_b_id`)
|
|
VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `created_at` = `created_at`
|
|
]], { proposed_match_id, account_a, account_b })
|
|
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([[
|
|
INSERT INTO `sky_phone_flare_messages` (`id`, `match_id`, `sender_account_id`, `body`, `read_at`)
|
|
VALUES (?, ?, ?, ?, NULL)
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `read_at` = NULL
|
|
]], { flare_message, match_id, bot_two_id, flare_body })
|
|
end
|
|
|
|
local function seed_private_and_services(context)
|
|
local account_id = context.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([[
|
|
INSERT INTO `sky_phone_darkchat_profiles`
|
|
(`account_id`, `dark_id`, `invite_code`, `alias`, `avatar_seed`, `notification_mode`, `activity_visible`)
|
|
VALUES (?, ?, ?, 'NightTester', 42, 'private', 1)
|
|
ON DUPLICATE KEY UPDATE `dark_id` = VALUES(`dark_id`), `invite_code` = VALUES(`invite_code`),
|
|
`alias` = VALUES(`alias`), `notification_mode` = VALUES(`notification_mode`)
|
|
]], { account_id, user_dark_id, user_invite_code })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_darkchat_profiles`
|
|
(`account_id`, `dark_id`, `invite_code`, `alias`, `avatar_seed`, `notification_mode`, `activity_visible`)
|
|
VALUES (?, ?, ?, 'GhostAlex', 17, 'full', 1)
|
|
ON DUPLICATE KEY UPDATE `dark_id` = VALUES(`dark_id`), `invite_code` = VALUES(`invite_code`),
|
|
`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_bot = ensure_numeric_profile("sky_phone_darkchat_profiles", bot_id)
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_darkchat_contacts` (`profile_id`, `contact_profile_id`, `alias_override`)
|
|
VALUES (?, ?, 'Ghost')
|
|
]], { dark_user, dark_bot })
|
|
local conversation_id = stable_uuid(context.key .. ":dark:conversation")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_darkchat_conversations` (`id`, `disappearing_seconds`)
|
|
VALUES (?, 0) ON DUPLICATE KEY UPDATE `disappearing_seconds` = 0
|
|
]], { conversation_id })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_darkchat_members` (`conversation_id`, `profile_id`, `last_read_at`)
|
|
VALUES (?, ?, CURRENT_TIMESTAMP), (?, ?, NULL)
|
|
]], { conversation_id, dark_user, conversation_id, dark_bot })
|
|
local dark_one = stable_uuid(context.key .. ":dark:message:one")
|
|
local dark_two = stable_uuid(context.key .. ":dark:message:two")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_darkchat_messages` (`id`, `conversation_id`, `sender_profile_id`, `body`)
|
|
VALUES (?, ?, ?, 'Willkommen im privaten Testchat.')
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `deleted_for_everyone` = 0
|
|
]], { dark_one, conversation_id, dark_bot })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_darkchat_messages`
|
|
(`id`, `conversation_id`, `sender_profile_id`, `message_type`, `body`, `reactions`)
|
|
VALUES (?, ?, ?, 'emoji', '🔥', ?)
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`), `reactions` = VALUES(`reactions`)
|
|
]], { dark_two, conversation_id, dark_user, json.encode({ ["🔥"] = { dark_bot } }) })
|
|
|
|
local crew_user_seed = stable_uuid(context.key .. ":crew:user")
|
|
local crew_bot_seed = stable_uuid("sky_phone:testbot:crew:alex")
|
|
local group_id = stable_uuid(context.key .. ":crew:group")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_crewlink_profiles` (`id`, `account_id`, `username`, `active_group_id`)
|
|
VALUES (?, ?, ?, NULL)
|
|
ON DUPLICATE KEY UPDATE `username` = VALUES(`username`)
|
|
]], { crew_user_seed, account_id, ("tester%s"):format(account_id):sub(1, 20) })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_crewlink_profiles` (`id`, `account_id`, `username`, `active_group_id`)
|
|
VALUES (?, ?, 'alexcrew', NULL)
|
|
ON DUPLICATE KEY UPDATE `username` = VALUES(`username`)
|
|
]], { crew_bot_seed, bot_id })
|
|
local crew_user = ensure_string_profile("sky_phone_crewlink_profiles", account_id)
|
|
local crew_bot = ensure_string_profile("sky_phone_crewlink_profiles", bot_id)
|
|
local crew_password_salt = crew_user_seed:gsub("-", "")
|
|
local crew_bot_password_salt = crew_bot_seed:gsub("-", "")
|
|
local crew_password_pepper = tostring(Config.Server.CrewLinkPasswordPepper or "")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_crewlink_credentials` (`profile_id`, `password_hash`, `password_salt`)
|
|
VALUES (?, UNHEX(SHA2(CONCAT(?, ?, 'CrewLink123!'), 256)), ?),
|
|
(?, UNHEX(SHA2(CONCAT(?, ?, 'CrewLink123!'), 256)), ?)
|
|
ON DUPLICATE KEY UPDATE `password_hash` = VALUES(`password_hash`),
|
|
`password_salt` = VALUES(`password_salt`)
|
|
]], {
|
|
crew_user, crew_password_pepper, crew_password_salt, crew_password_salt,
|
|
crew_bot, crew_password_pepper, crew_bot_password_salt, crew_bot_password_salt,
|
|
})
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_crewlink_groups`
|
|
(`id`, `name`, `colour`, `owner_profile_id`, `invite_code`, `allow_member_pings`, `overhead_allowed`)
|
|
VALUES (?, 'Phone QA Crew', 'violet', ?, ?, 1, 1)
|
|
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `colour` = VALUES(`colour`)
|
|
]], { group_id, crew_user, ("QA%s"):format(account_id):sub(1, 12) })
|
|
Bridge.Database.Query([[
|
|
INSERT IGNORE INTO `sky_phone_crewlink_memberships` (`group_id`, `profile_id`, `role`)
|
|
VALUES (?, ?, 'owner'), (?, ?, 'member')
|
|
]], { group_id, crew_user, group_id, crew_bot })
|
|
Bridge.Database.Query(
|
|
"UPDATE `sky_phone_crewlink_profiles` SET `active_group_id` = ? WHERE `id` IN (?, ?)",
|
|
{ group_id, crew_user, crew_bot }
|
|
)
|
|
local ping_id = stable_uuid(context.key .. ":crew:ping")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_crewlink_pings`
|
|
(`id`, `group_id`, `creator_profile_id`, `type`, `label`, `position_x`, `position_y`, `position_z`, `expires_at`)
|
|
VALUES (?, ?, ?, 'meeting', 'QA Treffpunkt', -337.3, -136.9, 39.0,
|
|
DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 12 HOUR))
|
|
ON DUPLICATE KEY UPDATE `label` = VALUES(`label`), `expires_at` = VALUES(`expires_at`)
|
|
]], { ping_id, group_id, crew_bot })
|
|
|
|
local ride_user_seed = stable_uuid(context.key .. ":skyride:user")
|
|
local ride_bot_seed = stable_uuid("sky_phone:testbot:skyride:alex")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_skyride_profiles` (`id`, `owner_identifier`)
|
|
VALUES (?, ?) ON DUPLICATE KEY UPDATE `owner_identifier` = VALUES(`owner_identifier`)
|
|
]], { ride_user_seed, context.identifier })
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_skyride_profiles` (`id`, `owner_identifier`)
|
|
VALUES (?, 'sky_phone:testbot:alex') ON DUPLICATE KEY UPDATE `owner_identifier` = VALUES(`owner_identifier`)
|
|
]], { ride_bot_seed })
|
|
local ride_user_rows = Bridge.Database.Query(
|
|
"SELECT `id` FROM `sky_phone_skyride_profiles` WHERE `owner_identifier` = ? LIMIT 1",
|
|
{ context.identifier }
|
|
)
|
|
local ride_bot_rows = Bridge.Database.Query(
|
|
"SELECT `id` FROM `sky_phone_skyride_profiles` WHERE `owner_identifier` = 'sky_phone:testbot:alex' LIMIT 1",
|
|
{}
|
|
)
|
|
local ride_user = ride_user_rows[1] and ride_user_rows[1].id
|
|
local ride_bot = ride_bot_rows[1] and ride_bot_rows[1].id
|
|
if type(ride_user) ~= "string" or type(ride_bot) ~= "string" then
|
|
error("[sky_phone] Test SkyRide profiles could not be loaded.")
|
|
end
|
|
local ride_id = stable_uuid(context.key .. ":skyride:completed")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_skyride_rides`
|
|
(`id`, `passenger_profile_id`, `driver_profile_id`, `passenger_name`, `driver_name`,
|
|
`status`, `service_class`, `pickup_label`, `pickup_x`, `pickup_y`, `pickup_z`,
|
|
`destination_label`, `destination_x`, `destination_y`, `destination_z`, `distance_meters`,
|
|
`duration_seconds`, `price`, `payout_amount`, `currency`, `driver_vehicle_model`,
|
|
`driver_vehicle_color`, `driver_vehicle_plate`, `passenger_rating`, `rating_comment`,
|
|
`tip_amount`, `tip_status`, `accepted_at`, `arrived_at`, `started_at`, `completed_at`, `paid_out_at`)
|
|
VALUES (?, ?, ?, ?, 'Alex Rivera', 'completed', 'comfort', 'Legion Square', 215.8, -810.1, 30.7,
|
|
'Del Perro Pier', -1649.7, -1078.3, 13.0, 6200, 540, 320, 240, '$', 'Sultan',
|
|
'Midnight Blue', 'QA 2026', 5, 'Saubere Testfahrt.', 25, 'completed',
|
|
DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 3 HOUR), DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 170 MINUTE),
|
|
DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 165 MINUTE), DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 2 HOUR),
|
|
DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 2 HOUR))
|
|
ON DUPLICATE KEY UPDATE `status` = 'completed', `passenger_rating` = 5,
|
|
`rating_comment` = 'Saubere Testfahrt.', `tip_amount` = 25, `tip_status` = 'completed'
|
|
]], { ride_id, ride_user, ride_bot, context.player_name })
|
|
|
|
local company_rows = Bridge.Database.Query(
|
|
"SELECT `company_id` FROM `sky_phone_company_profiles` WHERE `company_id` = 'mechanic' LIMIT 1",
|
|
{}
|
|
)
|
|
if company_rows[1] then
|
|
local request_id = stable_uuid(context.key .. ":company:request")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_company_requests`
|
|
(`id`, `company_id`, `service_id`, `customer_sim_id`, `subject`, `description`,
|
|
`status`, `customer_unread`, `company_activity_revision`, `revision`)
|
|
VALUES (?, 'mechanic', 'mechanic-repair', ?, 'Motor verliert Leistung',
|
|
'Testauftrag mit Chatverlauf und Status.', 'waiting_customer', 1, 3, 3)
|
|
ON DUPLICATE KEY UPDATE `subject` = VALUES(`subject`), `description` = VALUES(`description`),
|
|
`status` = 'waiting_customer', `customer_unread` = 1, `company_activity_revision` = 3, `revision` = 3
|
|
]], { request_id, context.sim.id })
|
|
local request_message = stable_uuid(context.key .. ":company:message")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_company_request_messages`
|
|
(`id`, `request_id`, `sender_type`, `sender_identifier`, `body`)
|
|
VALUES (?, ?, 'company', 'sky_phone:testbot:mechanic',
|
|
'Bitte komm für eine Diagnose bei Los Santos Customs vorbei.')
|
|
ON DUPLICATE KEY UPDATE `body` = VALUES(`body`)
|
|
]], { request_message, request_id })
|
|
local request_event = stable_uuid(context.key .. ":company:event")
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_company_request_events`
|
|
(`id`, `request_id`, `event_type`, `actor_type`, `actor_identifier`, `from_status`, `to_status`, `detail`)
|
|
VALUES (?, ?, 'status', 'company', 'sky_phone:testbot:mechanic', 'in_progress', 'waiting_customer',
|
|
'Waiting for the test customer.')
|
|
ON DUPLICATE KEY UPDATE `detail` = VALUES(`detail`)
|
|
]], { request_event, request_id })
|
|
end
|
|
end
|
|
|
|
local function seed_for_source(source)
|
|
local slots = Bridge.Inventory.GetSlotsWithItem(source, Config.Phone.Item)
|
|
local phone_slot = slots[1]
|
|
if not phone_slot then
|
|
error(("[sky_phone] Test data source %s has no phone item."):format(source))
|
|
end
|
|
local imei, device_error = SkyPhone.EnsureDevice(source, phone_slot)
|
|
if not imei then
|
|
error(("[sky_phone] Test data could not resolve the phone device: %s"):format(tostring(device_error)))
|
|
end
|
|
local identifier = Bridge.Framework.GetIdentifier(source)
|
|
if type(identifier) ~= "string" or identifier == "" then
|
|
error("[sky_phone] Test data could not resolve the player identifier.")
|
|
end
|
|
|
|
local player_name = ((Bridge.Framework.GetFirstname(source) or "") .. " "
|
|
.. (Bridge.Framework.GetLastname(source) or "")):match("^%s*(.-)%s*$")
|
|
if player_name == "" then
|
|
player_name = GetPlayerName(source) or ("Player %s"):format(source)
|
|
end
|
|
local device = SkyPhone.LoadDevice(imei)
|
|
local account
|
|
if device and device.account_id then
|
|
local rows = Bridge.Database.Query(
|
|
"SELECT `id`, `email` FROM `sky_phone_accounts` WHERE `id` = ? LIMIT 1",
|
|
{ device.account_id }
|
|
)
|
|
account = rows[1]
|
|
if not account then
|
|
error("[sky_phone] Test data found a device with a missing Sky Cloud account.")
|
|
end
|
|
account.id = tonumber(account.id)
|
|
else
|
|
account = ensure_account("tester_" .. seed_hash(identifier) .. "@" .. Config.Mail.Domain)
|
|
Bridge.Database.Query("UPDATE `sky_phone_devices` SET `account_id` = ? WHERE `imei` = ?", { account.id, imei })
|
|
end
|
|
|
|
device = SkyPhone.LoadDevice(imei)
|
|
local sim
|
|
if device and device.sim_id then
|
|
local rows = Bridge.Database.Query("SELECT * FROM `sky_phone_sims` WHERE `id` = ? LIMIT 1", { device.sim_id })
|
|
sim = rows[1]
|
|
else
|
|
sim = reserve_sim(identifier, Bridge.Framework.GetFirstname(source), Bridge.Framework.GetLastname(source))
|
|
local previous_imei = move_sim_to_device(sim.id, imei)
|
|
local metadata = phone_slot.metadata or {}
|
|
metadata.sim_id = sim.id
|
|
metadata.phone_number = sim.phone_number
|
|
metadata.formatted_number = SkyPhoneSimNumber.Format(
|
|
sim.phone_number,
|
|
Config.Sim.NumberGroups,
|
|
Config.Sim.NumberLength,
|
|
Config.Sim.NumberPrefix
|
|
)
|
|
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.")
|
|
end
|
|
end
|
|
|
|
local bot_one = ensure_bot("alex", "phone.bot.alex", "990000000000001", "Alex", "Rivera")
|
|
local bot_two = ensure_bot("mia", "phone.bot.mia", "990000000000002", "Mia", "Chen")
|
|
local context = {
|
|
source = source,
|
|
identifier = identifier,
|
|
player_name = player_name,
|
|
account = account,
|
|
imei = imei,
|
|
sim = sim,
|
|
bot_one = bot_one,
|
|
bot_two = bot_two,
|
|
key = identifier .. ":" .. imei,
|
|
media = {},
|
|
}
|
|
context.media.user_portrait = ensure_media(account.id, "test-user-portrait", photo_urls.portrait, "photo")
|
|
context.media.user_city = ensure_media(account.id, "test-user-city", photo_urls.city, "photo")
|
|
context.media.user_video = ensure_media(account.id, "test-user-video", video_url, "video")
|
|
context.media.bot_portrait = ensure_media(bot_one.account.id, "test-bot-alex-portrait", photo_urls.car, "photo")
|
|
context.media.bot_car = ensure_media(bot_one.account.id, "test-bot-alex-car", photo_urls.car, "photo")
|
|
context.media.bot_city = ensure_media(bot_one.account.id, "test-bot-alex-city", photo_urls.city, "photo")
|
|
context.media.bot_two_portrait = ensure_media(bot_two.account.id, "test-bot-mia-portrait", photo_urls.portrait, "photo")
|
|
context.media.bot_video = ensure_media(bot_two.account.id, "test-bot-mia-video", video_url, "video")
|
|
|
|
seed_core(context)
|
|
seed_marketplace_and_pages(context)
|
|
seed_social_apps(context)
|
|
seed_private_and_services(context)
|
|
SkyPhone.RefreshSource(source)
|
|
return account.email
|
|
end
|
|
|
|
local function run_test_data_command(source)
|
|
if not Config.TestData.Enabled then
|
|
return
|
|
end
|
|
if source <= 0 then
|
|
Bridge.Debug("warn", "[sky_phone] The test data command must be run by an in-game player.")
|
|
return
|
|
end
|
|
if Config.TestData.AdminOnly and not Bridge.Framework.HasPermission(source, "phonetestdata") then
|
|
Bridge.Debug("warn", "[sky_phone] Source %s attempted to run the restricted test data command.", tostring(source))
|
|
TriggerClientEvent("sky_phone:testdata:feedback", source, false)
|
|
return
|
|
end
|
|
local now = os.time()
|
|
if seed_attempts[source] and now - seed_attempts[source] < 30 then
|
|
Bridge.Debug("warn", "[sky_phone] Source %s repeated the test data command too quickly.", tostring(source))
|
|
TriggerClientEvent("sky_phone:testdata:feedback", source, false)
|
|
return
|
|
end
|
|
seed_attempts[source] = now
|
|
local success, result = pcall(seed_for_source, source)
|
|
if not success then
|
|
Bridge.Debug("error", "[sky_phone] Test data seeding failed for source %s: %s", tostring(source), tostring(result))
|
|
TriggerClientEvent("sky_phone:testdata:feedback", source, false)
|
|
return
|
|
end
|
|
Bridge.Debug("info", "[sky_phone] Test data seeded for source %s.", tostring(source), { always = true })
|
|
TriggerClientEvent("sky_phone:testdata:feedback", source, true, result)
|
|
end
|
|
|
|
local active_test_data_command = nil
|
|
local registered_test_data_commands = {}
|
|
|
|
local function refresh_test_data_command()
|
|
active_test_data_command = Config.TestData.Enabled and Config.TestData.Command or nil
|
|
if not active_test_data_command then
|
|
return
|
|
end
|
|
if type(active_test_data_command) ~= "string" or active_test_data_command == "" then
|
|
error("[sky_phone] Config.TestData.Command must be a non-empty command name.")
|
|
end
|
|
if registered_test_data_commands[active_test_data_command] then
|
|
return
|
|
end
|
|
local command_name = active_test_data_command
|
|
registered_test_data_commands[command_name] = true
|
|
RegisterCommand(command_name, function(source)
|
|
if active_test_data_command == command_name then
|
|
run_test_data_command(source)
|
|
end
|
|
end, false)
|
|
end
|
|
|
|
refresh_test_data_command()
|
|
|
|
AddEventHandler("sky_phone:configurator:serverUpdated", refresh_test_data_command)
|
|
|
|
AddEventHandler("playerDropped", function()
|
|
seed_attempts[source] = nil
|
|
end)
|
|
|
|
end)
|