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
38 changes: 38 additions & 0 deletions db/migrations/0050_add_normalized_camera_tags.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- Stable camera tag identities and normalized many-to-many assignments.

-- migrate:up

CREATE TABLE camera_tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
label TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);

CREATE UNIQUE INDEX idx_camera_tags_label
ON camera_tags(label COLLATE NOCASE);

CREATE TABLE camera_tag_assignments (
camera_uuid TEXT NOT NULL REFERENCES streams(camera_uuid) ON DELETE CASCADE,
tag_uuid TEXT NOT NULL REFERENCES camera_tags(uuid) ON DELETE CASCADE,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
PRIMARY KEY (camera_uuid, tag_uuid)
);

CREATE INDEX idx_camera_tag_assignments_tag
ON camera_tag_assignments(tag_uuid, camera_uuid);

-- Legacy streams.tags values are backfilled by db_camera_tags_backfill_legacy()
-- immediately after migrations. The C backfill handles whitespace and arbitrary
-- tag text without depending on optional SQLite JSON extensions.

-- migrate:down

DROP INDEX IF EXISTS idx_camera_tag_assignments_tag;
DROP TABLE IF EXISTS camera_tag_assignments;
DROP INDEX IF EXISTS idx_camera_tags_label;
DROP TABLE IF EXISTS camera_tags;
SELECT 1;
58 changes: 58 additions & 0 deletions include/database/db_camera_tags.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef LIGHTNVR_DB_CAMERA_TAGS_H
#define LIGHTNVR_DB_CAMERA_TAGS_H

#include <stdint.h>
#include <sqlite3.h>

#include "core/config.h"

#define CAMERA_TAG_LABEL_MAX 256
#define CAMERA_TAG_COLOR_MAX 16
#define CAMERA_TAG_DESCRIPTION_MAX 512
#define CAMERA_TAG_MAX_ASSIGNMENTS 64

typedef struct {
char uuid[CAMERA_UUID_STRING_SIZE];
char label[CAMERA_TAG_LABEL_MAX];
char color[CAMERA_TAG_COLOR_MAX];
char description[CAMERA_TAG_DESCRIPTION_MAX];
int camera_count;
int64_t created_at;
int64_t updated_at;
} camera_tag_t;

typedef enum {
DB_CAMERA_TAG_OK = 0,
DB_CAMERA_TAG_NOT_FOUND = -1,
DB_CAMERA_TAG_CONFLICT = -2,
DB_CAMERA_TAG_INVALID = -3,
DB_CAMERA_TAG_ERROR = -4,
DB_CAMERA_TAG_LIMIT = -5
} db_camera_tag_result_t;

int db_camera_tag_count(void);
int db_camera_tag_list(camera_tag_t *tags, int max_count);
db_camera_tag_result_t db_camera_tag_get(const char *uuid, camera_tag_t *tag);
db_camera_tag_result_t db_camera_tag_create(camera_tag_t *tag);
db_camera_tag_result_t db_camera_tag_update(camera_tag_t *tag);
db_camera_tag_result_t db_camera_tag_delete(const char *uuid);
db_camera_tag_result_t db_camera_tag_merge(const char *source_uuid,
const char *target_uuid);

int db_camera_tag_list_for_camera(const char *camera_uuid, camera_tag_t *tags,
int max_count);
db_camera_tag_result_t db_camera_tag_set_for_camera(
const char *camera_uuid, const char *const *tag_uuids, int tag_count);

/* Idempotently import streams.tags into the normalized tables. Called at
* startup immediately after migrations. */
int db_camera_tags_backfill_legacy(void);

/* Compatibility bridge used by db_streams.c while the database mutex is held.
* The normalized assignments are replaced from the legacy comma-separated
* value. The caller must already own get_db_mutex(). */
int db_camera_tags_sync_legacy_by_name_locked(sqlite3 *db,
const char *stream_name,
const char *legacy_tags);

#endif /* LIGHTNVR_DB_CAMERA_TAGS_H */
40 changes: 39 additions & 1 deletion include/database/db_embedded_migrations.h
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,37 @@ static const char migration_0049_down[] =
"DROP TABLE IF EXISTS camera_locations;\n"
"SELECT 1;";

static const char migration_0050_up[] =
"CREATE TABLE camera_tags (\n"
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" uuid TEXT NOT NULL UNIQUE,\n"
" label TEXT NOT NULL,\n"
" color TEXT NOT NULL DEFAULT '',\n"
" description TEXT NOT NULL DEFAULT '',\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_tags_label\n"
"ON camera_tags(label COLLATE NOCASE);\n"
"\n"
"CREATE TABLE camera_tag_assignments (\n"
" camera_uuid TEXT NOT NULL REFERENCES streams(camera_uuid) ON DELETE CASCADE,\n"
" tag_uuid TEXT NOT NULL REFERENCES camera_tags(uuid) ON DELETE CASCADE,\n"
" created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),\n"
" PRIMARY KEY (camera_uuid, tag_uuid)\n"
");\n"
"\n"
"CREATE INDEX idx_camera_tag_assignments_tag\n"
"ON camera_tag_assignments(tag_uuid, camera_uuid);";

static const char migration_0050_down[] =
"DROP INDEX IF EXISTS idx_camera_tag_assignments_tag;\n"
"DROP TABLE IF EXISTS camera_tag_assignments;\n"
"DROP INDEX IF EXISTS idx_camera_tags_label;\n"
"DROP TABLE IF EXISTS camera_tags;\n"
"SELECT 1;";

static const migration_t embedded_migrations_data[] = {
{
.version = "0001",
Expand Down Expand Up @@ -1090,8 +1121,15 @@ static const migration_t embedded_migrations_data[] = {
.sql_down = migration_0049_down,
.is_embedded = true
},
{
.version = "0050",
.description = "add_normalized_camera_tags",
.sql_up = migration_0050_up,
.sql_down = migration_0050_down,
.is_embedded = true
},
};

#define EMBEDDED_MIGRATIONS_COUNT 48
#define EMBEDDED_MIGRATIONS_COUNT 49

#endif /* DB_EMBEDDED_MIGRATIONS_H */
18 changes: 18 additions & 0 deletions include/web/api_handlers_camera_tags.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef API_HANDLERS_CAMERA_TAGS_H
#define API_HANDLERS_CAMERA_TAGS_H

#include "web/request_response.h"

void handle_get_camera_tags(const http_request_t *req, http_response_t *res);
void handle_post_camera_tag(const http_request_t *req, http_response_t *res);
void handle_get_camera_tag(const http_request_t *req, http_response_t *res);
void handle_put_camera_tag(const http_request_t *req, http_response_t *res);
void handle_delete_camera_tag(const http_request_t *req, http_response_t *res);
void handle_post_camera_tag_merge(const http_request_t *req,
http_response_t *res);
void handle_get_camera_tag_assignments(const http_request_t *req,
http_response_t *res);
void handle_put_camera_tag_assignments(const http_request_t *req,
http_response_t *res);

#endif /* API_HANDLERS_CAMERA_TAGS_H */
Loading
Loading