Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions db/migrations/0049_add_camera_locations.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
-- Hierarchical physical locations and one primary location per camera.

-- migrate:up

CREATE TABLE camera_locations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
parent_uuid TEXT REFERENCES camera_locations(uuid) ON DELETE RESTRICT,
name TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'area',
sort_order INTEGER NOT NULL DEFAULT 0,
metadata_json TEXT NOT NULL DEFAULT '{}',
is_system INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);

CREATE UNIQUE INDEX idx_camera_locations_sibling_name
ON camera_locations(ifnull(parent_uuid, ''), name COLLATE NOCASE);

CREATE INDEX idx_camera_locations_parent
ON camera_locations(parent_uuid, sort_order, name COLLATE NOCASE);

INSERT INTO camera_locations (uuid, parent_uuid, name, type, is_system)
VALUES (
lower(
hex(randomblob(4)) || '-' ||
hex(randomblob(2)) || '-4' ||
substr(hex(randomblob(2)), 2) || '-' ||
substr('89ab', (abs(random()) % 4) + 1, 1) ||
substr(hex(randomblob(2)), 2) || '-' ||
hex(randomblob(6))
),
NULL,
'Unassigned',
'system',
1
);

ALTER TABLE streams ADD COLUMN location_uuid TEXT DEFAULT NULL
REFERENCES camera_locations(uuid) ON DELETE RESTRICT;

UPDATE streams
SET location_uuid = (SELECT uuid FROM camera_locations WHERE is_system = 1 LIMIT 1)
WHERE location_uuid IS NULL;

CREATE INDEX idx_streams_location_uuid
ON streams(location_uuid);

-- migrate:down

