Skip to content

Commit 86173bc

Browse files
updated endpoint for getting all applications. 24 hour statusCheck needs testing
1 parent 6a64d47 commit 86173bc

File tree

5 files changed

+159
-46
lines changed

5 files changed

+159
-46
lines changed

src/controllers/admin-controller/application.controller.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,7 @@ export class ApplicationController {
7878
@Query() query?: ListAllApplicationsDto
7979
): Promise<ListAllApplicationsResponseDto> {
8080
if (req.user.permissions.isGlobalAdmin) {
81-
return this.applicationService.findAndCountWithPagination(
82-
query,
83-
query.organizationId ? [+query.organizationId] : null
84-
);
81+
return this.applicationService.findAndCountInList(query);
8582
}
8683

8784
return await this.getApplicationsForNonGlobalAdmin(req, query);
@@ -273,11 +270,11 @@ export class ApplicationController {
273270
// User admins have access to all applications in the organization
274271
const allFromOrg = req.user.permissions.getAllOrganizationsWithUserAdmin();
275272
if (allFromOrg.some(x => x === query?.organizationId)) {
276-
return await this.applicationService.findAndCountWithPagination(query, [query.organizationId]);
273+
return await this.applicationService.findAndCountInList(query, null);
277274
}
278275

279276
const allowedApplications = req.user.permissions.getAllApplicationsWithAtLeastRead();
280-
return await this.applicationService.findAndCountInList(query, allowedApplications, [query.organizationId]);
277+
return await this.applicationService.findAndCountInList(query, allowedApplications);
281278
}
282279

283280
private async getFilterInformationInOrganization(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { ListAllEntitiesResponseDto } from "@dto/list-all-entities-response.dto";
22
import { Application } from "@entities/application.entity";
33

4+
export type ApplicationWithStatus = Application & { statusCheck?: "stable" | "alert" };
5+
6+
export class ListAllApplicationsWithStatusResponseDto extends ListAllEntitiesResponseDto<ApplicationWithStatus> {}
47
export class ListAllApplicationsResponseDto extends ListAllEntitiesResponseDto<Application> {}

src/entities/dto/list-all-applications.dto.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { DefaultLimit, DefaultOffset } from "@config/constants/pagination-constants";
22
import { ListAllEntitiesDto } from "@dto/list-all-entities.dto";
3+
import { ApplicationStatus } from "@enum/application-status.enum";
34
import { IsSwaggerOptional } from "@helpers/optional-validator";
4-
import { NullableStringToNumber, StringToNumber } from "@helpers/string-to-number-validator";
5+
import {
6+
NullableApplicationStatus,
7+
NullableString,
8+
NullableStringToNumber,
9+
StringToNumber,
10+
} from "@helpers/string-to-number-validator";
511
import { ApiProperty, OmitType } from "@nestjs/swagger";
612
import { Transform } from "class-transformer";
713
import { IsNumber, IsOptional, IsString } from "class-validator";
@@ -26,15 +32,18 @@ export class ListAllApplicationsDto extends OmitType(ListAllEntitiesDto, ["limit
2632
@ApiProperty({ type: Number, required: false })
2733
@IsOptional()
2834
@IsString()
29-
status? = "";
35+
@Transform(({ value }) => NullableApplicationStatus(value))
36+
status?: ApplicationStatus | undefined;
3037

3138
@ApiProperty({ type: String, required: false })
3239
@IsOptional()
3340
@IsString()
34-
state? = "";
41+
@Transform(({ value }) => NullableString(value))
42+
statusCheck?: "stable" | "alert" | null;
3543

3644
@ApiProperty({ type: Number, required: false })
3745
@IsOptional()
3846
@IsString()
39-
owner? = "";
47+
@Transform(({ value }) => NullableString(value))
48+
owner?: string | null;
4049
}

src/helpers/string-to-number-validator.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ApplicationStatus } from "@enum/application-status.enum";
12
import { Type } from "class-transformer";
23
import { IsNumber } from "class-validator";
34

@@ -26,3 +27,19 @@ export const NullableStringToNumber = (value: unknown): number | null => {
2627

2728
return Number(value);
2829
};
30+
31+
export const NullableString = (value: unknown): string | null => {
32+
if (value === null || value === "null") {
33+
return null;
34+
}
35+
36+
return value as string;
37+
};
38+
39+
export const NullableApplicationStatus = (value: unknown): ApplicationStatus | null => {
40+
if (value === null || value === "null") {
41+
return null;
42+
}
43+
44+
return value as ApplicationStatus;
45+
};

src/services/device-management/application.service.ts

Lines changed: 123 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { CreateApplicationDto } from "@dto/create-application.dto";
2-
import { ListAllApplicationsResponseDto } from "@dto/list-all-applications-response.dto";
2+
import {
3+
ListAllApplicationsResponseDto,
4+
ListAllApplicationsWithStatusResponseDto,
5+
} from "@dto/list-all-applications-response.dto";
36
import { ListAllApplicationsDto } from "@dto/list-all-applications.dto";
47
import { ListAllEntitiesDto } from "@dto/list-all-entities.dto";
58
import { ListAllIoTDevicesResponseDto } from "@dto/list-all-iot-devices-response.dto";
@@ -50,25 +53,101 @@ export class ApplicationService {
5053
) {}
5154

5255
async findAndCountInList(
53-
query?: ListAllEntitiesDto,
54-
whitelist?: number[],
55-
allFromOrgs?: number[]
56-
): Promise<ListAllApplicationsResponseDto> {
56+
query?: ListAllApplicationsDto,
57+
whitelist?: number[]
58+
): Promise<ListAllApplicationsWithStatusResponseDto> {
5759
const sorting = this.getSortingForApplications(query);
58-
const orgCondition =
59-
allFromOrgs != null ? { id: In(whitelist), belongsTo: In(allFromOrgs) } : { id: In(whitelist) };
60-
const [result, total] = await this.applicationRepository.findAndCount({
61-
where: orgCondition,
62-
take: query.limit,
63-
skip: query.offset,
64-
relations: ["iotDevices", "dataTargets", "controlledProperties", "deviceTypes", nameof<Application>("belongsTo")],
65-
order: sorting,
66-
});
6760

68-
return {
69-
data: result,
70-
count: total,
71-
};
61+
const queryBuilder = this.applicationRepository
62+
.createQueryBuilder("app")
63+
.leftJoinAndSelect("app.iotDevices", "device")
64+
.leftJoinAndSelect("app.dataTargets", "dataTarget")
65+
.leftJoinAndSelect("app.belongsTo", "organization")
66+
.leftJoinAndSelect("device.latestReceivedMessage", "latestMessage");
67+
68+
if (whitelist && whitelist.length > 0) {
69+
queryBuilder.where("app.id IN (:...whitelist)", { whitelist });
70+
}
71+
72+
if (query.organizationId) {
73+
queryBuilder.andWhere("app.belongsToId = :organizationId", { organizationId: query.organizationId });
74+
}
75+
76+
if (query.status) {
77+
console.log("status : " + query.status);
78+
queryBuilder.andWhere("app.status = :status", { status: query.status });
79+
}
80+
81+
if (query.owner) {
82+
queryBuilder.andWhere("app.owner = :owner", { owner: query.owner });
83+
}
84+
85+
if (query.statusCheck) {
86+
queryBuilder.andWhere(
87+
`
88+
app.id IN (
89+
SELECT
90+
app.id,
91+
CASE
92+
WHEN COUNT(DISTINCT dataTarget.id) = 0 THEN 'alert'
93+
WHEN latestMessage.sentTime < NOW() - INTERVAL '24 HOURS' THEN 'alert'
94+
ELSE 'stable'
95+
END AS statusCheck
96+
FROM application app
97+
LEFT JOIN data_target dataTarget ON dataTarget.applicationId = app.id
98+
LEFT JOIN device ON device.applicationId = app.id
99+
LEFT JOIN latestMessage ON latestMessage.deviceId = device.id
100+
WHERE app.belongsToId = :organizationId
101+
GROUP BY app.id
102+
HAVING
103+
(
104+
COUNT(DISTINCT dataTarget.id) = 0
105+
OR latestMessage.sentTime < NOW() - INTERVAL '24 HOURS'
106+
)
107+
AND (
108+
:statusCheck = 'alert'
109+
OR COUNT(DISTINCT dataTarget.id) > 0
110+
)
111+
)
112+
`,
113+
{ statusCheck: query.statusCheck, organizationId: query.organizationId }
114+
);
115+
}
116+
117+
queryBuilder.orderBy(sorting);
118+
queryBuilder.take(query.limit).skip(query.offset);
119+
120+
try {
121+
const [result, total] = await queryBuilder.getManyAndCount();
122+
const mappedResult = result.map(app => {
123+
const latestMessage = app.iotDevices
124+
?.map(device => device.latestReceivedMessage)
125+
.find(msg => msg !== undefined);
126+
const hasDataTargets = app.dataTargets && app.dataTargets.length > 0;
127+
const isLatestMessageOld = latestMessage
128+
? new Date(latestMessage.sentTime) < new Date(Date.now() - 24 * 60 * 60 * 1000)
129+
: false;
130+
131+
let statusCheck: "stable" | "alert" = "stable";
132+
133+
if (!hasDataTargets || isLatestMessageOld) {
134+
statusCheck = "alert";
135+
}
136+
137+
return {
138+
...app,
139+
statusCheck,
140+
};
141+
});
142+
143+
return {
144+
data: mappedResult,
145+
count: total,
146+
};
147+
} catch (error) {
148+
console.error("Error executing query:", error);
149+
throw new Error("Database query failed");
150+
}
72151
}
73152

74153
async findAndCountApplicationInWhitelistOrOrganization(
@@ -512,26 +591,34 @@ export class ApplicationService {
512591
}
513592
return orderBy;
514593
}
594+
private getSortingForApplications(query: ListAllEntitiesDto): Record<string, "ASC" | "DESC"> {
595+
const sorting: Record<string, "ASC" | "DESC"> = {};
596+
597+
if (query.orderOn) {
598+
const validFields = new Set([
599+
"id",
600+
"name",
601+
"updatedAt",
602+
"startDate",
603+
"endDate",
604+
"owner",
605+
"contactPerson",
606+
"personalData",
607+
]);
608+
609+
const sortOrder = query.sort.toUpperCase() === "DESC" ? "DESC" : "ASC";
610+
611+
if (query.orderOn === "status") {
612+
sorting["status"] = sortOrder;
613+
} else if (validFields.has(query.orderOn)) {
614+
sorting[`app.${query.orderOn}`] = sortOrder;
615+
}
616+
}
515617

516-
private getSortingForApplications(query: ListAllEntitiesDto): Record<string, string | number> {
517-
const sorting: Record<string, string | number> = {};
518-
if (
519-
// TODO: Make this nicer
520-
query.orderOn != null &&
521-
(query.orderOn === "id" ||
522-
query.orderOn === "name" ||
523-
query.orderOn === "updatedAt" ||
524-
query.orderOn === "status" ||
525-
query.orderOn === "startDate" ||
526-
query.orderOn === "endDate" ||
527-
query.orderOn === "owner" ||
528-
query.orderOn === "contactPerson" ||
529-
query.orderOn === "personalData")
530-
) {
531-
sorting[query.orderOn] = query.sort.toLocaleUpperCase();
532-
} else {
533-
sorting["id"] = "ASC";
618+
if (Object.keys(sorting).length === 0) {
619+
sorting["app.id"] = "ASC";
534620
}
621+
535622
return sorting;
536623
}
537624
}

0 commit comments

Comments
 (0)