175 lines
6.3 KiB
JavaScript
175 lines
6.3 KiB
JavaScript
import { MySQLClient } from '@p42/p42modules'
|
|
|
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
|
|
export function isValidUuid(val) {
|
|
return(typeof(val) === 'string' && UUID_RE.test(val))
|
|
}
|
|
|
|
export class SimRepository {
|
|
|
|
constructor(dbPool, databases, debug = false) {
|
|
this.db = dbPool
|
|
this.guiDb = databases.guiDatabase
|
|
this.simDb = databases.simDatabase
|
|
this.debug = debug
|
|
}
|
|
|
|
#qualify(db, table) {
|
|
return(`\`${db}\`.${table}`)
|
|
}
|
|
|
|
#parseJsonObject(raw) {
|
|
let v = raw
|
|
if(v == null) return(null)
|
|
if(typeof(v) === 'string') {
|
|
try { v = JSON.parse(v) }
|
|
catch { return(null) }
|
|
}
|
|
if(typeof(v) !== 'object' || Array.isArray(v)) return(null)
|
|
return(v)
|
|
}
|
|
|
|
#parseGpsValues(raw) {
|
|
const v = this.#parseJsonObject(raw)
|
|
if(!v) return(null)
|
|
const position = v.position
|
|
const speed = v.speed ?? v.vector
|
|
if(!position || !speed) return(null)
|
|
const axes = ['x', 'y', 'z']
|
|
for(const axis of axes) {
|
|
if(typeof(position[axis]) !== 'number' || !Number.isFinite(position[axis])) return(null)
|
|
if(typeof(speed[axis]) !== 'number' || !Number.isFinite(speed[axis])) return(null)
|
|
}
|
|
return({
|
|
position: { x: position.x, y: position.y, z: position.z },
|
|
vector: { x: speed.x, y: speed.y, z: speed.z },
|
|
})
|
|
}
|
|
|
|
#parseStoreValues(raw) {
|
|
const v = this.#parseJsonObject(raw)
|
|
if(v == null) return(null)
|
|
return({ ...v })
|
|
}
|
|
|
|
async validateSimulationAccess(userUuid, simulationUuid) {
|
|
if(!isValidUuid(userUuid)) return({ ok: false, err: 'Invalid user UUID' })
|
|
if(!isValidUuid(simulationUuid)) return({ ok: false, err: 'Invalid simulation UUID' })
|
|
|
|
const rows = await MySQLClient.poolExecute(this.db, `
|
|
SELECT sim_id,
|
|
BIN_TO_UUID(sim_uuid) AS sim_uuid,
|
|
BIN_TO_UUID(sim_root_kf_uuid) AS sim_root_kf_uuid
|
|
FROM ${this.#qualify(this.simDb, 'simulations')}
|
|
INNER JOIN ${this.#qualify(this.guiDb, 'simowners')} ON own_sim_uuid = sim_uuid
|
|
INNER JOIN ${this.#qualify(this.guiDb, 'users')} ON own_usr_id = usr_id
|
|
WHERE usr_uuid = ?
|
|
AND sim_uuid = UUID_TO_BIN(?)
|
|
`, [userUuid, simulationUuid])
|
|
|
|
if(!rows.length) return({ ok: false, err: 'Simulation not found or access denied' })
|
|
|
|
const sim = rows[0]
|
|
if(!sim.sim_root_kf_uuid) return({ ok: false, err: 'Simulation has no root keyframe' })
|
|
|
|
const kfRows = await MySQLClient.poolExecute(this.db, `
|
|
SELECT ekf_uuid
|
|
FROM ${this.#qualify(this.simDb, 'edited_keyframes')}
|
|
WHERE ekf_uuid = UUID_TO_BIN(?)
|
|
`, [sim.sim_root_kf_uuid])
|
|
if(!kfRows.length) return({ ok: false, err: 'Root keyframe not found' })
|
|
|
|
return({ ok: true, sim })
|
|
}
|
|
|
|
async validateSimulationOwner(userUuid, simulationUuid) {
|
|
if(!isValidUuid(userUuid)) return({ ok: false, err: 'Invalid user UUID' })
|
|
if(!isValidUuid(simulationUuid)) return({ ok: false, err: 'Invalid simulation UUID' })
|
|
|
|
const rows = await MySQLClient.poolExecute(this.db, `
|
|
SELECT sim_id,
|
|
BIN_TO_UUID(sim_uuid) AS sim_uuid,
|
|
BIN_TO_UUID(sim_root_kf_uuid) AS sim_root_kf_uuid
|
|
FROM ${this.#qualify(this.simDb, 'simulations')}
|
|
INNER JOIN ${this.#qualify(this.guiDb, 'simowners')} ON own_sim_uuid = sim_uuid
|
|
INNER JOIN ${this.#qualify(this.guiDb, 'users')} ON own_usr_id = usr_id
|
|
WHERE usr_uuid = ?
|
|
AND sim_uuid = UUID_TO_BIN(?)
|
|
`, [userUuid, simulationUuid])
|
|
|
|
if(!rows.length) return({ ok: false, err: 'Simulation not found or access denied' })
|
|
return({ ok: true, sim: rows[0] })
|
|
}
|
|
|
|
async listSimulationOwnerUuids(simulationUuid) {
|
|
if(!isValidUuid(simulationUuid)) return({ ok: false, err: 'Invalid simulation UUID' })
|
|
|
|
const rows = await MySQLClient.poolExecute(this.db, `
|
|
SELECT BIN_TO_UUID(usr_uuid) AS owner_uuid
|
|
FROM ${this.#qualify(this.guiDb, 'simowners')}
|
|
INNER JOIN ${this.#qualify(this.guiDb, 'users')} ON own_usr_id = usr_id
|
|
WHERE own_sim_uuid = UUID_TO_BIN(?)
|
|
`, [simulationUuid])
|
|
|
|
return({
|
|
ok: true,
|
|
ownerUuids: rows.map(row => row.owner_uuid),
|
|
})
|
|
}
|
|
|
|
async listOwnerSimulations(userUuid) {
|
|
if(!isValidUuid(userUuid)) return({ ok: false, err: 'Invalid user UUID' })
|
|
|
|
const rows = await MySQLClient.poolExecute(this.db, `
|
|
SELECT BIN_TO_UUID(sim_uuid) AS simulation_id
|
|
FROM ${this.#qualify(this.simDb, 'simulations')}
|
|
INNER JOIN ${this.#qualify(this.guiDb, 'simowners')} ON own_sim_uuid = sim_uuid
|
|
INNER JOIN ${this.#qualify(this.guiDb, 'users')} ON own_usr_id = usr_id
|
|
WHERE usr_uuid = ?
|
|
ORDER BY sim_id
|
|
`, [userUuid])
|
|
|
|
return({
|
|
ok: true,
|
|
simulationIds: rows.map(row => row.simulation_id),
|
|
})
|
|
}
|
|
|
|
async loadKeyframeAgents(keyframeId) {
|
|
const rows = await MySQLClient.poolExecute(this.db, `
|
|
SELECT BIN_TO_UUID(ekfs_agent_id) AS agent_id,
|
|
ekfs_gps_values,
|
|
ekfs_store_values
|
|
FROM ${this.#qualify(this.simDb, 'edited_kf_store')}
|
|
WHERE ekfs_ekf_uuid = UUID_TO_BIN(?)
|
|
`, [keyframeId])
|
|
|
|
const agents = []
|
|
const errors = []
|
|
|
|
for(const row of rows) {
|
|
const parsed = this.#parseGpsValues(row.ekfs_gps_values)
|
|
if(!parsed) {
|
|
errors.push(`Invalid GPS values for agent ${row.agent_id}`)
|
|
continue
|
|
}
|
|
const store = this.#parseStoreValues(row.ekfs_store_values)
|
|
if(store == null) {
|
|
errors.push(`Invalid store values for agent ${row.agent_id}`)
|
|
continue
|
|
}
|
|
agents.push({
|
|
id: row.agent_id,
|
|
position: parsed.position,
|
|
vector: parsed.vector,
|
|
store,
|
|
})
|
|
}
|
|
|
|
if(errors.length) return({ ok: false, err: errors.join('; '), agents: [] })
|
|
return({ ok: true, agents })
|
|
}
|
|
|
|
}
|