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> {
|
pub fn update(device: Device) -> Result<(), DbError> {
|
||||||
let conn = db::get_db_connection();
|
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![
|
let mut params: Vec<rusqlite::types::Value> = vec![
|
||||||
device.ipv4_address.clone().into(),
|
device.ipv4_address.clone().into(),
|
||||||
device
|
device
|
||||||
.last_seen
|
.last_seen
|
||||||
.to_rfc3339_opts(chrono::SecondsFormat::Nanos, false)
|
.to_rfc3339_opts(chrono::SecondsFormat::Nanos, false)
|
||||||
.into(),
|
.into(),
|
||||||
device.is_registered.into(),
|
|
||||||
device.owner.clone().into(),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Only write the vendor (and its derived device_type) when deduced, so a sighting that
|
// 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
@@ -392,10 +428,22 @@ mod tests {
|
|||||||
))
|
))
|
||||||
.unwrap();
|
.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();
|
let mut device = read("uu:tt:tt:tt:tt:aa".to_string()).unwrap();
|
||||||
device.is_registered = true;
|
device.is_registered = false;
|
||||||
device.owner = "Grace".to_string();
|
device.owner = "".to_string();
|
||||||
device.device_type = "Phone".to_string();
|
device.ipv4_address = "192.168.200.50".to_string();
|
||||||
|
device.last_seen = new_last_seen;
|
||||||
|
|
||||||
update(device).unwrap();
|
update(device).unwrap();
|
||||||
|
|
||||||
@@ -405,9 +453,9 @@ mod tests {
|
|||||||
device,
|
device,
|
||||||
"uu:tt:tt:tt:tt:aa".to_string(),
|
"uu:tt:tt:tt:tt:aa".to_string(),
|
||||||
"Phone".to_string(),
|
"Phone".to_string(),
|
||||||
"192.168.200.1".to_string(),
|
"192.168.200.50".to_string(),
|
||||||
true,
|
true,
|
||||||
last_seen,
|
new_last_seen,
|
||||||
"Grace".to_string(),
|
"Grace".to_string(),
|
||||||
"Test vendor".to_string(),
|
"Test vendor".to_string(),
|
||||||
);
|
);
|
||||||
@@ -521,6 +569,63 @@ mod tests {
|
|||||||
assert_eq!(device.device_type, "tablet".to_string());
|
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]
|
#[tokio::test]
|
||||||
async fn test_insert() {
|
async fn test_insert() {
|
||||||
tests_common::setup().await;
|
tests_common::setup().await;
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ async fn process_announcement(
|
|||||||
match db::devices::read(mac.clone()) {
|
match db::devices::read(mac.clone()) {
|
||||||
Some(recorded) => {
|
Some(recorded) => {
|
||||||
debug!("mDNS sighting of known device {mac}; updating");
|
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()) {
|
if let Err(err) = db::devices::update(device.clone()) {
|
||||||
error!("Failed to update mDNS device {mac}: {err}");
|
error!("Failed to update mDNS device {mac}: {err}");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ pub async fn register(Json(payload): Json<RegisterDevicePayload>) -> impl IntoRe
|
|||||||
payload.mac_address, payload.owner, payload.device_type
|
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,
|
Some(value) => value,
|
||||||
None => {
|
None => {
|
||||||
return (
|
return (
|
||||||
@@ -122,11 +122,7 @@ pub async fn register(Json(payload): Json<RegisterDevicePayload>) -> impl IntoRe
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
device.is_registered = true;
|
match db::devices::register(payload.mac_address, payload.owner, payload.device_type) {
|
||||||
device.owner = payload.owner;
|
|
||||||
device.device_type = payload.device_type;
|
|
||||||
|
|
||||||
match db::devices::update(device) {
|
|
||||||
Ok(_) => (axum::http::StatusCode::CREATED, "Device registered"),
|
Ok(_) => (axum::http::StatusCode::CREATED, "Device registered"),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Error registering device in the database: {}", 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" = []))
|
security(("bearer_auth" = []))
|
||||||
)]
|
)]
|
||||||
pub async fn unregister(Path(mac_address): Path<String>) -> impl IntoResponse {
|
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,
|
Some(value) => value,
|
||||||
None => {
|
None => {
|
||||||
return (
|
return (
|
||||||
@@ -171,10 +167,7 @@ pub async fn unregister(Path(mac_address): Path<String>) -> impl IntoResponse {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
device.is_registered = false;
|
match db::devices::unregister(mac_address) {
|
||||||
device.owner = "".to_string();
|
|
||||||
|
|
||||||
match db::devices::update(device) {
|
|
||||||
Ok(_) => (axum::http::StatusCode::OK, "Device un-registered"),
|
Ok(_) => (axum::http::StatusCode::OK, "Device un-registered"),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Error updating device in the database: {}", err);
|
error!("Error updating device in the database: {}", err);
|
||||||
|
|||||||
Reference in New Issue
Block a user