@trafficgroup/knex-rel 0.1.11 → 0.1.12
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/.claude/settings.local.json +2 -5
- package/CLAUDE.md +11 -2
- package/dist/constants/folder.constants.d.ts +12 -0
- package/dist/constants/folder.constants.js +28 -0
- package/dist/constants/folder.constants.js.map +1 -0
- package/dist/constants/video.constants.d.ts +2 -2
- package/dist/constants/video.constants.js +9 -5
- package/dist/constants/video.constants.js.map +1 -1
- package/dist/dao/VideoMinuteResultDAO.d.ts +1 -1
- package/dist/dao/VideoMinuteResultDAO.js +29 -23
- package/dist/dao/VideoMinuteResultDAO.js.map +1 -1
- package/dist/dao/auth/auth.dao.js +4 -1
- package/dist/dao/auth/auth.dao.js.map +1 -1
- package/dist/dao/batch/batch.dao.js +13 -14
- package/dist/dao/batch/batch.dao.js.map +1 -1
- package/dist/dao/camera/camera.dao.js +10 -7
- package/dist/dao/camera/camera.dao.js.map +1 -1
- package/dist/dao/chat/chat.dao.d.ts +1 -1
- package/dist/dao/chat/chat.dao.js +27 -40
- package/dist/dao/chat/chat.dao.js.map +1 -1
- package/dist/dao/folder/folder.dao.d.ts +10 -1
- package/dist/dao/folder/folder.dao.js +44 -6
- package/dist/dao/folder/folder.dao.js.map +1 -1
- package/dist/dao/location/location.dao.js +16 -9
- package/dist/dao/location/location.dao.js.map +1 -1
- package/dist/dao/message/message.dao.d.ts +1 -1
- package/dist/dao/message/message.dao.js +18 -26
- package/dist/dao/message/message.dao.js.map +1 -1
- package/dist/dao/report-configuration/report-configuration.dao.js +32 -31
- package/dist/dao/report-configuration/report-configuration.dao.js.map +1 -1
- package/dist/dao/study/study.dao.js +7 -2
- package/dist/dao/study/study.dao.js.map +1 -1
- package/dist/dao/user/user.dao.js +4 -1
- package/dist/dao/user/user.dao.js.map +1 -1
- package/dist/dao/user-push-notification-token/user-push-notification-token.dao.js +26 -8
- package/dist/dao/user-push-notification-token/user-push-notification-token.dao.js.map +1 -1
- package/dist/dao/video/video.dao.js +30 -28
- package/dist/dao/video/video.dao.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/batch/batch.interfaces.d.ts +1 -1
- package/dist/interfaces/camera/camera.interfaces.d.ts +1 -1
- package/dist/interfaces/chat/chat.interfaces.d.ts +3 -3
- package/dist/interfaces/folder/folder.interfaces.d.ts +1 -1
- package/dist/interfaces/message/message.interfaces.d.ts +2 -2
- package/dist/interfaces/study/study.interfaces.d.ts +2 -2
- package/dist/interfaces/user/user.interfaces.d.ts +1 -1
- package/dist/interfaces/user-push-notification-token/user-push-notification-token.interfaces.d.ts +1 -1
- package/dist/interfaces/video/video.interfaces.d.ts +2 -2
- package/migrations/20250717160737_migration.ts +1 -1
- package/migrations/20250717160908_migration.ts +5 -2
- package/migrations/20250717161310_migration.ts +1 -1
- package/migrations/20250717161406_migration.ts +3 -3
- package/migrations/20250717162431_migration.ts +1 -1
- package/migrations/20250717173228_migration.ts +2 -2
- package/migrations/20250717204731_migration.ts +1 -1
- package/migrations/20250722210109_migration.ts +8 -4
- package/migrations/20250722211019_migration.ts +1 -1
- package/migrations/20250723153852_migration.ts +13 -10
- package/migrations/20250723162257_migration.ts +4 -7
- package/migrations/20250723171109_migration.ts +4 -7
- package/migrations/20250723205331_migration.ts +6 -9
- package/migrations/20250724191345_migration.ts +8 -11
- package/migrations/20250730180932_migration.ts +14 -13
- package/migrations/20250730213625_migration.ts +8 -11
- package/migrations/20250804124509_migration.ts +26 -21
- package/migrations/20250804132053_migration.ts +5 -8
- package/migrations/20250804164518_migration.ts +7 -7
- package/migrations/20250823223016_migration.ts +32 -21
- package/migrations/20250910015452_migration.ts +18 -6
- package/migrations/20250911000000_migration.ts +18 -4
- package/migrations/20250917144153_migration.ts +14 -7
- package/migrations/20250930200521_migration.ts +8 -4
- package/migrations/20251010143500_migration.ts +27 -6
- package/migrations/20251020225758_migration.ts +51 -15
- package/migrations/20251112120000_migration.ts +10 -2
- package/migrations/20251112120200_migration.ts +19 -7
- package/migrations/20251112120300_migration.ts +7 -2
- package/package.json +1 -1
- package/src/constants/folder.constants.ts +29 -0
- package/src/constants/video.constants.ts +11 -7
- package/src/d.types.ts +18 -14
- package/src/dao/VideoMinuteResultDAO.ts +72 -49
- package/src/dao/auth/auth.dao.ts +58 -55
- package/src/dao/batch/batch.dao.ts +101 -98
- package/src/dao/camera/camera.dao.ts +124 -121
- package/src/dao/chat/chat.dao.ts +45 -45
- package/src/dao/folder/folder.dao.ts +112 -55
- package/src/dao/location/location.dao.ts +109 -87
- package/src/dao/message/message.dao.ts +32 -32
- package/src/dao/report-configuration/report-configuration.dao.ts +370 -342
- package/src/dao/study/study.dao.ts +88 -63
- package/src/dao/user/user.dao.ts +52 -50
- package/src/dao/user-push-notification-token/user-push-notification-token.dao.ts +80 -48
- package/src/dao/video/video.dao.ts +385 -334
- package/src/entities/BaseEntity.ts +1 -1
- package/src/index.ts +42 -17
- package/src/interfaces/auth/auth.interfaces.ts +10 -10
- package/src/interfaces/batch/batch.interfaces.ts +1 -1
- package/src/interfaces/camera/camera.interfaces.ts +9 -9
- package/src/interfaces/chat/chat.interfaces.ts +4 -4
- package/src/interfaces/folder/folder.interfaces.ts +2 -2
- package/src/interfaces/location/location.interfaces.ts +7 -7
- package/src/interfaces/message/message.interfaces.ts +3 -3
- package/src/interfaces/report-configuration/report-configuration.interfaces.ts +16 -16
- package/src/interfaces/study/study.interfaces.ts +3 -3
- package/src/interfaces/user/user.interfaces.ts +9 -9
- package/src/interfaces/user-push-notification-token/user-push-notification-token.interfaces.ts +9 -9
- package/src/interfaces/video/video.interfaces.ts +34 -34
|
@@ -69,15 +69,15 @@ interface IGroupedResponse {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
interface IStudyTimeGroupResult {
|
|
72
|
-
absoluteTime: string;
|
|
72
|
+
absoluteTime: string; // ISO 8601 start of bucket
|
|
73
73
|
groupIndex: number;
|
|
74
|
-
startMinute: number;
|
|
75
|
-
endMinute: number;
|
|
76
|
-
label: string;
|
|
74
|
+
startMinute: number; // Start minute number (0-based from video start)
|
|
75
|
+
endMinute: number; // End minute number (0-based from video start)
|
|
76
|
+
label: string; // Formatted label
|
|
77
77
|
results: ITMCResult | IATRResult;
|
|
78
78
|
minuteCount: number;
|
|
79
79
|
videoCount: number;
|
|
80
|
-
contributingVideos: string[];
|
|
80
|
+
contributingVideos: string[]; // Video UUIDs
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
interface IGroupedStudyResponse {
|
|
@@ -87,7 +87,7 @@ interface IGroupedStudyResponse {
|
|
|
87
87
|
study: {
|
|
88
88
|
uuid: string;
|
|
89
89
|
name: string;
|
|
90
|
-
type:
|
|
90
|
+
type: "TMC" | "ATR";
|
|
91
91
|
status: string;
|
|
92
92
|
};
|
|
93
93
|
videoCount: number;
|
|
@@ -423,37 +423,41 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
423
423
|
// Use Knex query builder for safe parameter binding
|
|
424
424
|
const groupingQuery = this.knex(this.tableName)
|
|
425
425
|
.select(
|
|
426
|
-
this.knex.raw(
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
this.knex.raw(
|
|
430
|
-
this.knex.raw(
|
|
426
|
+
this.knex.raw("FLOOR(minute_number / ?) as group_index", [
|
|
427
|
+
groupingMinutes,
|
|
428
|
+
]),
|
|
429
|
+
this.knex.raw("MIN(minute_number) as start_minute"),
|
|
430
|
+
this.knex.raw("MAX(minute_number) as end_minute"),
|
|
431
|
+
this.knex.raw("COUNT(*) as minute_count"),
|
|
432
|
+
this.knex.raw(
|
|
433
|
+
"array_agg(results ORDER BY minute_number) as all_results",
|
|
434
|
+
),
|
|
431
435
|
)
|
|
432
|
-
.where(
|
|
436
|
+
.where("video_id", video.id);
|
|
433
437
|
|
|
434
438
|
if (startMinute !== undefined) {
|
|
435
|
-
groupingQuery.where(
|
|
439
|
+
groupingQuery.where("minute_number", ">=", startMinute);
|
|
436
440
|
}
|
|
437
441
|
|
|
438
442
|
if (endMinute !== undefined) {
|
|
439
|
-
groupingQuery.where(
|
|
443
|
+
groupingQuery.where("minute_number", "<=", endMinute);
|
|
440
444
|
}
|
|
441
445
|
|
|
442
446
|
const rows = await groupingQuery
|
|
443
|
-
.groupBy(
|
|
444
|
-
.orderBy(
|
|
447
|
+
.groupBy("group_index")
|
|
448
|
+
.orderBy("group_index");
|
|
445
449
|
|
|
446
450
|
// Aggregate the results in TypeScript based on video type
|
|
447
451
|
const aggregatedGroups: IGroupedResult[] = rows.map((row: any) => {
|
|
448
|
-
if (!row || typeof row !==
|
|
449
|
-
throw new Error(
|
|
452
|
+
if (!row || typeof row !== "object") {
|
|
453
|
+
throw new Error("Invalid row data received from database query");
|
|
450
454
|
}
|
|
451
455
|
|
|
452
456
|
const allResults = Array.isArray(row.all_results) ? row.all_results : [];
|
|
453
|
-
|
|
457
|
+
|
|
454
458
|
// Determine video type based on multiple factors
|
|
455
|
-
let studyType = video.videoType ||
|
|
456
|
-
|
|
459
|
+
let studyType = video.videoType || "ATR"; // Default fallback to ATR
|
|
460
|
+
|
|
457
461
|
// Check if minute data has study_type field (ATR usually does)
|
|
458
462
|
if (allResults.length > 0 && allResults[0].study_type) {
|
|
459
463
|
studyType = allResults[0].study_type;
|
|
@@ -465,20 +469,24 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
465
469
|
const vehicleKeys = Object.keys(sampleResult.vehicles);
|
|
466
470
|
if (vehicleKeys.length > 0) {
|
|
467
471
|
const firstVehicleType = sampleResult.vehicles[vehicleKeys[0]];
|
|
468
|
-
if (firstVehicleType && typeof firstVehicleType ===
|
|
472
|
+
if (firstVehicleType && typeof firstVehicleType === "object") {
|
|
469
473
|
const directions = Object.keys(firstVehicleType);
|
|
470
|
-
if (
|
|
471
|
-
|
|
472
|
-
|
|
474
|
+
if (
|
|
475
|
+
directions.includes("NORTH") ||
|
|
476
|
+
directions.includes("SOUTH") ||
|
|
477
|
+
directions.includes("EAST") ||
|
|
478
|
+
directions.includes("WEST")
|
|
479
|
+
) {
|
|
480
|
+
studyType = "TMC";
|
|
473
481
|
}
|
|
474
482
|
}
|
|
475
483
|
}
|
|
476
484
|
}
|
|
477
485
|
}
|
|
478
|
-
|
|
486
|
+
|
|
479
487
|
// Aggregate based on determined video type
|
|
480
488
|
let aggregatedResult;
|
|
481
|
-
if (studyType ===
|
|
489
|
+
if (studyType === "TMC") {
|
|
482
490
|
aggregatedResult = this.aggregateTMCResults(allResults);
|
|
483
491
|
} else {
|
|
484
492
|
aggregatedResult = this.aggregateATRResults(allResults);
|
|
@@ -569,8 +577,8 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
569
577
|
"v.uuid as videoUuid",
|
|
570
578
|
"v.recordingStartedAt",
|
|
571
579
|
this.knex.raw(
|
|
572
|
-
'"v"."recordingStartedAt" + (vmr.minute_number || \' minutes\')::INTERVAL as "absoluteTime"'
|
|
573
|
-
)
|
|
580
|
+
'"v"."recordingStartedAt" + (vmr.minute_number || \' minutes\')::INTERVAL as "absoluteTime"',
|
|
581
|
+
),
|
|
574
582
|
)
|
|
575
583
|
.whereIn("v.id", videoIds)
|
|
576
584
|
.orderBy("absoluteTime", "asc");
|
|
@@ -608,7 +616,7 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
608
616
|
for (const minute of minuteResults) {
|
|
609
617
|
const absoluteTime = new Date(minute.absoluteTime);
|
|
610
618
|
const minutesSinceEarliest = Math.floor(
|
|
611
|
-
(absoluteTime.getTime() - earliestTime.getTime()) / (1000 * 60)
|
|
619
|
+
(absoluteTime.getTime() - earliestTime.getTime()) / (1000 * 60),
|
|
612
620
|
);
|
|
613
621
|
const bucketIndex = Math.floor(minutesSinceEarliest / groupingMinutes);
|
|
614
622
|
|
|
@@ -616,7 +624,7 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
616
624
|
// Calculate bucket start time
|
|
617
625
|
const bucketStartMinutes = bucketIndex * groupingMinutes;
|
|
618
626
|
const bucketStartTime = new Date(
|
|
619
|
-
earliestTime.getTime() + bucketStartMinutes * 60 * 1000
|
|
627
|
+
earliestTime.getTime() + bucketStartMinutes * 60 * 1000,
|
|
620
628
|
);
|
|
621
629
|
|
|
622
630
|
buckets.set(bucketIndex, {
|
|
@@ -633,12 +641,14 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
633
641
|
}
|
|
634
642
|
|
|
635
643
|
// Step 5: Aggregate using existing methods based on study type
|
|
636
|
-
const aggregatedGroups: IStudyTimeGroupResult[] = Array.from(
|
|
644
|
+
const aggregatedGroups: IStudyTimeGroupResult[] = Array.from(
|
|
645
|
+
buckets.values(),
|
|
646
|
+
)
|
|
637
647
|
.sort((a, b) => a.groupIndex - b.groupIndex)
|
|
638
648
|
.map((bucket) => {
|
|
639
649
|
let aggregatedResult: ITMCResult | IATRResult;
|
|
640
650
|
|
|
641
|
-
if (study.type ===
|
|
651
|
+
if (study.type === "TMC") {
|
|
642
652
|
aggregatedResult = this.aggregateTMCResults(bucket.results);
|
|
643
653
|
} else {
|
|
644
654
|
aggregatedResult = this.aggregateATRResults(bucket.results);
|
|
@@ -649,7 +659,10 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
649
659
|
groupIndex: bucket.groupIndex,
|
|
650
660
|
startMinute: bucket.groupIndex * groupingMinutes,
|
|
651
661
|
endMinute: (bucket.groupIndex + 1) * groupingMinutes - 1,
|
|
652
|
-
label: this.formatStudyTimeLabel(
|
|
662
|
+
label: this.formatStudyTimeLabel(
|
|
663
|
+
bucket.absoluteTime,
|
|
664
|
+
groupingMinutes,
|
|
665
|
+
),
|
|
653
666
|
results: aggregatedResult,
|
|
654
667
|
minuteCount: bucket.results.length,
|
|
655
668
|
videoCount: bucket.videoUuids.size,
|
|
@@ -712,25 +725,27 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
712
725
|
vehicles: {},
|
|
713
726
|
counts: {
|
|
714
727
|
total_vehicles: 0,
|
|
715
|
-
entry_vehicles: 0
|
|
728
|
+
entry_vehicles: 0,
|
|
716
729
|
},
|
|
717
730
|
total: 0,
|
|
718
731
|
totalcount: 0,
|
|
719
732
|
detected_classes: {},
|
|
720
|
-
study_type: "TMC"
|
|
733
|
+
study_type: "TMC",
|
|
721
734
|
};
|
|
722
735
|
|
|
723
736
|
for (const minute of minutes) {
|
|
724
737
|
const results = minute; // minute is already the results object from array_agg
|
|
725
738
|
|
|
726
739
|
// Aggregate vehicle movements by class and direction
|
|
727
|
-
if (results.vehicles && typeof results.vehicles ===
|
|
728
|
-
for (const [vehicleClass, directions] of Object.entries(
|
|
740
|
+
if (results.vehicles && typeof results.vehicles === "object") {
|
|
741
|
+
for (const [vehicleClass, directions] of Object.entries(
|
|
742
|
+
results.vehicles,
|
|
743
|
+
)) {
|
|
729
744
|
// Skip the 'total' pseudo vehicle class - validate it's actually aggregate data
|
|
730
|
-
if (vehicleClass ===
|
|
745
|
+
if (vehicleClass === "total" && typeof directions === "number") {
|
|
731
746
|
continue; // This is aggregate total data, not a vehicle class
|
|
732
747
|
}
|
|
733
|
-
|
|
748
|
+
|
|
734
749
|
if (!aggregated.vehicles[vehicleClass]) {
|
|
735
750
|
aggregated.vehicles[vehicleClass] = {};
|
|
736
751
|
}
|
|
@@ -753,11 +768,12 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
753
768
|
for (const [turnType, count] of Object.entries(turns)) {
|
|
754
769
|
const turnCount = (count as number) || 0;
|
|
755
770
|
aggregated.vehicles[vehicleClass][direction][turnType] =
|
|
756
|
-
(aggregated.vehicles[vehicleClass][direction][turnType] ||
|
|
757
|
-
|
|
771
|
+
(aggregated.vehicles[vehicleClass][direction][turnType] ||
|
|
772
|
+
0) + turnCount;
|
|
773
|
+
|
|
758
774
|
// Add to detected_classes count for this vehicle type
|
|
759
775
|
aggregated.detected_classes[vehicleClass] += turnCount;
|
|
760
|
-
|
|
776
|
+
|
|
761
777
|
// Add to total counts
|
|
762
778
|
aggregated.total += turnCount;
|
|
763
779
|
aggregated.totalcount += turnCount;
|
|
@@ -765,10 +781,12 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
765
781
|
}
|
|
766
782
|
}
|
|
767
783
|
}
|
|
768
|
-
|
|
784
|
+
|
|
769
785
|
// Also process the 'total' entry for validation but don't count it as a vehicle class
|
|
770
786
|
if (results.vehicles.total) {
|
|
771
|
-
for (const [direction, turns] of Object.entries(
|
|
787
|
+
for (const [direction, turns] of Object.entries(
|
|
788
|
+
results.vehicles.total as any,
|
|
789
|
+
)) {
|
|
772
790
|
if (typeof turns === "object" && turns !== null) {
|
|
773
791
|
for (const [turnType, count] of Object.entries(turns)) {
|
|
774
792
|
const turnCount = (count as number) || 0;
|
|
@@ -781,7 +799,8 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
781
799
|
}
|
|
782
800
|
|
|
783
801
|
// Set entry_vehicles same as total_vehicles for TMC
|
|
784
|
-
aggregated.counts.entry_vehicles =
|
|
802
|
+
aggregated.counts.entry_vehicles =
|
|
803
|
+
aggregated.counts.total_vehicles || aggregated.total;
|
|
785
804
|
|
|
786
805
|
return aggregated;
|
|
787
806
|
}
|
|
@@ -805,7 +824,7 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
805
824
|
if (results.vehicles) {
|
|
806
825
|
for (const [vehicleClass, lanes] of Object.entries(results.vehicles)) {
|
|
807
826
|
// Skip 'total' pseudo-class if present
|
|
808
|
-
if (vehicleClass ===
|
|
827
|
+
if (vehicleClass === "total") {
|
|
809
828
|
continue;
|
|
810
829
|
}
|
|
811
830
|
|
|
@@ -814,7 +833,8 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
814
833
|
}
|
|
815
834
|
|
|
816
835
|
for (const [laneId, count] of Object.entries(lanes as any)) {
|
|
817
|
-
const numericCount =
|
|
836
|
+
const numericCount =
|
|
837
|
+
typeof count === "number" ? count : parseInt(String(count)) || 0;
|
|
818
838
|
aggregated.vehicles[vehicleClass][laneId] =
|
|
819
839
|
(aggregated.vehicles[vehicleClass][laneId] || 0) + numericCount;
|
|
820
840
|
}
|
|
@@ -921,7 +941,10 @@ export class VideoMinuteResultDAO implements IBaseDAO<IVideoMinuteResult> {
|
|
|
921
941
|
* Used when results are already grouped by date in the UI
|
|
922
942
|
* Uses UTC time for consistency with absoluteTime field
|
|
923
943
|
*/
|
|
924
|
-
private formatStudyTimeLabel(
|
|
944
|
+
private formatStudyTimeLabel(
|
|
945
|
+
startTime: Date,
|
|
946
|
+
durationMinutes: number,
|
|
947
|
+
): string {
|
|
925
948
|
const endTime = new Date(startTime.getTime() + durationMinutes * 60 * 1000);
|
|
926
949
|
|
|
927
950
|
const formatTime = (date: Date): string => {
|
package/src/dao/auth/auth.dao.ts
CHANGED
|
@@ -4,58 +4,61 @@ import { IAuth } from "../../interfaces/auth/auth.interfaces";
|
|
|
4
4
|
import KnexManager from "../../KnexConnection";
|
|
5
5
|
|
|
6
6
|
export class AuthDAO implements IBaseDAO<IAuth> {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
7
|
+
private _knex: Knex<any, unknown[]> = KnexManager.getConnection();
|
|
8
|
+
|
|
9
|
+
async create(item: IAuth): Promise<IAuth> {
|
|
10
|
+
const [createdAuth] = await this._knex("auth").insert(item).returning("*");
|
|
11
|
+
return createdAuth;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async getById(id: number): Promise<IAuth | null> {
|
|
15
|
+
const auth = await this._knex("auth").where({ id }).first();
|
|
16
|
+
return auth || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getByUuid(uuid: string): Promise<IAuth | null> {
|
|
20
|
+
// Auth table doesn't have uuid, so we'll return null or throw error
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async getByUserId(userId: number): Promise<IAuth | null> {
|
|
25
|
+
const auth = await this._knex("auth").where({ userId }).first();
|
|
26
|
+
return auth || null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getByEmailToken(emailToken: string): Promise<IAuth | null> {
|
|
30
|
+
const auth = await this._knex("auth").where({ emailToken }).first();
|
|
31
|
+
return auth || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async update(id: number, item: Partial<IAuth>): Promise<IAuth | null> {
|
|
35
|
+
const [updatedAuth] = await this._knex("auth")
|
|
36
|
+
.where({ id })
|
|
37
|
+
.update(item)
|
|
38
|
+
.returning("*");
|
|
39
|
+
return updatedAuth || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async delete(id: number): Promise<boolean> {
|
|
43
|
+
const result = await this._knex("auth").where({ id }).del();
|
|
44
|
+
return result > 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async getAll(page: number, limit: number): Promise<IDataPaginator<IAuth>> {
|
|
48
|
+
const offset = (page - 1) * limit;
|
|
49
|
+
|
|
50
|
+
const [countResult] = await this._knex("auth").count("* as count");
|
|
51
|
+
const totalCount = +countResult.count;
|
|
52
|
+
const auths = await this._knex("auth").limit(limit).offset(offset);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
data: auths,
|
|
57
|
+
page,
|
|
58
|
+
limit,
|
|
59
|
+
count: auths.length,
|
|
60
|
+
totalCount,
|
|
61
|
+
totalPages: Math.ceil(totalCount / limit),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -4,115 +4,118 @@ import { IBatch } from "../../interfaces/batch/batch.interfaces";
|
|
|
4
4
|
import KnexManager from "../../KnexConnection";
|
|
5
5
|
|
|
6
6
|
export class BatchDAO implements IBaseDAO<IBatch> {
|
|
7
|
-
|
|
7
|
+
private _knex: Knex<any, unknown[]> = KnexManager.getConnection();
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
async create(item: IBatch): Promise<IBatch> {
|
|
10
|
+
const [createdBatch] = await this._knex("video_batch")
|
|
11
|
+
.insert(item)
|
|
12
|
+
.returning("*");
|
|
13
|
+
return createdBatch;
|
|
14
|
+
}
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
+
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
}
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
async delete(id: number): Promise<boolean> {
|
|
43
|
+
const result = await this._knex("video_batch").where({ id }).del();
|
|
44
|
+
return result > 0;
|
|
45
|
+
}
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
async getAll(
|
|
48
|
+
page: number,
|
|
49
|
+
limit: number,
|
|
50
|
+
folderId?: number | null,
|
|
51
|
+
): Promise<IDataPaginator<IBatch>> {
|
|
52
|
+
const offset = (page - 1) * limit;
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
}
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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);
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
}
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
}
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
93
|
-
}
|
|
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
|
+
}
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
105
|
-
}
|
|
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
|
+
}
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
}
|
|
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
|
+
}
|
|
118
121
|
}
|