@trafficgroup/knex-rel 0.1.7 → 0.1.9

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.
Files changed (51) hide show
  1. package/dist/dao/VideoMinuteResultDAO.d.ts +40 -0
  2. package/dist/dao/VideoMinuteResultDAO.js +149 -0
  3. package/dist/dao/VideoMinuteResultDAO.js.map +1 -1
  4. package/dist/dao/batch/batch.dao.d.ts +27 -0
  5. package/dist/dao/batch/batch.dao.js +135 -0
  6. package/dist/dao/batch/batch.dao.js.map +1 -0
  7. package/dist/dao/camera/camera.dao.d.ts +17 -7
  8. package/dist/dao/camera/camera.dao.js +33 -48
  9. package/dist/dao/camera/camera.dao.js.map +1 -1
  10. package/dist/dao/folder/folder.dao.js +2 -1
  11. package/dist/dao/folder/folder.dao.js.map +1 -1
  12. package/dist/dao/location/location.dao.d.ts +17 -0
  13. package/dist/dao/location/location.dao.js +123 -0
  14. package/dist/dao/location/location.dao.js.map +1 -0
  15. package/dist/dao/study/study.dao.d.ts +1 -1
  16. package/dist/dao/study/study.dao.js +10 -10
  17. package/dist/dao/study/study.dao.js.map +1 -1
  18. package/dist/dao/video/video.dao.d.ts +30 -0
  19. package/dist/dao/video/video.dao.js +113 -1
  20. package/dist/dao/video/video.dao.js.map +1 -1
  21. package/dist/index.d.ts +6 -1
  22. package/dist/index.js +5 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/interfaces/batch/batch.interfaces.d.ts +13 -0
  25. package/dist/interfaces/batch/batch.interfaces.js +3 -0
  26. package/dist/interfaces/batch/batch.interfaces.js.map +1 -0
  27. package/dist/interfaces/camera/camera.interfaces.d.ts +4 -2
  28. package/dist/interfaces/location/location.interfaces.d.ts +9 -0
  29. package/dist/interfaces/location/location.interfaces.js +3 -0
  30. package/dist/interfaces/location/location.interfaces.js.map +1 -0
  31. package/dist/interfaces/study/study.interfaces.d.ts +4 -3
  32. package/dist/interfaces/video/video.interfaces.d.ts +9 -0
  33. package/migrations/20251020225758_migration.ts +135 -0
  34. package/migrations/20251112120000_migration.ts +89 -0
  35. package/migrations/20251112120100_migration.ts +21 -0
  36. package/migrations/20251112120200_migration.ts +50 -0
  37. package/migrations/20251112120300_migration.ts +27 -0
  38. package/package.json +1 -1
  39. package/src/dao/VideoMinuteResultDAO.ts +237 -0
  40. package/src/dao/batch/batch.dao.ts +121 -0
  41. package/src/dao/camera/camera.dao.ts +44 -61
  42. package/src/dao/folder/folder.dao.ts +7 -1
  43. package/src/dao/location/location.dao.ts +123 -0
  44. package/src/dao/study/study.dao.ts +10 -10
  45. package/src/dao/video/video.dao.ts +135 -1
  46. package/src/index.ts +13 -1
  47. package/src/interfaces/batch/batch.interfaces.ts +14 -0
  48. package/src/interfaces/camera/camera.interfaces.ts +4 -2
  49. package/src/interfaces/location/location.interfaces.ts +9 -0
  50. package/src/interfaces/study/study.interfaces.ts +4 -3
  51. package/src/interfaces/video/video.interfaces.ts +13 -1
