Files
VoCat/internal/store/migrations.go
T
MengMengCode 0d738d4ce4 feat: add custom phone number support to card policies
- Introduced a new field `custom_phone_number` in the CardPolicy model and database schema.
- Updated the API to handle custom phone number input, including validation and normalization.
- Modified the CardPolicyPanel component to allow users to set and save a custom phone number.
- Enhanced the settings API to include the custom phone number in responses and updates.
- Added tests to ensure the correct functionality of custom phone number handling.
- Removed hardcoded environment variable for VOCAT_ADDR in service files.
2026-08-11 21:58:28 +08:00

505 lines
19 KiB
Go

package store
func migrationStatements(version int) []string {
switch version {
case 1:
return []string{
`CREATE TABLE IF NOT EXISTS admins (
id INTEGER PRIMARY KEY CHECK (id = 1),
username TEXT NOT NULL UNIQUE,
password_hash BLOB NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)`,
`CREATE TABLE IF NOT EXISTS sessions (
token_hash BLOB PRIMARY KEY,
admin_id INTEGER NOT NULL,
csrf_hash BLOB NOT NULL,
expires_at INTEGER NOT NULL,
created_at INTEGER NOT NULL,
FOREIGN KEY (admin_id) REFERENCES admins(id) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS sessions_expires_at_idx ON sessions(expires_at)`,
}
case 2:
return domainSchemaV2
case 3:
return []string{
`CREATE TABLE phone_associations (
iccid TEXT PRIMARY KEY,
device_id TEXT NOT NULL DEFAULT '',
number TEXT NOT NULL,
source TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)`,
`CREATE INDEX phone_associations_device_idx
ON phone_associations(device_id, updated_at DESC)`,
}
case 4:
return []string{
// The conversation UI is a received-message view. Keep the SMSC
// timestamp for diagnostics, but display the first local receipt time.
`UPDATE sms_messages
SET extra_json = json_set(
extra_json,
'$.service_center_timestamp_unix', message_time,
'$.received_at_unix', created_at
),
message_time = created_at,
updated_at = CAST(strftime('%s', 'now') AS INTEGER)
WHERE source = 'ims'
AND json_valid(extra_json)
AND COALESCE(json_extract(extra_json, '$.raw_tpdu'), '') <> ''`,
}
case 5:
return []string{
// Earlier IMS persistence labelled every non-delivered submission as
// accepted_by_ims, including explicit SIP/RP rejection evidence.
`UPDATE sms_messages
SET delivery_state = 'failed',
updated_at = CAST(strftime('%s', 'now') AS INTEGER)
WHERE source = 'ims'
AND direction IN ('outbound', 'sent')
AND delivery_state = 'accepted_by_ims'
AND (
LOWER(status) LIKE '%reject%'
OR LOWER(status) LIKE '%fail%'
OR LOWER(status) LIKE '%partial%'
)`,
}
case 6:
return []string{
`CREATE TABLE IF NOT EXISTS device_proxy_bindings (
device_id TEXT PRIMARY KEY,
upstream_proxy_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE,
FOREIGN KEY (upstream_proxy_id) REFERENCES upstream_proxies(id) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS device_proxy_bindings_proxy_idx
ON device_proxy_bindings(upstream_proxy_id)`,
}
case 7:
return []string{
`ALTER TABLE sms_messages
ADD COLUMN modem_imei TEXT NOT NULL DEFAULT ''`,
`UPDATE sms_messages
SET modem_imei = COALESCE((
SELECT NULLIF(d.modem_imei, '')
FROM devices d
WHERE d.id = sms_messages.device_id
), '')
WHERE modem_imei = ''`,
`DELETE FROM sms_messages
WHERE modem_imei <> '' AND message_id <> ''
AND id NOT IN (
SELECT MIN(id)
FROM sms_messages
WHERE modem_imei <> '' AND message_id <> ''
GROUP BY modem_imei, message_id
)`,
`CREATE UNIQUE INDEX IF NOT EXISTS sms_messages_hardware_external_id_idx
ON sms_messages(modem_imei, message_id)
WHERE modem_imei <> '' AND message_id <> ''`,
`CREATE INDEX IF NOT EXISTS sms_messages_hardware_thread_idx
ON sms_messages(modem_imei, imsi, peer, message_time DESC, id DESC)`,
}
case 8:
return []string{
`ALTER TABLE devices
ADD COLUMN device_type TEXT NOT NULL DEFAULT 'pcie_ec20_ec25'`,
}
case 9:
return []string{
// VoWiFi deliberately owns airplane mode. Earlier schemas treated
// these flags as mutually exclusive, which made the RF-safe state
// impossible to persist. Rebuild the table without changing rows.
`ALTER TABLE card_policies RENAME TO card_policies_v8`,
`CREATE TABLE card_policies (
iccid TEXT PRIMARY KEY,
network_enabled INTEGER NOT NULL DEFAULT 0 CHECK (network_enabled IN (0, 1)),
vowifi_enabled INTEGER NOT NULL DEFAULT 0 CHECK (vowifi_enabled IN (0, 1)),
airplane_enabled INTEGER NOT NULL DEFAULT 0 CHECK (airplane_enabled IN (0, 1)),
apn TEXT NOT NULL DEFAULT '',
ip_version TEXT NOT NULL DEFAULT '',
source TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)`,
`INSERT INTO card_policies (
iccid, network_enabled, vowifi_enabled, airplane_enabled,
apn, ip_version, source, created_at, updated_at
) SELECT
iccid, network_enabled, vowifi_enabled, airplane_enabled,
apn, ip_version, source, created_at, updated_at
FROM card_policies_v8`,
`UPDATE card_policies
SET airplane_enabled = 1, network_enabled = 0
WHERE vowifi_enabled = 1`,
`DROP TABLE card_policies_v8`,
}
case 10:
return []string{
`CREATE TABLE IF NOT EXISTS automatic_tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
device_id TEXT NOT NULL,
profile_iccid TEXT NOT NULL,
profile_aid TEXT NOT NULL DEFAULT '',
task_type TEXT NOT NULL CHECK (task_type IN ('sms', 'call', 'public_ip')),
environment TEXT NOT NULL CHECK (environment IN ('vowifi', 'cellular')),
interval_days INTEGER NOT NULL CHECK (interval_days BETWEEN 1 AND 365),
start_date TEXT NOT NULL,
run_time TEXT NOT NULL,
timezone TEXT NOT NULL DEFAULT 'Local',
payload_json TEXT NOT NULL DEFAULT '{}',
retry_count INTEGER NOT NULL DEFAULT 0 CHECK (retry_count BETWEEN 0 AND 10),
notify INTEGER NOT NULL DEFAULT 0 CHECK (notify IN (0, 1)),
next_run_at INTEGER NOT NULL,
last_run_at INTEGER NOT NULL DEFAULT 0,
last_status TEXT NOT NULL DEFAULT '',
last_error TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS automatic_tasks_due_idx ON automatic_tasks(enabled, next_run_at, id)`,
`CREATE INDEX IF NOT EXISTS automatic_tasks_device_idx ON automatic_tasks(device_id, next_run_at, id)`,
`CREATE TABLE IF NOT EXISTS automatic_task_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
device_id TEXT NOT NULL,
scheduled_at INTEGER NOT NULL,
started_at INTEGER NOT NULL DEFAULT 0,
finished_at INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL CHECK (status IN ('queued', 'running', 'success', 'failed')),
attempts INTEGER NOT NULL DEFAULT 0,
output TEXT NOT NULL DEFAULT '',
error TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (task_id) REFERENCES automatic_tasks(id) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS automatic_task_runs_task_idx ON automatic_task_runs(task_id, id DESC)`,
`CREATE INDEX IF NOT EXISTS automatic_task_runs_status_idx ON automatic_task_runs(status, id)`,
}
case 11:
return []string{
`CREATE TABLE IF NOT EXISTS sms_send_attempts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL
)`,
`CREATE INDEX IF NOT EXISTS sms_send_attempts_created_idx
ON sms_send_attempts(created_at, id)`,
}
case 12:
return []string{
`ALTER TABLE device_proxy_bindings RENAME TO device_proxy_bindings_v11`,
`CREATE TABLE device_proxy_bindings (
iccid TEXT PRIMARY KEY,
device_id TEXT NOT NULL,
profile_name TEXT NOT NULL DEFAULT '',
upstream_proxy_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE,
FOREIGN KEY (upstream_proxy_id) REFERENCES upstream_proxies(id) ON DELETE CASCADE
)`,
// A legacy device-wide binding is safe to preserve only when the
// currently observed ICCID is known. It then becomes one profile binding
// instead of leaking onto every future profile used by that device.
`INSERT OR IGNORE INTO device_proxy_bindings (
iccid, device_id, profile_name, upstream_proxy_id, created_at, updated_at
)
SELECT COALESCE(NULLIF(v.iccid, ''), NULLIF(d.iccid, '')),
b.device_id, '', b.upstream_proxy_id, b.created_at, b.updated_at
FROM device_proxy_bindings_v11 b
LEFT JOIN vowifi_runtime v ON v.device_id = b.device_id
LEFT JOIN device_runtime d ON d.device_id = b.device_id
WHERE COALESCE(NULLIF(v.iccid, ''), NULLIF(d.iccid, '')) IS NOT NULL`,
`DROP TABLE device_proxy_bindings_v11`,
`CREATE INDEX device_proxy_bindings_proxy_idx
ON device_proxy_bindings(upstream_proxy_id)`,
`CREATE INDEX device_proxy_bindings_device_idx
ON device_proxy_bindings(device_id, iccid)`,
}
case 13:
return []string{
`CREATE TABLE IF NOT EXISTS card_apn_profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
iccid TEXT NOT NULL,
apn TEXT NOT NULL,
ip_version TEXT NOT NULL DEFAULT 'IPV4V6'
CHECK (ip_version IN ('IP', 'IPV6', 'IPV4V6')),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
UNIQUE (iccid, apn, ip_version),
FOREIGN KEY (iccid) REFERENCES card_policies(iccid) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS card_apn_profiles_iccid_idx
ON card_apn_profiles(iccid, id)`,
}
case 14:
return []string{
`ALTER TABLE card_apn_profiles ADD COLUMN username TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE card_apn_profiles ADD COLUMN password TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE card_apn_profiles ADD COLUMN proxy TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE card_apn_profiles ADD COLUMN mcc TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE card_apn_profiles ADD COLUMN mnc TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE card_apn_profiles ADD COLUMN roaming_ip_version TEXT NOT NULL DEFAULT 'IP'
CHECK (roaming_ip_version IN ('IP', 'IPV6', 'IPV4V6'))`,
`ALTER TABLE card_apn_profiles ADD COLUMN auth_type TEXT NOT NULL DEFAULT 'NONE'
CHECK (auth_type IN ('NONE', 'PAP', 'CHAP', 'PAP_OR_CHAP'))`,
}
case 15:
return []string{
`ALTER TABLE card_policies
ADD COLUMN custom_phone_number TEXT NOT NULL DEFAULT ''`,
}
default:
return nil
}
}
var domainSchemaV2 = []string{
`CREATE TABLE devices (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
interface TEXT NOT NULL DEFAULT '',
control_device TEXT NOT NULL DEFAULT '',
at_port TEXT NOT NULL DEFAULT '',
usb_path TEXT NOT NULL DEFAULT '',
audio_device TEXT NOT NULL DEFAULT '',
modem_imei TEXT NOT NULL DEFAULT '',
apn TEXT NOT NULL DEFAULT '',
proxy_port INTEGER NOT NULL DEFAULT 0 CHECK (proxy_port BETWEEN 0 AND 65535),
baud_rate INTEGER NOT NULL DEFAULT 115200 CHECK (baud_rate > 0),
data_bits INTEGER NOT NULL DEFAULT 8,
stop_bits INTEGER NOT NULL DEFAULT 1,
parity TEXT NOT NULL DEFAULT 'none',
device_backend TEXT NOT NULL DEFAULT 'at',
esim_transport TEXT NOT NULL DEFAULT 'at',
qmi_use_proxy INTEGER NOT NULL DEFAULT 1 CHECK (qmi_use_proxy IN (0, 1)),
qmi_proxy_path TEXT NOT NULL DEFAULT '',
qmi_proxy_executable TEXT NOT NULL DEFAULT '',
network_enabled INTEGER NOT NULL DEFAULT 0 CHECK (network_enabled IN (0, 1)),
sms_enabled INTEGER NOT NULL DEFAULT 1 CHECK (sms_enabled IN (0, 1)),
vowifi_enabled INTEGER NOT NULL DEFAULT 0 CHECK (vowifi_enabled IN (0, 1)),
extra_json TEXT NOT NULL DEFAULT '{}',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)`,
`CREATE INDEX devices_interface_idx ON devices(interface)`,
`CREATE INDEX devices_imei_idx ON devices(modem_imei)`,
`CREATE TABLE device_runtime (
device_id TEXT PRIMARY KEY,
running INTEGER NOT NULL DEFAULT 0 CHECK (running IN (0, 1)),
healthy INTEGER NOT NULL DEFAULT 0 CHECK (healthy IN (0, 1)),
control_online INTEGER NOT NULL DEFAULT 0 CHECK (control_online IN (0, 1)),
physical_present INTEGER NOT NULL DEFAULT 0 CHECK (physical_present IN (0, 1)),
worker_running INTEGER NOT NULL DEFAULT 0 CHECK (worker_running IN (0, 1)),
data_connected INTEGER NOT NULL DEFAULT 0 CHECK (data_connected IN (0, 1)),
radio_registered INTEGER NOT NULL DEFAULT 0 CHECK (radio_registered IN (0, 1)),
network_connected INTEGER NOT NULL DEFAULT 0 CHECK (network_connected IN (0, 1)),
flight_mode INTEGER NOT NULL DEFAULT 0 CHECK (flight_mode IN (0, 1)),
lifecycle_phase TEXT NOT NULL DEFAULT '',
lifecycle_reason TEXT NOT NULL DEFAULT '',
public_ip TEXT NOT NULL DEFAULT '',
private_ip TEXT NOT NULL DEFAULT '',
operator TEXT NOT NULL DEFAULT '',
native_mcc TEXT NOT NULL DEFAULT '',
native_mnc TEXT NOT NULL DEFAULT '',
native_spn TEXT NOT NULL DEFAULT '',
network_mode TEXT NOT NULL DEFAULT '',
network_duplex TEXT NOT NULL DEFAULT '',
radio_band TEXT NOT NULL DEFAULT '',
radio_channel INTEGER NOT NULL DEFAULT 0,
signal_dbm INTEGER NOT NULL DEFAULT 0,
signal_rsrp INTEGER,
signal_rsrq INTEGER,
signal_sinr INTEGER,
imei TEXT NOT NULL DEFAULT '',
iccid TEXT NOT NULL DEFAULT '',
imsi TEXT NOT NULL DEFAULT '',
firmware TEXT NOT NULL DEFAULT '',
reg_status INTEGER NOT NULL DEFAULT 0,
reg_status_text TEXT NOT NULL DEFAULT '',
ps_attached INTEGER CHECK (ps_attached IN (0, 1)),
sim_inserted INTEGER CHECK (sim_inserted IN (0, 1)),
operating_mode INTEGER,
phone_number TEXT NOT NULL DEFAULT '',
phone_number_source TEXT NOT NULL DEFAULT '',
traffic_json TEXT NOT NULL DEFAULT '{}',
extra_json TEXT NOT NULL DEFAULT '{}',
updated_at INTEGER NOT NULL,
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE
)`,
`CREATE INDEX device_runtime_iccid_idx ON device_runtime(iccid)`,
`CREATE INDEX device_runtime_imsi_idx ON device_runtime(imsi)`,
`CREATE TABLE vowifi_runtime (
device_id TEXT PRIMARY KEY,
phase TEXT NOT NULL DEFAULT '',
dataplane_mode TEXT NOT NULL DEFAULT '',
iccid TEXT NOT NULL DEFAULT '',
imsi TEXT NOT NULL DEFAULT '',
sim_ready INTEGER NOT NULL DEFAULT 0 CHECK (sim_ready IN (0, 1)),
access_ready INTEGER NOT NULL DEFAULT 0 CHECK (access_ready IN (0, 1)),
tunnel_ready INTEGER NOT NULL DEFAULT 0 CHECK (tunnel_ready IN (0, 1)),
ims_ready INTEGER NOT NULL DEFAULT 0 CHECK (ims_ready IN (0, 1)),
sms_ready INTEGER NOT NULL DEFAULT 0 CHECK (sms_ready IN (0, 1)),
reg_status INTEGER NOT NULL DEFAULT 0,
reg_status_text TEXT NOT NULL DEFAULT '',
network_mode TEXT NOT NULL DEFAULT '',
local_phone TEXT NOT NULL DEFAULT '',
phone_number_source TEXT NOT NULL DEFAULT '',
last_error_class TEXT NOT NULL DEFAULT '',
last_error TEXT NOT NULL DEFAULT '',
last_reason TEXT NOT NULL DEFAULT '',
tunnel_json TEXT NOT NULL DEFAULT '{}',
imscore_json TEXT NOT NULL DEFAULT '{}',
smsip_json TEXT NOT NULL DEFAULT '{}',
extra_json TEXT NOT NULL DEFAULT '{}',
updated_at INTEGER NOT NULL,
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE
)`,
`CREATE INDEX vowifi_runtime_ims_ready_idx ON vowifi_runtime(ims_ready)`,
`CREATE TABLE sms_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id TEXT NOT NULL DEFAULT '',
device_id TEXT NOT NULL,
imsi TEXT NOT NULL DEFAULT '',
peer TEXT NOT NULL,
direction TEXT NOT NULL,
body TEXT NOT NULL DEFAULT '',
message_time INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT '',
source TEXT NOT NULL DEFAULT '',
parts_total INTEGER NOT NULL DEFAULT 1 CHECK (parts_total > 0),
delivery_state TEXT NOT NULL DEFAULT '',
is_read INTEGER NOT NULL DEFAULT 0 CHECK (is_read IN (0, 1)),
extra_json TEXT NOT NULL DEFAULT '{}',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)`,
`CREATE UNIQUE INDEX sms_messages_external_id_idx
ON sms_messages(device_id, message_id)
WHERE message_id <> ''`,
`CREATE INDEX sms_messages_thread_idx
ON sms_messages(device_id, imsi, peer, message_time DESC, id DESC)`,
`CREATE INDEX sms_messages_time_idx ON sms_messages(message_time DESC)`,
`CREATE TABLE local_proxy_config (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
mode TEXT NOT NULL CHECK (mode IN ('socks5', 'http')),
device_id TEXT NOT NULL,
listen_addr TEXT NOT NULL,
listen_port INTEGER NOT NULL CHECK (listen_port BETWEEN 1 AND 65535),
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
auth_enabled INTEGER NOT NULL DEFAULT 0 CHECK (auth_enabled IN (0, 1)),
username TEXT NOT NULL DEFAULT '',
password TEXT NOT NULL DEFAULT '',
extra_json TEXT NOT NULL DEFAULT '{}',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE
)`,
`CREATE INDEX local_proxy_device_idx ON local_proxy_config(device_id)`,
`CREATE TABLE upstream_proxies (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
addr TEXT NOT NULL,
username TEXT NOT NULL DEFAULT '',
password TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
extra_json TEXT NOT NULL DEFAULT '{}',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)`,
`CREATE TABLE country_rules (
country_code TEXT PRIMARY KEY,
country_name TEXT NOT NULL DEFAULT '',
upstream_proxy_id TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
extra_json TEXT NOT NULL DEFAULT '{}',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (upstream_proxy_id) REFERENCES upstream_proxies(id) ON DELETE CASCADE
)`,
`CREATE INDEX country_rules_proxy_idx ON country_rules(upstream_proxy_id)`,
`CREATE TABLE notification_settings (
channel TEXT PRIMARY KEY,
enabled INTEGER NOT NULL DEFAULT 0 CHECK (enabled IN (0, 1)),
config_json TEXT NOT NULL DEFAULT '{}',
sensitive_fields_json TEXT NOT NULL DEFAULT '[]',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)`,
`CREATE TABLE app_settings (
key TEXT PRIMARY KEY,
value_json TEXT NOT NULL,
sensitive INTEGER NOT NULL DEFAULT 0 CHECK (sensitive IN (0, 1)),
updated_at INTEGER NOT NULL
)`,
`CREATE TABLE audit_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
actor TEXT NOT NULL DEFAULT '',
action TEXT NOT NULL,
entity_type TEXT NOT NULL DEFAULT '',
entity_id TEXT NOT NULL DEFAULT '',
outcome TEXT NOT NULL DEFAULT '',
remote_addr TEXT NOT NULL DEFAULT '',
details_json TEXT NOT NULL DEFAULT '{}',
created_at INTEGER NOT NULL
)`,
`CREATE INDEX audit_events_created_idx ON audit_events(created_at DESC, id DESC)`,
`CREATE INDEX audit_events_entity_idx ON audit_events(entity_type, entity_id, created_at DESC)`,
`CREATE TABLE log_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_time INTEGER NOT NULL,
level TEXT NOT NULL,
message TEXT NOT NULL,
caller TEXT NOT NULL DEFAULT '',
fields_json TEXT NOT NULL DEFAULT '{}'
)`,
`CREATE INDEX log_events_time_idx ON log_events(event_time DESC, id DESC)`,
`CREATE INDEX log_events_level_idx ON log_events(level, event_time DESC)`,
`CREATE TABLE card_policies (
iccid TEXT PRIMARY KEY,
network_enabled INTEGER NOT NULL DEFAULT 0 CHECK (network_enabled IN (0, 1)),
vowifi_enabled INTEGER NOT NULL DEFAULT 0 CHECK (vowifi_enabled IN (0, 1)),
airplane_enabled INTEGER NOT NULL DEFAULT 0 CHECK (airplane_enabled IN (0, 1)),
apn TEXT NOT NULL DEFAULT '',
ip_version TEXT NOT NULL DEFAULT '',
source TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
CHECK (NOT (vowifi_enabled = 1 AND airplane_enabled = 1))
)`,
`CREATE TABLE traffic_buckets (
device_id TEXT NOT NULL,
bucket TEXT NOT NULL,
period_start INTEGER NOT NULL,
rx_bytes INTEGER NOT NULL DEFAULT 0 CHECK (rx_bytes >= 0),
tx_bytes INTEGER NOT NULL DEFAULT 0 CHECK (tx_bytes >= 0),
PRIMARY KEY (device_id, bucket, period_start)
)`,
`CREATE INDEX traffic_buckets_period_idx
ON traffic_buckets(bucket, period_start DESC)`,
}