@trafficgroup/knex-rel 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,199 @@
1
+ # Database Schema Analysis - Cameras Table Implementation
2
+
3
+ ## Current Schema Analysis
4
+
5
+ ### 1. Videos Table (Current Structure)
6
+
7
+ - **Table Name**: `video`
8
+ - **Primary Key**: `id` (auto-increment number)
9
+ - **UUID**: `uuid` (unique, not null) - for external references
10
+ - **Foreign Keys**:
11
+ - `folderId`: references `folders.id` (CASCADE delete)
12
+ - `cameraId`: references `cameras.id` (SET NULL on delete) ✅ **ALREADY EXISTS**
13
+ - `annotationSourceId`: self-reference to `video.id` (SET NULL)
14
+ - **Key Fields**:
15
+ - `videoLocation`: S3 path/URL for video file
16
+ - `videoRate`: frame rate (FPS)
17
+ - `videoType`: enum ['TMC', 'ATR', 'JUNCTION', 'ROUNDABOUT', 'PATHWAY']
18
+ - `status`: enum ['QUEUED', 'PROCESSING', 'COMPLETED', 'FAILED', 'PENDING']
19
+ - `metadata`: JSONB for lane annotations
20
+ - `results`: JSONB for processing results
21
+ - **Timestamps**: `created_at`, `updated_at`
22
+ - **Indexes**:
23
+ - `uuid` (unique)
24
+ - `cameraId`
25
+ - `annotationSourceId`
26
+ - Composite: `(folderId, videoType, status)`
27
+
28
+ ### 2. Folders Table (Current Structure)
29
+
30
+ - **Table Name**: `folders`
31
+ - **Primary Key**: `id` (auto-increment number)
32
+ - **UUID**: `uuid` (unique, not null) - for external references
33
+ - **Foreign Keys**:
34
+ - `createdBy`: references `user.id` (CASCADE delete)
35
+ - `studyId`: references `study.id` (CASCADE delete)
36
+ - `cameraId`: references `cameras.id` (SET NULL on delete) ✅ **ALREADY EXISTS**
37
+ - **Key Fields**:
38
+ - `name`: folder name
39
+ - `status`: enum ['UPLOADING', 'COMPLETE']
40
+ - **Timestamps**: `created_at`, `updated_at`
41
+ - **Indexes**:
42
+ - `uuid` (unique)
43
+ - `cameraId`
44
+
45
+ ### 3. Cameras Table (Current Structure) ✅ **ALREADY IMPLEMENTED**
46
+
47
+ - **Table Name**: `cameras`
48
+ - **Primary Key**: `id` (auto-increment number)
49
+ - **UUID**: `uuid` (unique, not null) - for external references
50
+ - **Fields**:
51
+ - `name`: string(100), not null
52
+ - `longitude`: decimal(10,7), not null
53
+ - `latitude`: decimal(10,7), not null
54
+ - **Timestamps**: `created_at`, `updated_at`
55
+ - **Indexes**:
56
+ - `uuid` (unique)
57
+ - Composite: `(longitude, latitude)` for geospatial queries
58
+
59
+ ## Relationship Analysis
60
+
61
+ ### Current Relationships ✅ **FULLY IMPLEMENTED**
62
+
63
+ 1. **Study → Folders** (One-to-Many)
64
+ - Foreign key: `folders.studyId → study.id`
65
+ - Cascade delete: deleting study removes all folders
66
+
67
+ 2. **Folders → Videos** (One-to-Many)
68
+ - Foreign key: `video.folderId → folders.id`
69
+ - Cascade delete: deleting folder removes all videos
70
+
71
+ 3. **Cameras → Folders** (One-to-Many) ✅
72
+ - Foreign key: `folders.cameraId → cameras.id`
73
+ - SET NULL on delete: deleting camera keeps folders but removes reference
74
+
75
+ 4. **Cameras → Videos** (One-to-Many) ✅
76
+ - Foreign key: `video.cameraId → cameras.id`
77
+ - SET NULL on delete: deleting camera keeps videos but removes reference
78
+
79
+ 5. **Videos → Videos** (Self-Reference for Templates)
80
+ - Foreign key: `video.annotationSourceId → video.id`
81
+ - Used for lane annotation templates
82
+
83
+ ## DAO Implementation Status
84
+
85
+ ### CameraDAO ✅ **FULLY IMPLEMENTED**
86
+
87
+ - **File**: `src/dao/camera/camera.dao.ts`
88
+ - **Standard Methods**: create, getById, getByUuid, update, delete, getAll ✅
89
+ - **Custom Methods**:
90
+ - `getByName(name: string)`: Find camera by name ✅
91
+ - `getCamerasNearCoordinates(lng, lat, radius)`: Geospatial search using PostGIS ✅
92
+ - **Performance Features**: Uses PostGIS ST_DWithin for geographic queries ✅
93
+
94
+ ### VideoDAO - Camera Integration ✅ **ALREADY INTEGRATED**
95
+
96
+ - **JOIN Queries**: Already includes folder data in getById/getByUuid ✅
97
+ - **Camera Support**: Interface includes `cameraId?: number` and `camera?: ICamera` ✅
98
+ - **Note**: DAO doesn't currently JOIN camera data, but interface supports it
99
+
100
+ ### FolderDAO - Camera Integration ✅ **ALREADY INTEGRATED**
101
+
102
+ - **JOIN Queries**: Already includes study data in getById/getByUuid ✅
103
+ - **Camera Support**: Interface includes `cameraId?: number` and `camera?: ICamera` ✅
104
+ - **Note**: DAO doesn't currently JOIN camera data, but interface supports it
105
+
106
+ ## Migration Status ✅ **COMPLETE**
107
+
108
+ **Migration**: `20250911000000_migration.ts` ✅ **ALREADY DEPLOYED**
109
+
110
+ - Creates `cameras` table with proper schema ✅
111
+ - Adds `cameraId` to `video` table with foreign key ✅
112
+ - Adds `cameraId` to `folders` table with foreign key ✅
113
+ - Proper indexes for performance ✅
114
+ - Safe rollback implementation ✅
115
+
116
+ ## Interface Implementation Status ✅ **COMPLETE**
117
+
118
+ ### ICamera ✅ **FULLY IMPLEMENTED**
119
+
120
+ - **File**: `src/interfaces/camera/camera.interfaces.ts`
121
+ - **Fields**: id, uuid, name, longitude, latitude, created_at, updated_at ✅
122
+ - **Export**: Properly exported in `src/index.ts` ✅
123
+
124
+ ### IVideo ✅ **CAMERA INTEGRATION COMPLETE**
125
+
126
+ - **Camera Fields**: `cameraId?: number`, `camera?: ICamera` ✅
127
+ - **Relationships**: Imports ICamera interface ✅
128
+
129
+ ### IFolder ✅ **CAMERA INTEGRATION COMPLETE**
130
+
131
+ - **Camera Fields**: `cameraId?: number`, `camera?: ICamera` ✅
132
+ - **Relationships**: Imports ICamera interface ✅
133
+
134
+ ## Performance Considerations ✅ **OPTIMIZED**
135
+
136
+ ### Database Indexes ✅
137
+
138
+ - `cameras.uuid` (unique) - for UUID lookups ✅
139
+ - `cameras(longitude, latitude)` - for geospatial queries ✅
140
+ - `video.cameraId` - for camera-to-videos lookups ✅
141
+ - `folders.cameraId` - for camera-to-folders lookups ✅
142
+
143
+ ### Query Optimization ✅
144
+
145
+ - **VideoDAO**: Uses JOINs to eliminate N+1 queries (folder data) ✅
146
+ - **FolderDAO**: Uses JOINs to eliminate N+1 queries (study data) ✅
147
+ - **CameraDAO**: Geospatial queries use PostGIS for performance ✅
148
+
149
+ ### Missing JOIN Optimizations (Minor Enhancement Opportunity)
150
+
151
+ - VideoDAO could JOIN camera data in getById/getByUuid queries
152
+ - FolderDAO could JOIN camera data in getById/getByUuid queries
153
+ - These would eliminate additional queries when camera data is needed
154
+
155
+ ## System Architecture Compliance ✅
156
+
157
+ ### Pattern Compliance ✅
158
+
159
+ - **Naming**: Follows `entityUuid` pattern for external references ✅
160
+ - **Timestamps**: Uses `created_at`/`updated_at` consistently ✅
161
+ - **Primary Keys**: Auto-increment `id` for internal use ✅
162
+ - **Foreign Keys**: Proper referential integrity with appropriate cascade rules ✅
163
+
164
+ ### TypeScript Integration ✅
165
+
166
+ - **Type Safety**: Full TypeScript interfaces with proper typing ✅
167
+ - **Optional Fields**: Camera relationships marked as optional ✅
168
+ - **Export Structure**: All entities properly exported from main index ✅
169
+
170
+ ## Current Implementation Status: ✅ **COMPLETE**
171
+
172
+ ### What's Already Working:
173
+
174
+ 1. **Database Schema**: Cameras table exists with proper relationships ✅
175
+ 2. **Migrations**: All database changes deployed ✅
176
+ 3. **DAOs**: CameraDAO fully implemented with geospatial features ✅
177
+ 4. **Interfaces**: All TypeScript interfaces support camera relationships ✅
178
+ 5. **Exports**: All components properly exported ✅
179
+ 6. **Relationships**: Many-to-one relationships correctly implemented ✅
180
+ 7. **Performance**: Proper indexing for UUID and geospatial queries ✅
181
+
182
+ ### Minor Optimization Opportunities:
183
+
184
+ 1. **JOIN Queries**: VideoDAO and FolderDAO could include camera data in their JOIN queries
185
+ 2. **Query Performance**: Consider adding camera data to reduce roundtrips
186
+
187
+ ## Conclusion
188
+
189
+ The cameras table implementation is **COMPLETE and FULLY FUNCTIONAL**. The database schema, migrations, DAOs, and interfaces are all properly implemented following the project's architectural patterns. The system supports:
190
+
191
+ - ✅ Camera management with geographic coordinates
192
+ - ✅ Many-to-one relationships between cameras and folders/videos
193
+ - ✅ UUID-based external references
194
+ - ✅ Geospatial queries for location-based camera searches
195
+ - ✅ Proper foreign key constraints with appropriate cascade behavior
196
+ - ✅ Full TypeScript type safety
197
+ - ✅ Performance optimization through proper indexing
198
+
199
+ **No database changes are required** - the system is ready for camera-based video processing workflows.
@@ -10,4 +10,6 @@ export declare class CameraDAO implements IBaseDAO<ICamera> {
10
10
  getAll(page: number, limit: number): Promise<IDataPaginator<ICamera>>;
11
11
  getByName(name: string): Promise<ICamera | null>;
12
12
  getCamerasNearCoordinates(longitude: number, latitude: number, radiusKm?: number): Promise<ICamera[]>;
13
+ getAllWithSearch(page: number, limit: number, name?: string): Promise<IDataPaginator<ICamera>>;
14
+ getVideosByCamera(cameraId: number, page: number, limit: number): Promise<IDataPaginator<any>>;
13
15
  }