DROP INDEX IF EXISTS idx_streams_location_uuid;
UPDATE streams SET location_uuid = NULL;
DROP INDEX IF EXISTS idx_camera_locations_parent;
DROP INDEX IF EXISTS idx_camera_locations_sibling_name;
DROP TABLE IF EXISTS camera_locations;
SELECT 1;
1 change: 1 addition & 0 deletions include/core/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ typedef enum {
// Stream configuration structure
typedef struct {
char camera_uuid[CAMERA_UUID_STRING_SIZE]; // Immutable fleet identity
char location_uuid[CAMERA_UUID_STRING_SIZE]; // Primary physical location
char name[MAX_STREAM_NAME];
char url[MAX_URL_LENGTH];
bool enabled;
Expand Down
63 changes: 62 additions & 1 deletion include/database/db_embedded_migrations.h
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,60 @@ static const char migration_0048_down[] =
"DROP INDEX IF EXISTS idx_streams_camera_uuid;\n"
"SELECT 1;";

static const char migration_0049_up[] =
"CREATE TABLE camera_locations (\n"
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" uuid TEXT NOT NULL UNIQUE,\n"
" parent_uuid TEXT REFERENCES camera_locations(uuid) ON DELETE RESTRICT,\n"
" name TEXT NOT NULL,\n"
" type TEXT NOT NULL DEFAULT 'area',\n"
" sort_order INTEGER NOT NULL DEFAULT 0,\n"
" metadata_json TEXT NOT NULL DEFAULT '{}',\n"
" is_system INTEGER NOT NULL DEFAULT 0,\n"
" created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),\n"
" updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))\n"
");\n"
"\n"
"CREATE UNIQUE INDEX idx_camera_locations_sibling_name\n"
"ON camera_locations(ifnull(parent_uuid, ''), name COLLATE NOCASE);\n"
"\n"
"CREATE INDEX idx_camera_locations_parent\n"
"ON camera_locations(parent_uuid, sort_order, name COLLATE NOCASE);\n"
"\n"
"INSERT INTO camera_locations (uuid, parent_uuid, name, type, is_system)\n"
"VALUES (\n"
" lower(\n"
" hex(randomblob(4)) || '-' ||\n"
" hex(randomblob(2)) || '-4' ||\n"
" substr(hex(randomblob(2)), 2) || '-' ||\n"
" substr('89ab', (abs(random()) % 4) + 1, 1) ||\n"
" substr(hex(randomblob(2)), 2) || '-' ||\n"
" hex(randomblob(6))\n"
" ),\n"
" NULL,\n"
" 'Unassigned',\n"
" 'system',\n"
" 1\n"
");\n"
"\n"
"ALTER TABLE streams ADD COLUMN location_uuid TEXT DEFAULT NULL\n"
"REFERENCES camera_locations(uuid) ON DELETE RESTRICT;\n"
"\n"
"UPDATE streams\n"
"SET location_uuid = (SELECT uuid FROM camera_locations WHERE is_system = 1 LIMIT 1)\n"
"WHERE location_uuid IS NULL;\n"
"\n"
"CREATE INDEX idx_streams_location_uuid\n"
"ON streams(location_uuid);";

static const char migration_0049_down[] =
"DROP INDEX IF EXISTS idx_streams_location_uuid;\n"
"UPDATE streams SET location_uuid = NULL;\n"
"DROP INDEX IF EXISTS idx_camera_locations_parent;\n"
"DROP INDEX IF EXISTS idx_camera_locations_sibling_name;\n"
"DROP TABLE IF EXISTS camera_locations;\n"
"SELECT 1;";

static const migration_t embedded_migrations_data[] = {
{
.version = "0001",
Expand Down Expand Up @@ -1029,8 +1083,15 @@ static const migration_t embedded_migrations_data[] = {
.sql_down = migration_0048_down,
.is_embedded = true
},
{
.version = "0049",
.description = "add_camera_locations",
.sql_up = migration_0049_up,
.sql_down = migration_0049_down,
.is_embedded = true
},
};

#define EMBEDDED_MIGRATIONS_COUNT 47
#define EMBEDDED_MIGRATIONS_COUNT 48

#endif /* DB_EMBEDDED_MIGRATIONS_H */
47 changes: 47 additions & 0 deletions include/database/db_locations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#ifndef LIGHTNVR_DB_LOCATIONS_H
#define LIGHTNVR_DB_LOCATIONS_H

#include <stdint.h>

#include "core/config.h"

#define LOCATION_NAME_MAX 128
#define LOCATION_TYPE_MAX 64
#define LOCATION_METADATA_MAX 512

typedef struct {
char uuid[CAMERA_UUID_STRING_SIZE];
char parent_uuid[CAMERA_UUID_STRING_SIZE];
char name[LOCATION_NAME_MAX];
char type[LOCATION_TYPE_MAX];
char metadata_json[LOCATION_METADATA_MAX];
int sort_order;
int is_system;
int direct_child_count;
int direct_camera_count;
int64_t created_at;
int64_t updated_at;
} camera_location_t;

typedef enum {
DB_LOCATION_OK = 0,
DB_LOCATION_NOT_FOUND = -1,
DB_LOCATION_CONFLICT = -2,
DB_LOCATION_INVALID = -3,
DB_LOCATION_ERROR = -4
} db_location_result_t;

db_location_result_t db_location_get_unassigned(camera_location_t *location);
db_location_result_t db_location_get(const char *uuid,
camera_location_t *location);
int db_location_count(void);
int db_location_list(camera_location_t *locations, int max_count);
db_location_result_t db_location_create(camera_location_t *location);
db_location_result_t db_location_update(camera_location_t *location);
db_location_result_t db_location_delete(const char *uuid);
db_location_result_t db_location_assign_camera(const char *camera_uuid,
const char *location_uuid);
db_location_result_t db_location_get_for_camera(const char *camera_uuid,
camera_location_t *location);

#endif /* LIGHTNVR_DB_LOCATIONS_H */
14 changes: 14 additions & 0 deletions include/web/api_handlers_locations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef API_HANDLERS_LOCATIONS_H
#define API_HANDLERS_LOCATIONS_H

#include "web/request_response.h"

void handle_get_locations(const http_request_t *req, http_response_t *res);
void handle_post_location(const http_request_t *req, http_response_t *res);
void handle_get_location(const http_request_t *req, http_response_t *res);
void handle_put_location(const http_request_t *req, http_response_t *res);
void handle_delete_location(const http_request_t *req, http_response_t *res);
void handle_put_camera_location(const http_request_t *req,
http_response_t *res);

#endif /* API_HANDLERS_LOCATIONS_H */
7 changes: 5 additions & 2 deletions src/core/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1206,8 +1206,8 @@ int save_stream_configs(const config_t *config) {
}

if (identical) {
/* The database owns stable camera identity. Hydrate UUIDs even when
* no stream configuration write is necessary. */
/* The database owns stable camera identity and location. Hydrate
* both even when no stream configuration write is necessary. */
for (int i = 0; i < config->max_streams; i++) {
if (config->streams[i].name[0] == '\0') {
continue;
Expand All @@ -1218,6 +1218,9 @@ int save_stream_configs(const config_t *config) {
safe_strcpy(config->streams[i].camera_uuid,
db_streams[j].camera_uuid,
sizeof(config->streams[i].camera_uuid), 0);
safe_strcpy(config->streams[i].location_uuid,
db_streams[j].location_uuid,
sizeof(config->streams[i].location_uuid), 0);
break;
}
}
Expand Down
Loading
Loading