@trafficgroup/knex-rel 0.1.4 → 0.1.5
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.
- package/dist/dao/camera/camera.dao.d.ts +9 -0
- package/dist/dao/camera/camera.dao.js +61 -0
- package/dist/dao/camera/camera.dao.js.map +1 -1
- package/dist/dao/video/video.dao.d.ts +13 -0
- package/dist/dao/video/video.dao.js +63 -0
- package/dist/dao/video/video.dao.js.map +1 -1
- package/package.json +1 -1
- package/plan.md +596 -799
- package/src/dao/camera/camera.dao.ts +75 -0
- package/src/dao/video/video.dao.ts +75 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Knex } from "knex";
|
|
2
2
|
import { IBaseDAO, IDataPaginator } from "../../d.types";
|
|
3
3
|
import { ICamera } from "../../interfaces/camera/camera.interfaces";
|
|
4
|
+
import { IVideo } from "../../interfaces/video/video.interfaces";
|
|
4
5
|
import KnexManager from "../../KnexConnection";
|
|
5
6
|
|
|
6
7
|
export class CameraDAO implements IBaseDAO<ICamera> {
|
|
@@ -76,4 +77,78 @@ export class CameraDAO implements IBaseDAO<ICamera> {
|
|
|
76
77
|
);
|
|
77
78
|
return cameras;
|
|
78
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get all cameras with optional search filter by name (case-insensitive partial match)
|
|
83
|
+
*/
|
|
84
|
+
async getAllWithSearch(
|
|
85
|
+
page: number,
|
|
86
|
+
limit: number,
|
|
87
|
+
name?: string,
|
|
88
|
+
): Promise<IDataPaginator<ICamera>> {
|
|
89
|
+
const offset = (page - 1) * limit;
|
|
90
|
+
|
|
91
|
+
let query = this._knex("cameras");
|
|
92
|
+
|
|
93
|
+
// Apply search filter if name provided (escape special chars to prevent pattern injection)
|
|
94
|
+
if (name && name.trim().length > 0) {
|
|
95
|
+
const escapedName = name.trim().replace(/[%_\\]/g, "\\$&");
|
|
96
|
+
query = query.where("name", "ilike", `%${escapedName}%`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const [countResult] = await query.clone().count("* as count");
|
|
100
|
+
const totalCount = +countResult.count;
|
|
101
|
+
const cameras = await query
|
|
102
|
+
.clone()
|
|
103
|
+
.limit(limit)
|
|
104
|
+
.offset(offset)
|
|
105
|
+
.orderBy("name", "asc");
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
data: cameras,
|
|
110
|
+
page,
|
|
111
|
+
limit,
|
|
112
|
+
count: cameras.length,
|
|
113
|
+
totalCount,
|
|
114
|
+
totalPages: Math.ceil(totalCount / limit),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get paginated videos for a specific camera with folder data included
|
|
120
|
+
*/
|
|
121
|
+
async getVideosByCamera(
|
|
122
|
+
cameraId: number,
|
|
123
|
+
page: number,
|
|
124
|
+
limit: number,
|
|
125
|
+
): Promise<IDataPaginator<IVideo>> {
|
|
126
|
+
const offset = (page - 1) * limit;
|
|
127
|
+
|
|
128
|
+
const query = this._knex("video as v")
|
|
129
|
+
.innerJoin("folders as f", "v.folderId", "f.id")
|
|
130
|
+
.select("v.*", this._knex.raw("to_jsonb(f.*) as folder"))
|
|
131
|
+
.where("v.cameraId", cameraId);
|
|
132
|
+
|
|
133
|
+
// Optimized count query without JOIN
|
|
134
|
+
const [countResult] = await this._knex("video as v")
|
|
135
|
+
.where("v.cameraId", cameraId)
|
|
136
|
+
.count("* as count");
|
|
137
|
+
const totalCount = +countResult.count;
|
|
138
|
+
const videos = await query
|
|
139
|
+
.clone()
|
|
140
|
+
.limit(limit)
|
|
141
|
+
.offset(offset)
|
|
142
|
+
.orderBy("v.created_at", "desc");
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
data: videos,
|
|
147
|
+
page,
|
|
148
|
+
limit,
|
|
149
|
+
count: videos.length,
|
|
150
|
+
totalCount,
|
|
151
|
+
totalPages: Math.ceil(totalCount / limit),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
79
154
|
}
|
|
@@ -245,4 +245,79 @@ export class VideoDAO implements IBaseDAO<IVideo> {
|
|
|
245
245
|
throw error;
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get all video IDs for a specific folder (used for cascade operations)
|
|
251
|
+
*/
|
|
252
|
+
async getVideoIdsByFolderId(folderId: number): Promise<number[]> {
|
|
253
|
+
const rows = await this._knex("video")
|
|
254
|
+
.where({ folderId })
|
|
255
|
+
.select("id")
|
|
256
|
+
.orderBy("id", "asc");
|
|
257
|
+
|
|
258
|
+
return rows.map((row) => row.id);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Bulk update camera assignment for multiple videos
|
|
263
|
+
* Supports optional transaction for service-level transaction management
|
|
264
|
+
*/
|
|
265
|
+
async bulkUpdateCamera(
|
|
266
|
+
videoIds: number[],
|
|
267
|
+
cameraId: number | null,
|
|
268
|
+
trx?: Knex.Transaction,
|
|
269
|
+
): Promise<number> {
|
|
270
|
+
if (!videoIds || videoIds.length === 0) {
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const query = trx || this._knex;
|
|
275
|
+
|
|
276
|
+
const result = await query("video")
|
|
277
|
+
.whereIn("id", videoIds)
|
|
278
|
+
.update({
|
|
279
|
+
cameraId: cameraId,
|
|
280
|
+
updated_at: query.fn.now(),
|
|
281
|
+
})
|
|
282
|
+
.returning("id");
|
|
283
|
+
|
|
284
|
+
return result.length;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get videos by camera ID with folder information (paginated)
|
|
289
|
+
*/
|
|
290
|
+
async getVideosByCameraIdWithFolder(
|
|
291
|
+
cameraId: number,
|
|
292
|
+
page: number,
|
|
293
|
+
limit: number,
|
|
294
|
+
): Promise<IDataPaginator<IVideo>> {
|
|
295
|
+
const offset = (page - 1) * limit;
|
|
296
|
+
|
|
297
|
+
const query = this._knex("video as v")
|
|
298
|
+
.innerJoin("folders as f", "v.folderId", "f.id")
|
|
299
|
+
.select("v.*", this._knex.raw("to_jsonb(f.*) as folder"))
|
|
300
|
+
.where("v.cameraId", cameraId);
|
|
301
|
+
|
|
302
|
+
// Optimized count query without JOIN
|
|
303
|
+
const [countResult] = await this._knex("video as v")
|
|
304
|
+
.where("v.cameraId", cameraId)
|
|
305
|
+
.count("* as count");
|
|
306
|
+
const totalCount = +countResult.count;
|
|
307
|
+
const videos = await query
|
|
308
|
+
.clone()
|
|
309
|
+
.limit(limit)
|
|
310
|
+
.offset(offset)
|
|
311
|
+
.orderBy("v.created_at", "desc");
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
data: videos,
|
|
316
|
+
page,
|
|
317
|
+
limit,
|
|
318
|
+
count: videos.length,
|
|
319
|
+
totalCount,
|
|
320
|
+
totalPages: Math.ceil(totalCount / limit),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
248
323
|
}
|