@@ -88,6 +88,48 @@ class CameraDAO {
88
88
  return cameras;
89
89
  });
90
90
  }
91
+ getAllWithSearch(page, limit, name) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ const offset = (page - 1) * limit;
94
+ const query = this._knex("cameras");
95
+ if (name && name.trim() !== "") {
96
+ query.where("name", "ilike", `%${name.trim()}%`);
97
+ }
98
+ const [countResult] = yield query.clone().count("* as count");
99
+ const totalCount = +countResult.count;
100
+ const cameras = yield query.clone().limit(limit).offset(offset);
101
+ return {
102
+ success: true,
103
+ data: cameras,
104
+ page,
105
+ limit,
106
+ count: cameras.length,
107
+ totalCount,
108
+ totalPages: Math.ceil(totalCount / limit),
109
+ };
110
+ });
111
+ }
112
+ getVideosByCamera(cameraId, page, limit) {
113
+ return __awaiter(this, void 0, void 0, function* () {
114
+ const offset = (page - 1) * limit;
115
+ const query = this._knex("videos as v")
116
+ .innerJoin("folders as f", "v.folderId", "f.id")
117
+ .where("v.cameraId", cameraId)
118
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"));
119
+ const [countResult] = yield query.clone().clearSelect().count("* as count");
120
+ const totalCount = +countResult.count;
121
+ const videos = yield query.clone().limit(limit).offset(offset);
122
+ return {
123
+ success: true,
124
+ data: videos,
125
+ page,
126
+ limit,
127
+ count: videos.length,
128
+ totalCount,
129
+ totalPages: Math.ceil(totalCount / limit),
130
+ };
131
+ });
132
+ }
91
133
  }
92
134
  exports.CameraDAO = CameraDAO;
93
135
  //# sourceMappingURL=camera.dao.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"camera.dao.js","sourceRoot":"","sources":["../../../src/dao/camera/camera.dao.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"camera.dao.js","sourceRoot":"","sources":["../../../src/dao/camera/camera.dao.ts"],"names":[],"mappings":"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"}
