mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Split device registration out of the update path
Add dedicated db::devices::register/unregister methods so registration state (is_registered, owner) is owned by the API endpoints, and make update sighting-only. This stops scanner re-sightings from wiping a device's registration. Also preserve an existing mDNS hostname instead of overwriting it on re-sighting. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
98a23aa7fd
commit
561fbdb548
+113
-8
@@ -183,18 +183,18 @@ pub fn get_summary() -> Result<DeviceSummary, DbError> {
|
||||
})
|
||||
}
|
||||
|
||||
// Records a sighting of an existing device. Registration fields (is_registered, owner) are
|
||||
// owned by register/unregister and are intentionally never touched here.
|
||||
pub fn update(device: Device) -> Result<(), DbError> {
|
||||
let conn = db::get_db_connection();
|
||||
|
||||
let mut sql = "UPDATE devices SET ipv4_address=?, last_seen=?, is_registered=?, owner=?".to_string();
|
||||
let mut sql = "UPDATE devices SET ipv4_address=?, last_seen=?".to_string();
|
||||
let mut params: Vec<rusqlite::types::Value> = vec![
|
||||
device.ipv4_address.clone().into(),
|
||||
device
|
||||
.last_seen
|
||||
.to_rfc3339_opts(chrono::SecondsFormat::Nanos, false)
|
||||
.into(),
|
||||
device.is_registered.into(),
|
||||
device.owner.clone().into(),
|
||||
];
|
||||
|
||||
// Only write the vendor (and its derived device_type) when deduced, so a sighting that
|
||||
@@ -227,6 +227,42 @@ pub fn update(device: Device) -> Result<(), DbError> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(mac_address: String, owner: String, device_type: String) -> Result<(), DbError> {
|
||||
let conn = db::get_db_connection();
|
||||
|
||||
match conn.execute(
|
||||
"UPDATE devices SET is_registered=1, owner=?1, device_type=?2 WHERE mac_address=?3",
|
||||
params![owner, device_type, mac_address],
|
||||
) {
|
||||
Ok(_) => {
|
||||
debug!("Device registered in database: {mac_address}");
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error registering device ({mac_address}) in database: {error}");
|
||||
Err(DbError::from(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unregister(mac_address: String) -> Result<(), DbError> {
|
||||
let conn = db::get_db_connection();
|
||||
|
||||
match conn.execute(
|
||||
"UPDATE devices SET is_registered=0, owner='' WHERE mac_address=?1",
|
||||
params![mac_address],
|
||||
) {
|
||||
Ok(_) => {
|
||||
debug!("Device unregistered in database: {mac_address}");
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error unregistering device ({mac_address}) in database: {error}");
|
||||
Err(DbError::from(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
@@ -392,10 +428,22 @@ mod tests {
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
// Register the device, then update it with a Device carrying the scanner defaults
|
||||
// (is_registered=false, owner=""). The update must NOT clobber the registration while
|
||||
// still refreshing sighting fields like ipv4_address and last_seen.
|
||||
register(
|
||||
"uu:tt:tt:tt:tt:aa".to_string(),
|
||||
"Grace".to_string(),
|
||||
"Phone".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let new_last_seen = Utc::now();
|
||||
let mut device = read("uu:tt:tt:tt:tt:aa".to_string()).unwrap();
|
||||
device.is_registered = true;
|
||||
device.owner = "Grace".to_string();
|
||||
device.device_type = "Phone".to_string();
|
||||
device.is_registered = false;
|
||||
device.owner = "".to_string();
|
||||
device.ipv4_address = "192.168.200.50".to_string();
|
||||
device.last_seen = new_last_seen;
|
||||
|
||||
update(device).unwrap();
|
||||
|
||||
@@ -405,9 +453,9 @@ mod tests {
|
||||
device,
|
||||
"uu:tt:tt:tt:tt:aa".to_string(),
|
||||
"Phone".to_string(),
|
||||
"192.168.200.1".to_string(),
|
||||
"192.168.200.50".to_string(),
|
||||
true,
|
||||
last_seen,
|
||||
new_last_seen,
|
||||
"Grace".to_string(),
|
||||
"Test vendor".to_string(),
|
||||
);
|
||||
@@ -521,6 +569,63 @@ mod tests {
|
||||
assert_eq!(device.device_type, "tablet".to_string());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_register() {
|
||||
tests_common::setup().await;
|
||||
|
||||
// A device with no deduced vendor must still persist the device_type chosen at registration.
|
||||
let last_seen = Utc::now();
|
||||
insert(Device::new(
|
||||
"rr:rr:rr:rr:rr:01".to_string(),
|
||||
"192.168.230.1".to_string(),
|
||||
"".to_string(),
|
||||
last_seen,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
register(
|
||||
"rr:rr:rr:rr:rr:01".to_string(),
|
||||
"Grace".to_string(),
|
||||
"Phone".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let device = read("rr:rr:rr:rr:rr:01".to_string()).unwrap();
|
||||
assert!(device.is_registered, "Device should be registered");
|
||||
assert_eq!(device.owner, "Grace".to_string());
|
||||
assert_eq!(device.device_type, "Phone".to_string());
|
||||
assert_eq!(device.vendor, "".to_string());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_unregister() {
|
||||
tests_common::setup().await;
|
||||
|
||||
let last_seen = Utc::now();
|
||||
insert(Device::new(
|
||||
"rr:rr:rr:rr:rr:02".to_string(),
|
||||
"192.168.230.2".to_string(),
|
||||
"Test vendor".to_string(),
|
||||
last_seen,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
register(
|
||||
"rr:rr:rr:rr:rr:02".to_string(),
|
||||
"Grace".to_string(),
|
||||
"Phone".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
unregister("rr:rr:rr:rr:rr:02".to_string()).unwrap();
|
||||
|
||||
let device = read("rr:rr:rr:rr:rr:02".to_string()).unwrap();
|
||||
assert!(!device.is_registered, "Device should not be registered");
|
||||
assert_eq!(device.owner, "".to_string());
|
||||
// device_type is left untouched by unregister
|
||||
assert_eq!(device.device_type, "Phone".to_string());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert() {
|
||||
tests_common::setup().await;
|
||||
|
||||
@@ -88,6 +88,11 @@ async fn process_announcement(
|
||||
match db::devices::read(mac.clone()) {
|
||||
Some(recorded) => {
|
||||
debug!("mDNS sighting of known device {mac}; updating");
|
||||
// Keep the previously stored hostname rather than overwriting it with this
|
||||
// announcement's hostname.
|
||||
if recorded.name.is_some() {
|
||||
device.name = recorded.name.clone();
|
||||
}
|
||||
if let Err(err) = db::devices::update(device.clone()) {
|
||||
error!("Failed to update mDNS device {mac}: {err}");
|
||||
return;
|
||||
|
||||
@@ -105,7 +105,7 @@ pub async fn register(Json(payload): Json<RegisterDevicePayload>) -> impl IntoRe
|
||||
payload.mac_address, payload.owner, payload.device_type
|
||||
);
|
||||
|
||||
let mut device = match db::devices::read(payload.mac_address) {
|
||||
let device = match db::devices::read(payload.mac_address.clone()) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
return (
|
||||
@@ -122,11 +122,7 @@ pub async fn register(Json(payload): Json<RegisterDevicePayload>) -> impl IntoRe
|
||||
);
|
||||
}
|
||||
|
||||
device.is_registered = true;
|
||||
device.owner = payload.owner;
|
||||
device.device_type = payload.device_type;
|
||||
|
||||
match db::devices::update(device) {
|
||||
match db::devices::register(payload.mac_address, payload.owner, payload.device_type) {
|
||||
Ok(_) => (axum::http::StatusCode::CREATED, "Device registered"),
|
||||
Err(err) => {
|
||||
error!("Error registering device in the database: {}", err);
|
||||
@@ -154,7 +150,7 @@ pub async fn register(Json(payload): Json<RegisterDevicePayload>) -> impl IntoRe
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn unregister(Path(mac_address): Path<String>) -> impl IntoResponse {
|
||||
let mut device = match db::devices::read(mac_address) {
|
||||
let device = match db::devices::read(mac_address.clone()) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
return (
|
||||
@@ -171,10 +167,7 @@ pub async fn unregister(Path(mac_address): Path<String>) -> impl IntoResponse {
|
||||
);
|
||||
}
|
||||
|
||||
device.is_registered = false;
|
||||
device.owner = "".to_string();
|
||||
|
||||
match db::devices::update(device) {
|
||||
match db::devices::unregister(mac_address) {
|
||||
Ok(_) => (axum::http::StatusCode::OK, "Device un-registered"),
|
||||
Err(err) => {
|
||||
error!("Error updating device in the database: {}", err);
|
||||
|
||||
Reference in New Issue
Block a user