@@ -0,0 +1,121 @@
1
+ import { Knex } from "knex";
2
+ import { IBaseDAO, IDataPaginator } from "../../d.types";
3
+ import { IBatch } from "../../interfaces/batch/batch.interfaces";
4
+ import KnexManager from "../../KnexConnection";
5
+
6
+ export class BatchDAO implements IBaseDAO<IBatch> {
7
+ private _knex: Knex<any, unknown[]> = KnexManager.getConnection();
8
+
9
+ async create(item: IBatch): Promise<IBatch> {
10
+ const [createdBatch] = await this._knex("video_batch")
11
+ .insert(item)
12
+ .returning("*");
13
+ return createdBatch;
14
+ }
15
+
16
+ async getById(id: number): Promise<IBatch | null> {
17
+ const batch = await this._knex("video_batch as b")
18
+ .innerJoin("folders as f", "b.folderId", "f.id")
19
+ .select("b.*", this._knex.raw("to_jsonb(f.*) as folder"))
20
+ .where("b.id", id)
21
+ .first();
22
+ return batch || null;
23
+ }
24
+
25
+ async getByUuid(uuid: string): Promise<IBatch | null> {
26
+ const batch = await this._knex("video_batch as b")
27
+ .innerJoin("folders as f", "b.folderId", "f.id")
28
+ .select("b.*", this._knex.raw("to_jsonb(f.*) as folder"))
29
+ .where("b.uuid", uuid)
30
+ .first();
31
+ return batch || null;
32
+ }
33
+
34
+ async update(id: number, item: Partial<IBatch>): Promise<IBatch | null> {
35
+ const [updatedBatch] = await this._knex("video_batch")
36
+ .where({ id })
37
+ .update(item)
38
+ .returning("*");
39
+ return updatedBatch || null;
40
+ }
41
+
42
+ async delete(id: number): Promise<boolean> {
43
+ const result = await this._knex("video_batch").where({ id }).del();
44
+ return result > 0;
45
+ }
46
+
47
+ async getAll(
48
+ page: number,
49
+ limit: number,
50
+ folderId?: number | null,
51
+ ): Promise<IDataPaginator<IBatch>> {
52
+ const offset = (page - 1) * limit;
53
+
54
+ const query = this._knex("video_batch as b")
55
+ .innerJoin("folders as f", "b.folderId", "f.id")
56
+ .select("b.*", this._knex.raw("to_jsonb(f.*) as folder"));
57
+ if (folderId !== undefined && folderId !== null) {
58
+ query.where("b.folderId", folderId);
59
+ }
60
+
61
+ const [countResult] = await query.clone().clearSelect().count("* as count");
62
+ const totalCount = +countResult.count;
63
+ const batches = await query.clone().limit(limit).offset(offset);
64
+
65
+ return {
66
+ success: true,
67
+ data: batches,
68
+ page,
69
+ limit,
70
+ count: batches.length,
71
+ totalCount,
72
+ totalPages: Math.ceil(totalCount / limit),
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Get batches by folder ID
78
+ */
79
+ async getByFolderId(folderId: number): Promise<IBatch[]> {
80
+ return this._knex("video_batch as b")
81
+ .innerJoin("folders as f", "b.folderId", "f.id")
82
+ .select("b.*", this._knex.raw("to_jsonb(f.*) as folder"))
83
+ .where("b.folderId", folderId)
84
+ .orderBy("b.created_at", "desc");
85
+ }
86
+
87
+ /**
88
+ * Update batch progress
89
+ */
90
+ async updateProgress(
91
+ id: number,
92
+ completedVideos: number,
93
+ failedVideos: number,
94
+ ): Promise<void> {
95
+ await this._knex("video_batch").where({ id }).update({
96
+ completedVideos,
97
+ failedVideos,
98
+ updated_at: this._knex.fn.now(),
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Mark batch as completed
104
+ */
105
+ async markCompleted(id: number): Promise<void> {
106
+ await this._knex("video_batch").where({ id }).update({
107
+ status: "COMPLETED",
108
+ updated_at: this._knex.fn.now(),
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Mark batch as failed
114
+ */
115
+ async markFailed(id: number): Promise<void> {
116
+ await this._knex("video_batch").where({ id }).update({
117
+ status: "FAILED",
118
+ updated_at: this._knex.fn.now(),
119
+ });
120
+ }
121
+ }
@@ -1,7 +1,6 @@
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";
5
4
  import KnexManager from "../../KnexConnection";
6
5
 
7
6
  export class CameraDAO implements IBaseDAO<ICamera> {
@@ -55,45 +54,43 @@ export class CameraDAO implements IBaseDAO<ICamera> {
55
54
  };
56
55
  }
57
56
 
58
- async getByName(name: string): Promise<ICamera | null> {
59
- const camera = await this._knex("cameras").where({ name }).first();
60
- return camera || null;
61
- }
62
-
63
- async getCamerasNearCoordinates(
64
- longitude: number,
65
- latitude: number,
66
- radiusKm: number = 1,
57
+ /**
58
+ * Get cameras for a specific location
59
+ * @param locationId - The location ID
60
+ * @param status - Optional filter by camera status
61
+ */
62
+ async getCamerasByLocationId(
63
+ locationId: number,
64
+ status?: string,
67
65
  ): Promise<ICamera[]> {
68
- // Using ST_DWithin for geographic distance calculation
69
- // This is a PostgreSQL-specific query for geospatial operations
70
- const cameras = await this._knex("cameras").whereRaw(
71
- `ST_DWithin(
72
- ST_MakePoint(longitude, latitude)::geography,
73
- ST_MakePoint(?, ?)::geography,
74
- ?
75
- )`,
76
- [longitude, latitude, radiusKm * 1000], // Convert km to meters
77
- );
78
- return cameras;
66
+ let query = this._knex("cameras").where("locationId", locationId);
67
+
68
+ if (status) {
69
+ query = query.where("status", status);
70
+ }
71
+
72
+ return query.orderBy("name", "asc");
79
73
  }
80
74
 
81
75
  /**
82
- * Get all cameras with optional search filter by name (case-insensitive partial match)
76
+ * Get cameras for a specific location with pagination
77
+ * @param locationId - The location ID
78
+ * @param page - Page number
79
+ * @param limit - Items per page
80
+ * @param status - Optional filter by camera status
83
81
  */
84
- async getAllWithSearch(
82
+ async getCamerasByLocationIdPaginated(
83
+ locationId: number,
85
84
  page: number,
86
85
  limit: number,
87
- name?: string,
86
+ status?: string,
88
87
  ): Promise<IDataPaginator<ICamera>> {
89
88
  const offset = (page - 1) * limit;
90
89
 
91
- let query = this._knex("cameras");
90
+ let query = this._knex("cameras").where("locationId", locationId);
92
91
 
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}%`);
92
+ if (status) {
93
+ query = query.where("status", status);
97
94
  }
98
95
 
99
96
  const [countResult] = await query.clone().count("* as count");
@@ -116,39 +113,25 @@ export class CameraDAO implements IBaseDAO<ICamera> {
116
113
  }
117
114
 
118
115
  /**
119
- * Get paginated videos for a specific camera with folder data included
116
+ * Check if a camera name already exists for a location
117
+ * @param name - Camera name
118
+ * @param locationId - Location ID
119
+ * @param excludeId - Optional camera ID to exclude (for update operations)
120
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");
121
+ async existsByNameAndLocation(
122
+ name: string,
123
+ locationId: number,
124
+ excludeId?: number,
125
+ ): Promise<boolean> {
126
+ let query = this._knex("cameras")
127
+ .where("locationId", locationId)
128
+ .where("name", name);
129
+
130
+ if (excludeId !== undefined) {
131
+ query = query.whereNot("id", excludeId);
132
+ }
143
133
 
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
- };
134
+ const result = await query.first();
135
+ return result !== null && result !== undefined;
153
136
  }
154
137
  }
@@ -53,7 +53,13 @@ export class FolderDAO implements IBaseDAO<IFolder> {
53
53
 
54
54
  const query = this._knex("folders as f")
55
55
  .innerJoin("study as s", "f.studyId", "s.id")
56
- .select("f.*", this._knex.raw("to_jsonb(s.*) as study"));
56
+ .leftJoin("locations as l", "s.locationId", "l.id")
57
+ .select(
58
+ "f.*",
59
+ this._knex.raw("to_jsonb(s.*) as study"),
60
+ "l.name as locationName",
61
+ "l.uuid as locationUuid",
62
+ );
57
63
  if (studyId !== undefined && studyId !== null) {
58
64
  query.where("f.studyId", studyId);
59
65
  }
@@ -0,0 +1,123 @@
1
+ import { Knex } from "knex";
2
+ import { IBaseDAO, IDataPaginator } from "../../d.types";
3
+ import { ILocation } from "../../interfaces/location/location.interfaces";
4
+ import { IVideo } from "../../interfaces/video/video.interfaces";
5
+ import KnexManager from "../../KnexConnection";
6
+
7
+ export class LocationDAO implements IBaseDAO<ILocation> {
8
+ private _knex: Knex<any, unknown[]> = KnexManager.getConnection();
9
+
10
+ async create(item: ILocation): Promise<ILocation> {
11
+ const [createdLocation] = await this._knex("locations")
12
+ .insert(item)
13
+ .returning("*");
14
+ return createdLocation;
15
+ }
16
+
17
+ async getById(id: number): Promise<ILocation | null> {
18
+ const location = await this._knex("locations").where({ id }).first();
19
+ return location || null;
20
+ }
21
+
22
+ async getByUuid(uuid: string): Promise<ILocation | null> {
23
+ const location = await this._knex("locations").where({ uuid }).first();
24
+ return location || null;
25
+ }
26
+
27
+ async update(
28
+ id: number,
29
+ item: Partial<ILocation>,
30
+ ): Promise<ILocation | null> {
31
+ const [updatedLocation] = await this._knex("locations")
32
+ .where({ id })
33
+ .update(item)
34
+ .returning("*");
35
+ return updatedLocation || null;
36
+ }
37
+
38
+ async delete(id: number): Promise<boolean> {
39
+ const result = await this._knex("locations").where({ id }).del();
40
+ return result > 0;
41
+ }
42
+
43
+ async getAll(
44
+ page: number,
45
+ limit: number,
46
+ ): Promise<IDataPaginator<ILocation>> {
47
+ const offset = (page - 1) * limit;
48
+
49
+ const [countResult] = await this._knex("locations").count("* as count");
50
+ const totalCount = +countResult.count;
51
+ const locations = await this._knex("locations").limit(limit).offset(offset);
52
+
53
+ return {
54
+ success: true,
55
+ data: locations,
56
+ page,
57
+ limit,
58
+ count: locations.length,
59
+ totalCount,
60
+ totalPages: Math.ceil(totalCount / limit),
61
+ };
62
+ }
63
+
64
+ async getByName(name: string): Promise<ILocation | null> {
65
+ const location = await this._knex("locations").where({ name }).first();
66
+ return location || null;
67
+ }
68
+
69
+ async getLocationsNearCoordinates(
70
+ longitude: number,
71
+ latitude: number,
72
+ radiusKm: number = 1,
73
+ ): Promise<ILocation[]> {
74
+ // Using ST_DWithin for geographic distance calculation
75
+ // This is a PostgreSQL-specific query for geospatial operations
76
+ const locations = await this._knex("locations").whereRaw(
77
+ `ST_DWithin(
78
+ ST_MakePoint(longitude, latitude)::geography,
79
+ ST_MakePoint(?, ?)::geography,
80
+ ?
81
+ )`,
82
+ [longitude, latitude, radiusKm * 1000], // Convert km to meters
83
+ );
84
+ return locations;
85
+ }
86
+
87
+ /**
88
+ * Get all locations with optional search filter by name (case-insensitive partial match)
89
+ */
90
+ async getAllWithSearch(
91
+ page: number,
92
+ limit: number,
93
+ name?: string,
94
+ ): Promise<IDataPaginator<ILocation>> {
95
+ const offset = (page - 1) * limit;
96
+
97
+ let query = this._knex("locations");
98
+
99
+ // Apply search filter if name provided (escape special chars to prevent pattern injection)
100
+ if (name && name.trim().length > 0) {
101
+ const escapedName = name.trim().replace(/[%_\\]/g, "\\$&");
102
+ query = query.where("name", "ilike", `%${escapedName}%`);
103
+ }
104
+
105
+ const [countResult] = await query.clone().count("* as count");
106
+ const totalCount = +countResult.count;
107
+ const locations = await query
108
+ .clone()
109
+ .limit(limit)
110
+ .offset(offset)
111
+ .orderBy("name", "asc");
112
+
113
+ return {
114
+ success: true,
115
+ data: locations,
116
+ page,
117
+ limit,
118
+ count: locations.length,
119
+ totalCount,
120
+ totalPages: Math.ceil(totalCount / limit),
121
+ };
122
+ }
123
+ }
@@ -16,11 +16,11 @@ export class StudyDAO implements IBaseDAO<IStudy> {
16
16
  async getById(id: number): Promise<IStudy | null> {
17
17
  const study = await this._knex("study as s")
18
18
  .innerJoin("users as u", "s.createdBy", "u.id")
19
- .leftJoin("cameras as c", "s.cameraId", "c.id")
19
+ .leftJoin("locations as l", "s.locationId", "l.id")
20
20
  .select(
21
21
  "s.*",
22
22
  this._knex.raw("to_jsonb(u.*) as user"),
23
- this._knex.raw("to_jsonb(c.*) as camera"),
23
+ this._knex.raw("to_jsonb(l.*) as location"),
24
24
  )
25
25
  .where("s.id", id)
26
26
  .first();
@@ -30,11 +30,11 @@ export class StudyDAO implements IBaseDAO<IStudy> {
30
30
  async getByUuid(uuid: string): Promise<IStudy | null> {
31
31
  const study = await this._knex("study as s")
32
32
  .innerJoin("users as u", "s.createdBy", "u.id")
33
- .leftJoin("cameras as c", "s.cameraId", "c.id")
33
+ .leftJoin("locations as l", "s.locationId", "l.id")
34
34
  .select(
35
35
  "s.*",
36
36
  this._knex.raw("to_jsonb(u.*) as user"),
37
- this._knex.raw("to_jsonb(c.*) as camera"),
37
+ this._knex.raw("to_jsonb(l.*) as location"),
38
38
  )
39
39
  .where("s.uuid", uuid)
40
40
  .first();
@@ -63,11 +63,11 @@ export class StudyDAO implements IBaseDAO<IStudy> {
63
63
 
64
64
  const query = this._knex("study as s")
65
65
  .innerJoin("users as u", "s.createdBy", "u.id")
66
- .leftJoin("cameras as c", "s.cameraId", "c.id")
66
+ .leftJoin("locations as l", "s.locationId", "l.id")
67
67
  .select(
68
68
  "s.*",
69
69
  this._knex.raw("to_jsonb(u.*) as user"),
70
- this._knex.raw("to_jsonb(c.*) as camera"),
70
+ this._knex.raw("to_jsonb(l.*) as location"),
71
71
  );
72
72
  if (createdBy !== undefined && createdBy !== null) {
73
73
  query.where("s.createdBy", createdBy);
@@ -88,16 +88,16 @@ export class StudyDAO implements IBaseDAO<IStudy> {
88
88
  };
89
89
  }
90
90
 
91
- async getStudiesByCamera(cameraId: number): Promise<IStudy[]> {
91
+ async getStudiesByLocation(locationId: number): Promise<IStudy[]> {
92
92
  const studies = await this._knex("study as s")
93
93
  .innerJoin("users as u", "s.createdBy", "u.id")
94
- .leftJoin("cameras as c", "s.cameraId", "c.id")
94
+ .leftJoin("locations as l", "s.locationId", "l.id")
95
95
  .select(
96
96
  "s.*",
97
97
  this._knex.raw("to_jsonb(u.*) as user"),
98
- this._knex.raw("to_jsonb(c.*) as camera"),
98
+ this._knex.raw("to_jsonb(l.*) as location"),
99
99
  )
100
- .where("s.cameraId", cameraId)
100
+ .where("s.locationId", locationId)
101
101
  .orderBy("s.created_at", "desc")
102
102
  .limit(10);
103
103
 
@@ -58,7 +58,13 @@ export class VideoDAO implements IBaseDAO<IVideo> {
58
58
 
59
59
  const query = this._knex("video as v")
60
60
  .innerJoin("folders as f", "v.folderId", "f.id")
61
- .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"));
61
+ .leftJoin("cameras as c", "v.cameraId", "c.id")
62
+ .select(
63
+ "v.*",
64
+ this._knex.raw("to_jsonb(f.*) as folder"),
65
+ "c.name as cameraName",
66
+ "c.uuid as cameraUuid",
67
+ );
62
68
  if (folderId !== undefined && folderId !== null) {
63
69
  query.where("v.folderId", folderId);
64
70
  }
@@ -257,4 +263,132 @@ export class VideoDAO implements IBaseDAO<IVideo> {
257
263
 
258
264
  return rows.map((row) => row.id);
259
265
  }
266
+
267
+ /**
268
+ * Get videos by batch ID
269
+ */
270
+ async getByBatchId(batchId: number): Promise<IVideo[]> {
271
+ return this._knex("video as v")
272
+ .innerJoin("folders as f", "v.folderId", "f.id")
273
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"))
274
+ .where("v.batchId", batchId)
275
+ .orderBy("v.created_at", "asc");
276
+ }
277
+
278
+ /**
279
+ * Get videos sorted chronologically by recording start time for a study
280
+ */
281
+ async getChronologicalVideos(
282
+ studyId: number,
283
+ startDate?: Date,
284
+ endDate?: Date,
285
+ ): Promise<IDataPaginator<IVideo>> {
286
+ let query = this._knex("video as v")
287
+ .innerJoin("folders as f", "v.folderId", "f.id")
288
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"))
289
+ .where("f.studyId", studyId)
290
+ .whereNotNull("v.recordingStartedAt");
291
+
292
+ if (startDate) {
293
+ query = query.where("v.recordingStartedAt", ">=", startDate);
294
+ }
295
+
296
+ if (endDate) {
297
+ query = query.where("v.recordingStartedAt", "<=", endDate);
298
+ }
299
+
300
+ const [countResult] = await query.clone().clearSelect().count("* as count");
301
+ const totalCount = +countResult.count;
302
+ const videos = await query.clone().orderBy("v.recordingStartedAt", "asc");
303
+
304
+ return {
305
+ success: true,
306
+ data: videos,
307
+ page: 1,
308
+ limit: totalCount,
309
+ count: videos.length,
310
+ totalCount,
311
+ totalPages: 1,
312
+ };
313
+ }
314
+
315
+ /**
316
+ * Update recording start time for a video
317
+ */
318
+ async updateRecordingStartTime(
319
+ id: number,
320
+ recordingStartedAt: Date,
321
+ ): Promise<void> {
322
+ await this._knex("video").where({ id }).update({
323
+ recordingStartedAt,
324
+ updated_at: this._knex.fn.now(),
325
+ });
326
+ }
327
+
328
+ /**
329
+ * Update trim settings for a video
330
+ */
331
+ async updateTrimSettings(
332
+ id: number,
333
+ trimEnabled: boolean,
334
+ trimPeriods: { startTime: string; endTime: string }[] | null,
335
+ ): Promise<void> {
336
+ await this._knex("video")
337
+ .where({ id })
338
+ .update({
339
+ trimEnabled,
340
+ trimPeriods: trimPeriods ? JSON.stringify(trimPeriods) : null,
341
+ updated_at: this._knex.fn.now(),
342
+ });
343
+ }
344
+
345
+ /**
346
+ * Get videos with trimming enabled for a folder
347
+ */
348
+ async getVideosWithTrimming(folderId: number): Promise<IVideo[]> {
349
+ return this._knex("video as v")
350
+ .innerJoin("folders as f", "v.folderId", "f.id")
351
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"))
352
+ .where("v.folderId", folderId)
353
+ .where("v.trimEnabled", true)
354
+ .orderBy("v.recordingStartedAt", "asc");
355
+ }
356
+
357
+ /**
358
+ * Get paginated videos for a specific camera
359
+ * @param cameraId - The camera ID
360
+ * @param page - Page number
361
+ * @param limit - Items per page
362
+ */
363
+ async getVideosByCameraId(
364
+ cameraId: number,
365
+ page: number,
366
+ limit: number,
367
+ ): Promise<IDataPaginator<IVideo>> {
368
+ const offset = (page - 1) * limit;
369
+
370
+ const query = this._knex("video as v")
371
+ .innerJoin("folders as f", "v.folderId", "f.id")
372
+ .select("v.*", this._knex.raw("to_jsonb(f.*) as folder"))
373
+ .where("v.cameraId", cameraId);
374
+
375
+ const [countResult] = await query.clone().clearSelect().count("* as count");
376
+ const totalCount = +countResult.count;
377
+
378
+ const videos = await query
379
+ .clone()
380
+ .limit(limit)
381
+ .offset(offset)
382
+ .orderBy("v.created_at", "desc");
383
+
384
+ return {
385
+ success: true,
386
+ data: videos,
387
+ page,
388
+ limit,
389
+ count: videos.length,
390
+ totalCount,
391
+ totalPages: Math.ceil(totalCount / limit),
392
+ };
393
+ }
260
394
  }
package/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  // DAOs
2
2
  export { AuthDAO } from "./dao/auth/auth.dao";
3
+ export { BatchDAO } from "./dao/batch/batch.dao";
3
4
  export { CameraDAO } from "./dao/camera/camera.dao";
4
5
  export { ChatDAO } from "./dao/chat/chat.dao";
5
6
  export { FolderDAO } from "./dao/folder/folder.dao";
7
+ export { LocationDAO } from "./dao/location/location.dao";
6
8
  export { MessageDAO } from "./dao/message/message.dao";
7
9
  export { ReportConfigurationDAO } from "./dao/report-configuration/report-configuration.dao";
8
10
  export { StudyDAO } from "./dao/study/study.dao";
@@ -14,6 +16,7 @@ export { VideoMinuteResultDAO } from "./dao/VideoMinuteResultDAO";
14
16
  // Interfaces
15
17
  export { IDataPaginator } from "./d.types";
16
18
  export { IAuth } from "./interfaces/auth/auth.interfaces";
19
+ export { IBatch } from "./interfaces/batch/batch.interfaces";
17
20
  export { ICamera } from "./interfaces/camera/camera.interfaces";
18
21
  export {
19
22
  IChat,
@@ -21,6 +24,7 @@ export {
21
24
  IChatUpdate,
22
25
  } from "./interfaces/chat/chat.interfaces";
23
26
  export { IFolder } from "./interfaces/folder/folder.interfaces";
27
+ export { ILocation } from "./interfaces/location/location.interfaces";
24
28
  export {
25
29
  IMessage,
26
30
  IMessageCreate,
@@ -36,12 +40,20 @@ export {
36
40
  export { IStudy } from "./interfaces/study/study.interfaces";
37
41
  export { IUser } from "./interfaces/user/user.interfaces";
38
42
  export { IUserPushNotificationToken } from "./interfaces/user-push-notification-token/user-push-notification-token.interfaces";
39
- export { IVideo } from "./interfaces/video/video.interfaces";
43
+ export { IVideo, ITrimPeriod } from "./interfaces/video/video.interfaces";
40
44
  export {
41
45
  IVideoMinuteResult,
42
46
  IVideoMinuteResultInput,
43
47
  IVideoMinuteBatch,
44
48
  } from "./entities/VideoMinuteResult";
49
+ export type {
50
+ IStudyTimeGroupResult,
51
+ IGroupedStudyResponse,
52
+ IGroupedResponse,
53
+ IGroupedResult,
54
+ ITMCResult,
55
+ IATRResult,
56
+ } from "./dao/VideoMinuteResultDAO";
45
57
 
46
58
  import KnexManager from "./KnexConnection";
47
59
  export { KnexManager };
@@ -0,0 +1,14 @@
1
+ import type { IFolder } from "../folder/folder.interfaces";
2
+
3
+ export interface IBatch {
4
+ id: number;
5
+ uuid: string;
6
+ folderId: number;
7
+ status: "PENDING" | "IN_PROGRESS" | "COMPLETED" | "FAILED";
8
+ totalVideos: number;
9
+ completedVideos: number;
10
+ failedVideos: number;
11
+ created_at: string;
12
+ updated_at: string;
13
+ folder?: IFolder;
14
+ }
@@ -1,9 +1,11 @@
1
1
  export interface ICamera {
2
2
  id: number;
3
3
  uuid: string;
4
+ locationId: number;
4
5
  name: string;
5
- longitude: number;
6
- latitude: number;
6
+ description?: string;
7
+ status: "ACTIVE" | "INACTIVE" | "MAINTENANCE";
8
+ metadata: Record<string, any>;
7
9
  created_at: string;
8
10
  updated_at: string;
9
11
  }