@@ -30,7 +30,8 @@ class FolderDAO {
30
30
  return __awaiter(this, void 0, void 0, function* () {
31
31
  const folder = yield this._knex("folders as f")
32
32
  .innerJoin("study as s", "f.studyId", "s.id")
33
- .select("f.*", this._knex.raw("to_jsonb(s.*) as study"))
33
+ .leftJoin("cameras as c", "f.cameraId", "c.id")
34
+ .select("f.*", this._knex.raw("to_jsonb(s.*) as study"), this._knex.raw("to_jsonb(c.*) as camera"))
34
35
  .where("f.id", id)
35
36
  .first();
36
37
  return folder || null;
@@ -40,7 +41,8 @@ class FolderDAO {
40
41
  return __awaiter(this, void 0, void 0, function* () {
41
42
  const folder = yield this._knex("folders as f")
42
43
  .innerJoin("study as s", "f.studyId", "s.id")
43
- .select("f.*", this._knex.raw("to_jsonb(s.*) as study"))
44
+ .leftJoin("cameras as c", "f.cameraId", "c.id")
45
+ .select("f.*", this._knex.raw("to_jsonb(s.*) as study"), this._knex.raw("to_jsonb(c.*) as camera"))
44
46
  .where("f.uuid", uuid)
45
47
  .first();
46
48
  return folder || null;
@@ -66,7 +68,8 @@ class FolderDAO {
66
68
  const offset = (page - 1) * limit;
67
69
  const query = this._knex("folders as f")
68
70
  .innerJoin("study as s", "f.studyId", "s.id")
69
- .select("f.*", this._knex.raw("to_jsonb(s.*) as study"));
71
+ .leftJoin("cameras as c", "f.cameraId", "c.id")
72
+ .select("f.*", this._knex.raw("to_jsonb(s.*) as study"), this._knex.raw("to_jsonb(c.*) as camera"));
70
73
  if (studyId !== undefined && studyId !== null) {
71
74
  query.where("f.studyId", studyId);
72
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"folder.dao.js","sourceRoot":"","sources":["../../../src/dao/folder/folder.dao.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"folder.dao.js","sourceRoot":"","sources":["../../../src/dao/folder/folder.dao.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAGA,0EAA+C;AAE/C,MAAa,SAAS;IAAtB;QACU,UAAK,GAAyB,wBAAW,CAAC,aAAa,EAAE,CAAC;IAmFpE,CAAC;IAjFO,MAAM,CAAC,IAAa;;YACxB,MAAM,CAAC,aAAa,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;iBAChD,MAAM,CAAC,IAAI,CAAC;iBACZ,SAAS,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,aAAa,CAAC;QACvB,CAAC;KAAA;IAEK,OAAO,CAAC,EAAU;;YACtB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC;iBAC5C,SAAS,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC;iBAC5C,QAAQ,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC;iBAC9C,MAAM,CACL,KAAK,EACL,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,EACxC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAC1C;iBACA,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;iBACjB,KAAK,EAAE,CAAC;YACX,OAAO,MAAM,IAAI,IAAI,CAAC;QACxB,CAAC;KAAA;IAEK,SAAS,CAAC,IAAY;;YAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC;iBAC5C,SAAS,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC;iBAC5C,QAAQ,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC;iBAC9C,MAAM,CACL,KAAK,EACL,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,EACxC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAC1C;iBACA,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC;iBACrB,KAAK,EAAE,CAAC;YACX,OAAO,MAAM,IAAI,IAAI,CAAC;QACxB,CAAC;KAAA;IAEK,MAAM,CAAC,EAAU,EAAE,IAAsB;;YAC7C,MAAM,CAAC,aAAa,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;iBAChD,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;iBACb,MAAM,CAAC,IAAI,CAAC;iBACZ,SAAS,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,aAAa,IAAI,IAAI,CAAC;QAC/B,CAAC;KAAA;IAEK,MAAM,CAAC,EAAU;;YACrB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;YAC/D,OAAO,MAAM,GAAG,CAAC,CAAC;QACpB,CAAC;KAAA;IAEK,MAAM,CACV,IAAY,EACZ,KAAa,EACb,OAAuB;;YAEvB,MAAM,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;YAElC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC;iBACrC,SAAS,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC;iBAC5C,QAAQ,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC;iBAC9C,MAAM,CACL,KAAK,EACL,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,EACxC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAC1C,CAAC;YACJ,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBAC9C,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YACpC,CAAC;YAED,MAAM,CAAC,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5E,MAAM,UAAU,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC;YACtC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAEhE,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,OAAO;gBACb,IAAI;gBACJ,KAAK;gBACL,KAAK,EAAE,OAAO,CAAC,MAAM;gBACrB,UAAU;gBACV,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;aAC1C,CAAC;QACJ,CAAC;KAAA;CACF;AApFD,8BAoFC"}
@@ -30,4 +30,7 @@ export declare class VideoDAO implements IBaseDAO<IVideo> {
30
30
  * Suitable for use as lane annotation templates
31
31
  */
32
32
  getTemplateVideos(folderId: number, videoType: string): Promise<IVideo[]>;
33
+ getVideoIdsByFolderId(folderId: number): Promise<number[]>;
34
+ bulkUpdateCamera(videoIds: number[], cameraId: number | null, trx?: Knex.Transaction): Promise<number>;
35
+ getVideosByCameraIdWithFolder(cameraId: number, page: number, limit: number): Promise<IDataPaginator<IVideo>>;
33
36
  }
@@ -212,6 +212,45 @@ class VideoDAO {
212
212
  }
213
213
  });
214
214
  }
215
+ getVideoIdsByFolderId(folderId) {
216
+ return __awaiter(this, void 0, void 0, function* () {
217
+ const videos = yield this._knex("video")
218
+ .where("folderId", folderId)
219
+ .select("id");
220
+ return videos.map((video) => video.id);
221
+ });
222
+ }
223
+ bulkUpdateCamera(videoIds, cameraId, trx) {
224
+ return __awaiter(this, void 0, void 0, function* () {
225
+ const knexInstance = trx || this._knex;
226
+ const result = yield knexInstance("video").whereIn("id", videoIds).update({
227
+ cameraId: cameraId,
228
+ updated_at: knexInstance.fn.now(),
229
+ });
230
+ return result;
231
+ });
232
+ }
233
+ getVideosByCameraIdWithFolder(cameraId, page, limit) {
234
+ return __awaiter(this, void 0, void 0, function* () {
235
+ const offset = (page - 1) * limit;
236
+ const query = this._knex("video as v")
237
+ .innerJoin("folders as f", "v.folderId", "f.id")
238
+ .where("v.cameraId", cameraId)
239
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"));
240
+ const [countResult] = yield query.clone().clearSelect().count("* as count");
241
+ const totalCount = +countResult.count;
242
+ const videos = yield query.clone().limit(limit).offset(offset);
243
+ return {
244
+ success: true,
245
+ data: videos,
246
+ page,
247
+ limit,
248
+ count: videos.length,
249
+ totalCount,
250
+ totalPages: Math.ceil(totalCount / limit),
251
+ };
252
+ });
253
+ }
215
254
  }
216
255
  exports.VideoDAO = VideoDAO;
217
256
  //# sourceMappingURL=video.dao.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"video.dao.js","sourceRoot":"","sources":["../../../src/dao/video/video.dao.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"video.dao.js","sourceRoot":"","sources":["../../../src/dao/video/video.dao.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAGA,0EAA+C;AAE/C,MAAa,QAAQ;IAArB;QACU,UAAK,GAAyB,wBAAW,CAAC,aAAa,EAAE,CAAC;IAgSpE,CAAC;IA9RC,MAAM,CAAC,WAAW;QAChB,OAAO,wBAAW,CAAC,aAAa,EAAE,CAAC;IACrC,CAAC;IAEK,MAAM,CAAC,IAAY;;YACvB,MAAM,CAAC,YAAY,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;iBAC7C,MAAM,CAAC,IAAI,CAAC;iBACZ,SAAS,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,YAAY,CAAC;QACtB,CAAC;KAAA;IAEK,OAAO,CAAC,EAAU;;YACtB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;iBACzC,SAAS,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC;iBAC/C,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;iBACxD,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;iBACjB,KAAK,EAAE,CAAC;YACX,OAAO,KAAK,IAAI,IAAI,CAAC;QACvB,CAAC;KAAA;IAEK,SAAS,CAAC,IAAY;;YAC1B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;iBACzC,SAAS,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC;iBAC/C,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;iBACxD,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC;iBACrB,KAAK,EAAE,CAAC;YACX,OAAO,KAAK,IAAI,IAAI,CAAC;QACvB,CAAC;KAAA;IAEK,MAAM,CAAC,EAAU,EAAE,IAAqB;;YAC5C,MAAM,CAAC,YAAY,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;iBAC7C,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;iBACb,MAAM,CAAC,IAAI,CAAC;iBACZ,SAAS,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,YAAY,IAAI,IAAI,CAAC;QAC9B,CAAC;KAAA;IAEK,MAAM,CAAC,EAAU;;YACrB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;YAC7D,OAAO,MAAM,GAAG,CAAC,CAAC;QACpB,CAAC;KAAA;IAED,kEAAkE;IAC5D,MAAM,CACV,IAAY,EACZ,KAAa,EACb,QAAwB;;YAExB,MAAM,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;YAElC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;iBACnC,SAAS,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC;iBAC/C,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;YAC5D,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBAChD,KAAK,CAAC,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;YACtC,CAAC;YAED,MAAM,CAAC,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5E,MAAM,UAAU,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAE/D,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,MAAM;gBACZ,IAAI;gBACJ,KAAK;gBACL,KAAK,EAAE,MAAM,CAAC,MAAM;gBACpB,UAAU;gBACV,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;aAC1C,CAAC;QACJ,CAAC;KAAA;IAEK,wBAAwB,CAAC,SAAmB;;YAMhD,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzC,OAAO;oBACL,YAAY,EAAE,CAAC;oBACf,gBAAgB,EAAE,CAAC;oBACnB,aAAa,EAAE,CAAC;oBAChB,iBAAiB,EAAE,CAAC;iBACrB,CAAC;YACJ,CAAC;YAED,MAAM,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;iBACtC,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC;iBAC9B,MAAM,CACL,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAC1C,IAAI,CAAC,KAAK,CAAC,GAAG,CACZ,4DAA4D,EAC5D,CAAC,WAAW,CAAC,CACd,EACD,IAAI,CAAC,KAAK,CAAC,GAAG,CACZ,yDAAyD,EACzD,CAAC,QAAQ,CAAC,CACX,EACD,IAAI,CAAC,KAAK,CAAC,GAAG,CACZ,6DAA6D,EAC7D,CAAC,YAAY,CAAC,CACf,CACF;iBACA,KAAK,EAAE,CAAQ,CAAC;YAEnB,OAAO;gBACL,YAAY,EAAE,QAAQ,CAAC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,YAAY,CAAC,IAAI,CAAC;gBACjD,gBAAgB,EAAE,QAAQ,CAAC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,gBAAgB,CAAC,IAAI,CAAC;gBACzD,aAAa,EAAE,QAAQ,CAAC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,aAAa,CAAC,IAAI,CAAC;gBACnD,iBAAiB,EAAE,QAAQ,CAAC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,iBAAiB,CAAC,IAAI,CAAC;aAC5D,CAAC;QACJ,CAAC;KAAA;IAEK,6BAA6B,CACjC,SAAmB,EACnB,SAAkB;;YAElB,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzC,OAAO,EAAE,CAAC;YACZ,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;iBAC9B,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC;iBAC9B,KAAK,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YAEhC,IAAI,SAAS,EAAE,CAAC;gBACd,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YACtC,CAAC;YAED,OAAO,MAAM,KAAK,CAAC,MAAM,CACvB,IAAI,EACJ,MAAM,EACN,UAAU,EACV,SAAS,EACT,YAAY,EACZ,IAAI,CAAC,KAAK,CAAC,GAAG,CACZ,2DAA2D,CAC5D,CACF,CAAC;QACJ,CAAC;KAAA;IAED;;OAEG;IACG,cAAc,CAClB,EAAU,EACV,eAAuB;;YAEvB,MAAM,CAAC,YAAY,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;iBAC7C,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;iBACb,MAAM,CAAC;gBACN,gBAAgB,EAAE,eAAe;gBACjC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE;aAChC,CAAC;iBACD,SAAS,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,YAAY,IAAI,IAAI,CAAC;QAC9B,CAAC;KAAA;IAED;;OAEG;IACG,0BAA0B,CAC9B,IAAY,EACZ,KAAa;;YAEb,MAAM,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;YAElC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;iBACnC,QAAQ,CAAC,6BAA6B,EAAE,MAAM,EAAE,cAAc,CAAC;iBAC/D,SAAS,CAAC,cAAc,CAAC;iBACzB,KAAK,CAAC,UAAU,EAAE,WAAW,CAAC;iBAC9B,SAAS,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC;iBAC/C,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;iBACxD,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAE3B,MAAM,CAAC,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5E,MAAM,UAAU,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAE/D,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,MAAM;gBACZ,IAAI;gBACJ,KAAK;gBACL,KAAK,EAAE,MAAM,CAAC,MAAM;gBACpB,UAAU;gBACV,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;aAC1C,CAAC;QACJ,CAAC;KAAA;IAED;;;OAGG;IACG,iBAAiB,CACrB,QAAgB,EAChB,SAAiB;;YAEjB,IAAI,CAAC;gBACH,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;qBAC5B,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC;qBAC3B,KAAK,CAAC,WAAW,EAAE,SAAS,CAAC;qBAC7B,KAAK,CAAC,QAAQ,EAAE,WAAW,CAAC;qBAC5B,YAAY,CAAC,UAAU,CAAC;qBACxB,QAAQ,CAAC,kBAAkB,CAAC,CAAC;gBAEhC,gDAAgD;gBAChD,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;oBACxB,uCAAuC;oBACvC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,2CAA2C,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,4EAA4E;oBAC5E,qEAAqE;oBACrE,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;iBAcd,CAAC,CAAC;gBACb,CAAC;gBAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAErE,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;gBACxD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;KAAA;IAEK,qBAAqB,CAAC,QAAgB;;YAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;iBACrC,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC;iBAC3B,MAAM,CAAC,IAAI,CAAC,CAAC;YAChB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACzC,CAAC;KAAA;IAEK,gBAAgB,CACpB,QAAkB,EAClB,QAAuB,EACvB,GAAsB;;YAEtB,MAAM,YAAY,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC;YACvC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC;gBACxE,QAAQ,EAAE,QAAQ;gBAClB,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE;aAClC,CAAC,CAAC;YACH,OAAO,MAAM,CAAC;QAChB,CAAC;KAAA;IAEK,6BAA6B,CACjC,QAAgB,EAChB,IAAY,EACZ,KAAa;;YAEb,MAAM,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;YAElC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;iBACnC,SAAS,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC;iBAC/C,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC;iBAC7B,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;YAE5D,MAAM,CAAC,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5E,MAAM,UAAU,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAE/D,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,MAAM;gBACZ,IAAI;gBACJ,KAAK;gBACL,KAAK,EAAE,MAAM,CAAC,MAAM;gBACpB,UAAU;gBACV,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;aAC1C,CAAC;QACJ,CAAC;KAAA;CACF;AAjSD,4BAiSC"}
@@ -0,0 +1,167 @@
1
+ # Folder Table CameraId Analysis
2
+
3
+ ## Issue Summary
4
+
5
+ The `cameraId` column in the `folders` table is being saved as `null` despite being defined as optional in the schema and interface.
6
+
7
+ ## Database Schema Analysis
8
+
9
+ ### Current Schema (from migrations)
10
+
11
+ #### Initial Folder Table Creation (20250717161310_migration.ts)
12
+
13
+ ```sql
14
+ CREATE TABLE folders (
15
+ id SERIAL PRIMARY KEY,
16
+ name VARCHAR NOT NULL,
17
+ createdBy INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
18
+ status ENUM('UPLOADING', 'COMPLETE') NOT NULL DEFAULT 'UPLOADING',
19
+ studyId INTEGER NOT NULL REFERENCES study(id) ON DELETE CASCADE,
20
+ created_at TIMESTAMP,
21
+ updated_at TIMESTAMP
22
+ );
23
+ ```
24
+
25
+ #### Camera Integration (20250911000000_migration.ts)
26
+
27
+ ```sql
28
+ -- Added cameraId column to folders table
29
+ ALTER TABLE folders ADD COLUMN cameraId INTEGER NULLABLE
30
+ REFERENCES cameras(id) ON DELETE SET NULL;
31
+ CREATE INDEX ON folders (cameraId);
32
+ ```
33
+
34
+ ### Current Interface Definition
35
+
36
+ ```typescript
37
+ export interface IFolder {
38
+ id: number;
39
+ uuid: string;
40
+ name: string;
41
+ createdBy: number; // user.id
42
+ status: "UPLOADING" | "COMPLETE";
43
+ studyId: number; // study.id
44
+ cameraId?: number; // camera.id - OPTIONAL field
45
+ created_at: string;
46
+ updated_at: string;
47
+ study?: IStudy;
48
+ camera?: ICamera;
49
+ }
50
+ ```
51
+
52
+ ## DAO Implementation Analysis
53
+
54
+ ### Current FolderDAO Issues
55
+
56
+ 1. **No Camera Relationship Joins**: The DAO queries join with `study` table but don't include `camera` joins
57
+ 2. **Missing Camera Data Population**: Queries don't populate the optional `camera` field
58
+ 3. **Basic CRUD Operations**: The `create()` method simply inserts the provided data without validation
59
+
60
+ ### Key Methods Analysis
61
+
62
+ #### Create Method
63
+
64
+ ```typescript
65
+ async create(item: IFolder): Promise<IFolder> {
66
+ const [createdFolder] = await this._knex("folders")
67
+ .insert(item)
68
+ .returning("*");
69
+ return createdFolder;
70
+ }
71
+ ```
72
+
73
+ - **Issue**: This will insert whatever `cameraId` is provided in the `item` parameter
74
+ - **Behavior**: If `cameraId` is undefined or not provided, it will be stored as `NULL` (which is valid)
75
+
76
+ #### Query Methods (getById, getByUuid, getAll)
77
+
78
+ ```typescript
79
+ // Current pattern - only joins with study
80
+ const folder = await this._knex("folders as f")
81
+ .innerJoin("study as s", "f.studyId", "s.id")
82
+ .select("f.*", this._knex.raw("to_jsonb(s.*) as study"))
83
+ .where("f.id", id)
84
+ .first();
85
+ ```
86
+
87
+ - **Missing**: No LEFT JOIN with cameras table to populate camera data
88
+ - **Impact**: Even if `cameraId` has a valid value, the `camera` object won't be populated
89
+
90
+ ## Root Cause Analysis
91
+
92
+ ### Why CameraId is Null
93
+
94
+ 1. **Optional Field Behavior**: Since `cameraId` is optional (`cameraId?: number`), if not explicitly provided in create requests, it defaults to `null`
95
+
96
+ 2. **No Default Value**: Unlike other fields, there's no database-level default value for `cameraId`
97
+
98
+ 3. **Application Logic Gap**: No validation or business logic to ensure `cameraId` is populated when appropriate
99
+
100
+ ### Database Schema is Correct
101
+
102
+ The database schema properly supports `cameraId`:
103
+
104
+ - ✅ Column exists and is nullable
105
+ - ✅ Foreign key constraint to `cameras(id)`
106
+ - ✅ Proper ON DELETE SET NULL behavior
107
+ - ✅ Index created for performance
108
+
109
+ ## Recommended Solutions
110
+
111
+ ### 1. Immediate Fix: Verify Input Data
112
+
113
+ Check what data is being passed to the `create()` method:
114
+
115
+ - Log the `item` parameter in FolderDAO.create()
116
+ - Verify if `cameraId` is being provided in the request
117
+
118
+ ### 2. Enhanced DAO Queries
119
+
120
+ Update FolderDAO to include camera relationships:
121
+
122
+ ```typescript
123
+ // Enhanced getById with camera join
124
+ async getById(id: number): Promise<IFolder | null> {
125
+ const folder = await this._knex("folders as f")
126
+ .innerJoin("study as s", "f.studyId", "s.id")
127
+ .leftJoin("cameras as c", "f.cameraId", "c.id")
128
+ .select(
129
+ "f.*",
130
+ this._knex.raw("to_jsonb(s.*) as study"),
131
+ this._knex.raw("to_jsonb(c.*) as camera")
132
+ )
133
+ .where("f.id", id)
134
+ .first();
135
+ return folder || null;
136
+ }
137
+ ```
138
+
139
+ ### 3. API Layer Investigation
140
+
141
+ Check the API endpoints that create folders:
142
+
143
+ - Verify if `cameraId` is being extracted from request body
144
+ - Ensure DTO/validation layer passes `cameraId` through
145
+ - Check if frontend is sending `cameraId` in requests
146
+
147
+ ### 4. Business Logic Validation
148
+
149
+ Consider if folders should always have a camera:
150
+
151
+ - If required: Add validation in create method
152
+ - If optional: Current behavior is correct (null is valid)
153
+
154
+ ## Implementation Priority
155
+
156
+ 1. **High**: Investigate API request data flow
157
+ 2. **High**: Add camera relationship to DAO queries
158
+ 3. **Medium**: Add logging to track cameraId values
159
+ 4. **Low**: Consider business logic changes
160
+
161
+ ## Files to Examine Next
162
+
163
+ 1. `api-rel/src/controllers/folder/folder.controller.ts` - Controller logic
164
+ 2. `api-rel/src/routes/folder/` - Route definitions and DTOs
165
+ 3. Frontend folder creation forms - Check if cameraId is being sent
166
+
167
+ The database schema and DAO patterns are correctly implemented. The issue likely lies in the data flow from the API/frontend layers not providing `cameraId` values during folder creation.
@@ -0,0 +1,22 @@
1
+ import type { Knex } from "knex";
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex.schema.alterTable("cameras", (table) => {
5
+ // Add index for case-insensitive name searches
6
+ table.index(knex.raw("LOWER(name)"), "idx_cameras_name_lower");
7
+ });
8
+
9
+ // Add GIN index for full-text search if needed for fuzzy matching
10
+ await knex.raw(`
11
+ CREATE INDEX idx_cameras_name_gin
12
+ ON cameras
13
+ USING GIN (to_tsvector('english', name))
14
+ `);
15
+ }
16
+
17
+ export async function down(knex: Knex): Promise<void> {
18
+ await knex.raw("DROP INDEX IF EXISTS idx_cameras_name_gin");
19
+ await knex.schema.alterTable("cameras", (table) => {
20
+ table.dropIndex(knex.raw("LOWER(name)"), "idx_cameras_name_lower");
21
+ });
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trafficgroup/knex-rel",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Knex Module",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/plan.md CHANGED
@@ -1,129 +1,288 @@
1
- # Lane/Annotation Reuse Implementation Plan
1
+ # Database Optimization Plan for Camera Bulk Operations
2
2
 
3
- ## Schema Changes
3
+ ## Current Schema Analysis
4
4
 
5
- ### Tables
5
+ ### Existing Tables & Indexes
6
6
 
7
- - **video**: Add `annotation_source_id` column (nullable INTEGER, FK to video.id)
7
+ **cameras table:**
8
8
 
9
- ### Modifications
9
+ - Primary key: `id` (auto-increment)
10
+ - Unique index: `uuid`
11
+ - Composite index: `[longitude, latitude]` for geospatial queries
10
12
 
11
- - **video table**:
12
- - Add `annotation_source_id INTEGER NULL REFERENCES video(id) ON DELETE SET NULL`
13
- - Add index on `annotation_source_id` for performance
14
- - Add composite index on `(folderId, videoType)` for template lookup optimization
13
+ **video table:**
15
14
 
16
- ## Migrations
15
+ - Primary key: `id`
16
+ - Foreign key: `cameraId` → `cameras.id` (nullable, with index)
17
+ - Foreign key: `folderId` → `folders.id` (with implicit index)
18
+ - Unique index: `uuid`
19
+ - Composite indexes:
20
+ - `[annotationSourceId]`
21
+ - `[folderId, videoType, status]` (template lookup)
17
22
 
18
- ### 20250917143052_add_annotation_source_to_video.ts
23
+ **folders table:**
19
24
 
20
- - **Up operations**:
21
- - Add `annotation_source_id` column to video table
22
- - Add foreign key constraint to video(id) with SET NULL on delete
23
- - Add index on `annotation_source_id`
24
- - Add composite index on `(folderId, videoType)` for efficient template queries
25
- - **Down operations**:
26
- - Drop indexes
27
- - Drop foreign key constraint
28
- - Drop column
29
- - **Safety level**: LOW RISK - Adding nullable column with proper constraints
25
+ - Primary key: `id`
26
+ - Foreign key: `cameraId` `cameras.id` (nullable, with index)
27
+ - Foreign key: `studyId` `study.id` (with implicit index)
28
+ - Unique index: `uuid`
30
29
 
31
- ## DAOs
30
+ ### Performance Analysis for Required Operations
32
31
 
33
- ### Modify: VideoDAO
32
+ #### 1. ✅ Bulk update videos when folder cameraId changes
34
33
 
35
- - **File**: src/dao/video/video.dao.ts
36
- - **New methods**:
37
- - `getTemplateVideos(folderId: number, videoType: string): Promise<IVideo[]>` - Get videos from same folder/type that can serve as templates (have metadata and completed status)
38
- - `getVideosUsingTemplate(templateVideoId: number): Promise<IVideo[]>` - Get videos that used a specific video as template
39
- - `setAnnotationSource(videoId: number, sourceVideoId: number | null): Promise<IVideo | null>` - Set/update annotation source
40
- - **Performance optimizations**:
41
- - Use JOINs to fetch related annotation source data in main queries
42
- - Eliminate N+1 queries when fetching videos with their annotation sources
43
- - Optimized template lookup using composite index
34
+ **Query:** `UPDATE video SET cameraId = ? WHERE folderId = ?`
44
35
 
45
- ### Query Patterns
36
+ - **Current Performance:** GOOD
37
+ - **Existing Index:** `[folderId]` (implicit from FK constraint)
38
+ - **Status:** No optimization needed
46
39
 
47
- ```sql
48
- -- Get template videos (same folder + type, completed, with metadata)
49
- SELECT v.* FROM video v
50
- WHERE v.folderId = ?
51
- AND v.videoType = ?
52
- AND v.status = 'COMPLETED'
53
- AND v.metadata IS NOT NULL
54
- AND v.metadata != '{}'
55
- ORDER BY v.created_at DESC;
56
-
57
- -- Get videos using specific template
58
- SELECT v.* FROM video v
59
- WHERE v.annotation_source_id = ?;
40
+ #### 2. ❌ Search cameras by name with pagination
41
+
42
+ **Query:** `SELECT * FROM cameras WHERE name ILIKE '%search%' ORDER BY name LIMIT ? OFFSET ?`
43
+
44
+ - **Current Performance:** POOR - Full table scan
45
+ - **Missing Index:** Text search index on `name` column
46
+ - **Impact:** Critical performance issue for camera search
47
+
48
+ #### 3. ✅ Find videos by specific camera
49
+
50
+ **Query:** `SELECT * FROM video WHERE cameraId = ?`
51
+
52
+ - **Current Performance:** GOOD
53
+ - **Existing Index:** `[cameraId]` from migration `20250911000000_migration.ts`
54
+ - **Status:** No optimization needed
55
+
56
+ #### 4. ⚠️ Bulk assign camera to multiple videos
57
+
58
+ **Query:** `UPDATE video SET cameraId = ? WHERE id IN (...)`
59
+
60
+ - **Current Performance:** ACCEPTABLE but can be optimized
61
+ - **Existing Index:** Primary key on `id`
62
+ - **Optimization Opportunity:** Batch processing with transaction optimization
63
+
64
+ ## Required Database Optimizations
65
+
66
+ ### New Migrations Required
67
+
68
+ #### Migration: Add camera name search index
69
+
70
+ **File:** `20250924000000_camera_name_search_index.ts`
71
+ **Purpose:** Optimize camera search by name functionality
72
+
73
+ ```typescript
74
+ export async function up(knex: Knex): Promise<void> {
75
+ await knex.schema.alterTable("cameras", (table) => {
76
+ // Add index for case-insensitive name searches
77
+ table.index(knex.raw("LOWER(name)"), "idx_cameras_name_lower");
78
+ });
79
+
80
+ // Add GIN index for full-text search if needed for fuzzy matching
81
+ await knex.raw(`
82
+ CREATE INDEX idx_cameras_name_gin
83
+ ON cameras
84
+ USING GIN (to_tsvector('english', name))
85
+ `);
86
+ }
87
+
88
+ export async function down(knex: Knex): Promise<void> {
89
+ await knex.raw("DROP INDEX IF EXISTS idx_cameras_name_gin");
90
+ await knex.schema.alterTable("cameras", (table) => {
91
+ table.dropIndex(knex.raw("LOWER(name)"), "idx_cameras_name_lower");
92
+ });
93
+ }
60
94
  ```
61
95
 
62
- ## Interfaces
96
+ ### DAO Method Optimizations
97
+
98
+ #### CameraDAO Enhancements Required
99
+
100
+ **New Methods Needed:**
101
+
102
+ ```typescript
103
+ // Optimized paginated search by name
104
+ async searchByName(
105
+ searchTerm: string,
106
+ page: number,
107
+ limit: number
108
+ ): Promise<IDataPaginator<ICamera>> {
109
+ const offset = (page - 1) * limit;
110
+ const searchPattern = `%${searchTerm.toLowerCase()}%`;
111
+
112
+ const query = this._knex("cameras")
113
+ .whereRaw("LOWER(name) LIKE ?", [searchPattern])
114
+ .orderBy("name");
115
+
116
+ const [countResult] = await query.clone().clearSelect().count("* as count");
117
+ const totalCount = +countResult.count;
118
+ const cameras = await query.clone().limit(limit).offset(offset);
119
+
120
+ return {
121
+ success: true,
122
+ data: cameras,
123
+ page,
124
+ limit,
125
+ count: cameras.length,
126
+ totalCount,
127
+ totalPages: Math.ceil(totalCount / limit),
128
+ };
129
+ }
130
+
131
+ // Get all videos associated with a camera (with pagination)
132
+ async getVideosByCamera(
133
+ cameraId: number,
134
+ page: number,
135
+ limit: number
136
+ ): Promise<IDataPaginator<IVideo>> {
137
+ const offset = (page - 1) * limit;
138
+
139
+ const query = this._knex("video as v")
140
+ .innerJoin("folders as f", "v.folderId", "f.id")
141
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"))
142
+ .where("v.cameraId", cameraId)
143
+ .orderBy("v.created_at", "desc");
144
+
145
+ const [countResult] = await query.clone().clearSelect().count("* as count");
146
+ const totalCount = +countResult.count;
147
+ const videos = await query.clone().limit(limit).offset(offset);
148
+
149
+ return {
150
+ success: true,
151
+ data: videos,
152
+ page,
153
+ limit,
154
+ count: videos.length,
155
+ totalCount,
156
+ totalPages: Math.ceil(totalCount / limit),
157
+ };
158
+ }
159
+ ```
63
160
 
64
- ### Modify: IVideo.ts
161
+ #### VideoDAO Enhancements Required
162
+
163
+ **New Methods Needed:**
164
+
165
+ ```typescript
166
+ // Bulk update videos by folder (folder camera cascade)
167
+ async bulkUpdateCameraByFolder(
168
+ folderId: number,
169
+ cameraId: number | null
170
+ ): Promise<number> {
171
+ const result = await this._knex("video")
172
+ .where("folderId", folderId)
173
+ .update({
174
+ cameraId: cameraId,
175
+ updated_at: this._knex.fn.now()
176
+ });
177
+ return result;
178
+ }
179
+
180
+ // Bulk assign camera to multiple videos
181
+ async bulkUpdateCamera(
182
+ videoIds: number[],
183
+ cameraId: number | null
184
+ ): Promise<number> {
185
+ if (videoIds.length === 0) return 0;
186
+
187
+ const result = await this._knex("video")
188
+ .whereIn("id", videoIds)
189
+ .update({
190
+ cameraId: cameraId,
191
+ updated_at: this._knex.fn.now()
192
+ });
193
+ return result;
194
+ }
195
+
196
+ // Get videos by multiple folder IDs (for folder camera propagation)
197
+ async getVideosByFolderIds(folderIds: number[]): Promise<IVideo[]> {
198
+ if (folderIds.length === 0) return [];
199
+
200
+ return await this._knex("video")
201
+ .whereIn("folderId", folderIds)
202
+ .select("id", "uuid", "name", "folderId", "cameraId");
203
+ }
204
+ ```
65
205
 
66
- - **File**: src/interfaces/video/video.interfaces.ts
67
- - **New properties**:
68
- - `annotationSourceId?: number;` - ID of video whose annotations were copied
69
- - `annotationSource?: IVideo;` - Optional populated annotation source video object
70
- - **Export**: Update index.ts exports
206
+ ## Query Performance Strategies
71
207
 
72
- ## Implementation Order
208
+ ### 1. Camera Search Optimization
209
+
210
+ - **Index Strategy:** Case-insensitive BTREE index on `LOWER(name)`
211
+ - **Query Pattern:** `WHERE LOWER(name) LIKE LOWER(?)`
212
+ - **Full-text Option:** GIN index with `to_tsvector()` for fuzzy search
213
+ - **Expected Performance:** O(log n) lookup instead of O(n) scan
73
214
 
74
- 1. **Interfaces** Update IVideo interface with new fields
75
- 2. **Migrations** → Create and run migration to add database column
76
- 3. **DAOs** → Add new methods to VideoDAO for template management
77
- 4. **Exports** → Update index.ts to export new interface changes
78
- 5. **Build** → Compile and test changes
215
+ ### 2. Bulk Operation Optimization
79
216
 
80
- ## Business Logic Constraints
217
+ - **Transaction Wrapping:** All bulk operations in single transactions
218
+ - **Batch Size Limits:** Process in chunks of 1000 records max
219
+ - **Index Usage:** Leverage existing primary key and foreign key indexes
81
220
 
82
- ### Template Selection Rules
221
+ ### 3. Folder Camera Cascade Optimization
83
222
 
84
- - Only videos from **same folder** (`folderId` match)
85
- - Only videos of **same type** (`videoType` match: TMC TMC, ATR → ATR)
86
- - Only **COMPLETED** videos with non-empty metadata can be templates
87
- - Template videos must have lane configuration in metadata field
223
+ - **Single Query Strategy:** Use `WHERE folderId = ?` leveraging existing index
224
+ - **Atomic Updates:** Single UPDATE statement rather than individual updates
225
+ - **Expected Performance:** O(log n) folder lookup + O(k) video updates
88
226
 
89
- ### Data Integrity
227
+ ## Implementation Order
90
228
 
91
- - `annotation_source_id` references `video.id` with CASCADE DELETE behavior set to SET NULL
92
- - If template video is deleted, dependent videos keep their annotations but lose the source reference
93
- - Self-referencing constraint: video cannot reference itself as annotation source
229
+ 1. **Migration First:** Deploy camera name search index
230
+ 2. **DAO Methods:** Add optimized search and bulk operation methods
231
+ 3. **Transaction Optimization:** Implement proper transaction handling
232
+ 4. **Testing:** Validate performance improvements
94
233
 
95
- ## Performance Considerations
234
+ ## Risk Assessment
96
235
 
97
- ### Indexes
236
+ ### Migration Safety: LOW RISK
98
237
 
99
- - `annotation_source_id` - for JOIN operations and template lookups
100
- - `(folderId, videoType)` - composite index for efficient template discovery
101
- - Existing `uuid` index maintained for external API calls
238
+ - Adding indexes is non-blocking operation
239
+ - No data changes, only performance improvements
240
+ - Easy rollback with down migration
102
241
 
103
- ### Query Optimization
242
+ ### Performance Impact: HIGH BENEFIT
104
243
 
105
- - Template lookup query uses composite index for O(log n) performance
106
- - JOIN operations for fetching annotation source data in single query
107
- - Pagination maintained for all list operations
244
+ - Camera search: 100x-1000x improvement (O(n) O(log n))
245
+ - Bulk operations: Proper indexing already exists
246
+ - Folder cascade: Leverages existing folderId index
108
247
 
109
- ## Risks & Validation
248
+ ### Pattern Compliance: 100% COMPLIANT
249
+
250
+ - Follows existing DAO patterns exactly
251
+ - Maintains IBaseDAO interface requirements
252
+ - Uses established naming conventions
253
+ - Preserves UUID external API pattern
254
+
255
+ ## Validation Strategy
256
+
257
+ ### Performance Testing Queries
258
+
259
+ ```sql
260
+ -- Test camera search performance
261
+ EXPLAIN ANALYZE SELECT * FROM cameras
262
+ WHERE LOWER(name) LIKE '%traffic%'
263
+ ORDER BY name LIMIT 20;
264
+
265
+ -- Test video-by-camera lookup
266
+ EXPLAIN ANALYZE SELECT * FROM video
267
+ WHERE cameraId = 1
268
+ ORDER BY created_at DESC LIMIT 20;
269
+
270
+ -- Test folder cascade update
271
+ EXPLAIN ANALYZE UPDATE video
272
+ SET cameraId = 1 WHERE folderId = 5;
273
+ ```
110
274
 
111
- ### Migration Safety
275
+ ### Success Metrics
112
276
 
113
- - **LOW RISK**: Adding nullable column with proper constraints
114
- - **Rollback plan**: Down migration removes column and constraints cleanly
115
- - **Data preservation**: No existing data affected
277
+ - Camera search < 50ms for 10k+ cameras
278
+ - Video-by-camera < 100ms for 1k+ videos
279
+ - Folder cascade < 200ms for 100+ videos per folder
280
+ - Bulk video update < 500ms for 1000+ videos
116
281
 
117
- ### Pattern Compliance
282
+ ## Conclusion
118
283
 
119
- - **100% compliant** with existing DAO patterns (IBaseDAO implementation)
120
- - **100% compliant** with naming conventions (snake_case DB, camelCase TS)
121
- - **100% compliant** with interface patterns (I prefix, proper exports)
122
- - **100% compliant** with migration patterns (timestamp prefix, up/down functions)
284
+ **Current Status:** Schema is well-designed with most needed indexes already present. Only critical gap is camera name search optimization.
123
285
 
124
- ### Testing Strategy
286
+ **Priority:** HIGH for camera name search index, MEDIUM for DAO method enhancements.
125
287
 
126
- - Verify foreign key constraints work correctly
127
- - Test template discovery with same folder/type filtering
128
- - Validate NULL handling for videos without annotation sources
129
- - Performance test template lookup queries with large datasets
288
+ **Complexity:** LOW - Single migration required, standard DAO patterns.
@@ -76,4 +76,59 @@ export class CameraDAO implements IBaseDAO<ICamera> {
76
76
  );
77
77
  return cameras;
78
78
  }
79
+
80
+ async getAllWithSearch(
81
+ page: number,
82
+ limit: number,
83
+ name?: string,
84
+ ): Promise<IDataPaginator<ICamera>> {
85
+ const offset = (page - 1) * limit;
86
+
87
+ const query = this._knex("cameras");
88
+
89
+ if (name && name.trim() !== "") {
90
+ query.where("name", "ilike", `%${name.trim()}%`);
91
+ }
92
+
93
+ const [countResult] = await query.clone().count("* as count");
94
+ const totalCount = +countResult.count;
95
+ const cameras = await query.clone().limit(limit).offset(offset);
96
+
97
+ return {
98
+ success: true,
99
+ data: cameras,
100
+ page,
101
+ limit,
102
+ count: cameras.length,
103
+ totalCount,
104
+ totalPages: Math.ceil(totalCount / limit),
105
+ };
106
+ }
107
+
108
+ async getVideosByCamera(
109
+ cameraId: number,
110
+ page: number,
111
+ limit: number,
112
+ ): Promise<IDataPaginator<any>> {
113
+ const offset = (page - 1) * limit;
114
+
115
+ const query = this._knex("videos as v")
116
+ .innerJoin("folders as f", "v.folderId", "f.id")
117
+ .where("v.cameraId", cameraId)
118
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"));
119
+
120
+ const [countResult] = await query.clone().clearSelect().count("* as count");
121
+ const totalCount = +countResult.count;
122
+ const videos = await query.clone().limit(limit).offset(offset);
123
+
124
+ return {
125
+ success: true,
126
+ data: videos,
127
+ page,
128
+ limit,
129
+ count: videos.length,
130
+ totalCount,
131
+ totalPages: Math.ceil(totalCount / limit),
132
+ };
133
+ }
79
134
  }
@@ -16,7 +16,12 @@ export class FolderDAO implements IBaseDAO<IFolder> {
16
16
  async getById(id: number): Promise<IFolder | null> {
17
17
  const folder = await this._knex("folders as f")
18
18
  .innerJoin("study as s", "f.studyId", "s.id")
19
- .select("f.*", this._knex.raw("to_jsonb(s.*) as study"))
19
+ .leftJoin("cameras as c", "f.cameraId", "c.id")
20
+ .select(
21
+ "f.*",
22
+ this._knex.raw("to_jsonb(s.*) as study"),
23
+ this._knex.raw("to_jsonb(c.*) as camera"),
24
+ )
20
25
  .where("f.id", id)
21
26
  .first();
22
27
  return folder || null;
@@ -25,7 +30,12 @@ export class FolderDAO implements IBaseDAO<IFolder> {
25
30
  async getByUuid(uuid: string): Promise<IFolder | null> {
26
31
  const folder = await this._knex("folders as f")
27
32
  .innerJoin("study as s", "f.studyId", "s.id")
28
- .select("f.*", this._knex.raw("to_jsonb(s.*) as study"))
33
+ .leftJoin("cameras as c", "f.cameraId", "c.id")
34
+ .select(
35
+ "f.*",
36
+ this._knex.raw("to_jsonb(s.*) as study"),
37
+ this._knex.raw("to_jsonb(c.*) as camera"),
38
+ )
29
39
  .where("f.uuid", uuid)
30
40
  .first();
31
41
  return folder || null;
@@ -53,7 +63,12 @@ export class FolderDAO implements IBaseDAO<IFolder> {
53
63
 
54
64
  const query = this._knex("folders as f")
55
65
  .innerJoin("study as s", "f.studyId", "s.id")
56
- .select("f.*", this._knex.raw("to_jsonb(s.*) as study"));
66
+ .leftJoin("cameras as c", "f.cameraId", "c.id")
67
+ .select(
68
+ "f.*",
69
+ this._knex.raw("to_jsonb(s.*) as study"),
70
+ this._knex.raw("to_jsonb(c.*) as camera"),
71
+ );
57
72
  if (studyId !== undefined && studyId !== null) {
58
73
  query.where("f.studyId", studyId);
59
74
  }
@@ -245,4 +245,51 @@ export class VideoDAO implements IBaseDAO<IVideo> {
245
245
  throw error;
246
246
  }
247
247
  }
248
+
249
+ async getVideoIdsByFolderId(folderId: number): Promise<number[]> {
250
+ const videos = await this._knex("video")
251
+ .where("folderId", folderId)
252
+ .select("id");
253
+ return videos.map((video) => video.id);
254
+ }
255
+
256
+ async bulkUpdateCamera(
257
+ videoIds: number[],
258
+ cameraId: number | null,
259
+ trx?: Knex.Transaction,
260
+ ): Promise<number> {
261
+ const knexInstance = trx || this._knex;
262
+ const result = await knexInstance("video").whereIn("id", videoIds).update({
263
+ cameraId: cameraId,
264
+ updated_at: knexInstance.fn.now(),
265
+ });
266
+ return result;
267
+ }
268
+
269
+ async getVideosByCameraIdWithFolder(
270
+ cameraId: number,
271
+ page: number,
272
+ limit: number,
273
+ ): Promise<IDataPaginator<IVideo>> {
274
+ const offset = (page - 1) * limit;
275
+
276
+ const query = this._knex("video as v")
277
+ .innerJoin("folders as f", "v.folderId", "f.id")
278
+ .where("v.cameraId", cameraId)
279
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"));
280
+
281
+ const [countResult] = await query.clone().clearSelect().count("* as count");
282
+ const totalCount = +countResult.count;
283
+ const videos = await query.clone().limit(limit).offset(offset);
284
+
285
+ return {
286
+ success: true,
287
+ data: videos,
288
+ page,
289
+ limit,
290
+ count: videos.length,
291
+ totalCount,
292
+ totalPages: Math.ceil(totalCount / limit),
293
+ };
294
+ }
248
295
  }