Skip to content

Commit e72b501

Browse files
IOT-1610: Added new metadata field for ODDK datatargets (#286)
* Wip * More work * Last fixes * Package update * went through endpoints to verify no extra data should be shown * npm i * addd minimal on org * get one with security and removed minimal from org controller * Added new metadata field for ODDK datatargets * Fix chirpstack gateway tags format to handle non-string JSON values like false * Also fix gateway edit + device profile * Bugfix/#206 gateway metadata error (#287) * Fix chirpstack gateway tags format to handle non-string JSON values like false * Also fix gateway edit + device profile * Removed some unused imports --------- Co-authored-by: August Andersen <aha@iterator-it.dk>
1 parent d1c1fde commit e72b501

File tree

8 files changed

+197
-132
lines changed

8 files changed

+197
-132
lines changed

src/entities/dto/create-open-data-dk-dataset.dto.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
2-
import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUrl } from "class-validator";
2+
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, IsUrl } from "class-validator";
33

44
export class CreateOpenDataDkDatasetDto {
55
@ApiProperty({ required: true })
@@ -17,6 +17,11 @@ export class CreateOpenDataDkDatasetDto {
1717
@IsString({ each: true, always: true })
1818
keywords?: string[];
1919

20+
@ApiProperty({ required: false })
21+
@IsOptional()
22+
@IsString()
23+
keywordTags: string;
24+
2025
@ApiProperty({ required: true })
2126
@IsString()
2227
@IsUrl({ protocols: ["http", "https"] })
@@ -36,4 +41,18 @@ export class CreateOpenDataDkDatasetDto {
3641
@IsString()
3742
@IsNotEmpty()
3843
resourceTitle: string;
44+
45+
@ApiProperty({ required: false })
46+
@IsOptional()
47+
@IsString()
48+
updateFrequency: string = "UNKNOWN";
49+
50+
@ApiProperty({ required: false })
51+
@IsOptional()
52+
@IsUrl()
53+
documentationUrl: string;
54+
55+
@ApiProperty({ required: true })
56+
@IsBoolean()
57+
dataDirectory: boolean;
3958
}

src/entities/dto/open-data-dk-dcat.dto.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export class Dataset {
3737
distribution: Distribution[];
3838
spatial: string;
3939
theme: string[];
40+
documentation: string;
41+
frequency: string | undefined;
42+
dataDirectory: boolean;
4043
}
4144

4245
export class DCATRootObject {

src/entities/open-data-dk-dataset.entity.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export class OpenDataDkDataset extends DbBaseEntity {
1818
@Column("text", { array: true, nullable: true })
1919
keywords?: string[];
2020

21+
@Column({ nullable: true })
22+
keywordTags?: string;
23+
2124
@Column()
2225
license: string;
2326

@@ -29,4 +32,13 @@ export class OpenDataDkDataset extends DbBaseEntity {
2932

3033
@Column({ nullable: false, default: "" })
3134
resourceTitle: string;
35+
36+
@Column({ nullable: true })
37+
updateFrequency?: string;
38+
39+
@Column({ nullable: true })
40+
documentationUrl?: string;
41+
42+
@Column({ nullable: false, default: false })
43+
dataDirectory: boolean;
3244
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class AddedNewFieldsToOddkDatatarget1762524771197 implements MigrationInterface {
4+
name = 'AddedNewFieldsToOddkDatatarget1762524771197'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`ALTER TABLE "open_data_dk_dataset" ADD "keywordTags" character varying`);
8+
await queryRunner.query(`ALTER TABLE "open_data_dk_dataset" ADD "updateFrequency" character varying`);
9+
await queryRunner.query(`ALTER TABLE "open_data_dk_dataset" ADD "documentationUrl" character varying`);
10+
await queryRunner.query(`ALTER TABLE "open_data_dk_dataset" ADD "dataDirectory" boolean NOT NULL DEFAULT false`);
11+
}
12+
13+
public async down(queryRunner: QueryRunner): Promise<void> {
14+
await queryRunner.query(`ALTER TABLE "open_data_dk_dataset" DROP COLUMN "dataDirectory"`);
15+
await queryRunner.query(`ALTER TABLE "open_data_dk_dataset" DROP COLUMN "documentationUrl"`);
16+
await queryRunner.query(`ALTER TABLE "open_data_dk_dataset" DROP COLUMN "updateFrequency"`);
17+
await queryRunner.query(`ALTER TABLE "open_data_dk_dataset" DROP COLUMN "keywordTags"`);
18+
}
19+
20+
}

src/services/chirpstack/chirpstack-gateway.service.ts

Lines changed: 108 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
2-
Gateway as ChirpstackGateway,
32
CreateGatewayRequest,
43
DeleteGatewayRequest,
4+
Gateway as ChirpstackGateway,
55
GetGatewayMetricsRequest,
66
GetGatewayMetricsResponse,
77
GetGatewayRequest,
@@ -53,6 +53,12 @@ import { Repository } from "typeorm";
5353

5454
@Injectable()
5555
export class ChirpstackGatewayService extends GenericChirpstackConfigurationService {
56+
GATEWAY_STATS_INTERVAL_IN_DAYS = 29;
57+
GATEWAY_LAST_ACTIVE_SINCE_IN_MINUTES = 3;
58+
private readonly logger = new Logger(ChirpstackGatewayService.name, {
59+
timestamp: true,
60+
});
61+
5662
constructor(
5763
@InjectRepository(DbGateway)
5864
private gatewayRepository: Repository<DbGateway>,
@@ -62,11 +68,6 @@ export class ChirpstackGatewayService extends GenericChirpstackConfigurationServ
6268
) {
6369
super();
6470
}
65-
GATEWAY_STATS_INTERVAL_IN_DAYS = 29;
66-
GATEWAY_LAST_ACTIVE_SINCE_IN_MINUTES = 3;
67-
private readonly logger = new Logger(ChirpstackGatewayService.name, {
68-
timestamp: true,
69-
});
7071

7172
async createNewGateway(dto: CreateGatewayDto, userId: number): Promise<ChirpstackResponseStatus> {
7273
dto.gateway = await this.updateDtoContents(dto.gateway);
@@ -87,7 +88,7 @@ export class ChirpstackGatewayService extends GenericChirpstackConfigurationServ
8788

8889
const gatewayChirpstack = await this.mapToChirpstackGateway(dto, chirpstackLocation);
8990
Object.entries(dto.gateway.tags).forEach(([key, value]) => {
90-
gatewayChirpstack.getTagsMap().set(key, value);
91+
gatewayChirpstack.getTagsMap().set(key, value.toString());
9192
});
9293

9394
req.setGateway(gatewayChirpstack);
@@ -302,54 +303,6 @@ export class ChirpstackGatewayService extends GenericChirpstackConfigurationServ
302303
}
303304
}
304305

305-
//TODO: This could be moved to a helper function in the future, since it has a lot of similarities with metrics from chirpstack devices.
306-
private mapPackets(metrics: GetGatewayMetricsResponse) {
307-
const gatewayResponseDto: GatewayStatsElementDto[] = [];
308-
const packetCounts: { [timestamp: string]: { rx: number; tx: number } } = {};
309-
310-
const rxTimestamps = metrics.getRxPackets().getTimestampsList();
311-
const rxPackets = metrics
312-
.getRxPackets()
313-
.getDatasetsList()
314-
.find(e => e.getLabel() === "rx_count")
315-
.getDataList();
316-
317-
this.processPackets(rxTimestamps, rxPackets, "rx", packetCounts);
318-
319-
const txTimestamps = metrics.getTxPackets().getTimestampsList();
320-
const txPackets = metrics
321-
.getTxPackets()
322-
.getDatasetsList()
323-
.find(e => e.getLabel() === "tx_count")
324-
.getDataList();
325-
326-
this.processPackets(txTimestamps, txPackets, "tx", packetCounts);
327-
328-
Object.keys(packetCounts).forEach(timestamp => {
329-
const packetCount = packetCounts[timestamp];
330-
const dto: GatewayStatsElementDto = {
331-
timestamp,
332-
rxPacketsReceived: packetCount.rx,
333-
txPacketsEmitted: packetCount.tx,
334-
};
335-
gatewayResponseDto.push(dto);
336-
});
337-
return gatewayResponseDto;
338-
}
339-
340-
private processPackets(
341-
timestamps: Array<Timestamp>,
342-
packets: number[],
343-
key: string,
344-
packetCounts: { [timestamp: string]: { rx: number; tx: number } }
345-
) {
346-
timestamps.forEach((timestamp, index) => {
347-
const isoTimestamp = timestamp.toDate().toISOString();
348-
packetCounts[isoTimestamp] = packetCounts[isoTimestamp] || { rx: 0, tx: 0 };
349-
(packetCounts[isoTimestamp] as any)[key] = packets[index];
350-
});
351-
}
352-
353306
async modifyGateway(
354307
gatewayId: string,
355308
dto: UpdateGatewayDto,
@@ -370,7 +323,7 @@ export class ChirpstackGatewayService extends GenericChirpstackConfigurationServ
370323
const gatewayCs = await this.mapToChirpstackGateway(dto, location, gatewayId);
371324

372325
Object.entries(dto.gateway.tags).forEach(([key, value]) => {
373-
gatewayCs.getTagsMap().set(key, value);
326+
gatewayCs.getTagsMap().set(key, value.toString());
374327
});
375328

376329
request.setGateway(gatewayCs);
@@ -447,20 +400,6 @@ export class ChirpstackGatewayService extends GenericChirpstackConfigurationServ
447400
}
448401
}
449402

450-
private async updateDtoContents(
451-
contentsDto: GatewayContentsDto | UpdateGatewayContentsDto
452-
): Promise<GatewayContentsDto | UpdateGatewayContentsDto> {
453-
if (contentsDto?.tagsString) {
454-
contentsDto.tags = JSON.parse(contentsDto.tagsString);
455-
} else {
456-
contentsDto.tags = {};
457-
}
458-
459-
contentsDto.id = contentsDto.gatewayId;
460-
461-
return contentsDto;
462-
}
463-
464403
public mapContentsDtoToGateway(dto: GatewayContentsDto) {
465404
const gateway = new DbGateway();
466405
gateway.name = dto.name;
@@ -536,24 +475,6 @@ export class ChirpstackGatewayService extends GenericChirpstackConfigurationServ
536475

537476
return gateway;
538477
}
539-
private mapGatewayToResponseDto(gateway: DbGateway, forMap = false): GatewayResponseDto {
540-
const responseDto = gateway as unknown as GatewayResponseDto;
541-
responseDto.organizationId = gateway.organization.id;
542-
responseDto.organizationName = gateway.organization.name;
543-
544-
const commonLocation = new CommonLocationDto();
545-
commonLocation.latitude = gateway.location.coordinates[1];
546-
commonLocation.longitude = gateway.location.coordinates[0];
547-
548-
if (!forMap) {
549-
commonLocation.altitude = gateway.altitude;
550-
responseDto.tags = JSON.parse(gateway.tags);
551-
}
552-
553-
responseDto.location = commonLocation;
554-
555-
return responseDto;
556-
}
557478

558479
async getAllGatewaysFromChirpstack(): Promise<ListAllChirpstackGatewaysResponseDto> {
559480
const limit = 1000;
@@ -587,24 +508,6 @@ export class ChirpstackGatewayService extends GenericChirpstackConfigurationServ
587508
return responseList;
588509
}
589510

590-
private getSortingForGateways(query: ListAllEntitiesDto) {
591-
let orderBy = "gateway.id";
592-
593-
if (!query.orderOn) {
594-
return orderBy;
595-
}
596-
597-
if (query.orderOn === "organizationName") {
598-
orderBy = "organization.name";
599-
} else if (query.orderOn === "status") {
600-
orderBy = "gateway.lastSeenAt";
601-
} else {
602-
orderBy = `gateway.${query.orderOn}`;
603-
}
604-
605-
return orderBy;
606-
}
607-
608511
validatePackageAlarmInput(dto: UpdateGatewayDto) {
609512
if (dto.gateway.minimumPackages > dto.gateway.maximumPackages) {
610513
throw new BadRequestException({
@@ -628,6 +531,105 @@ export class ChirpstackGatewayService extends GenericChirpstackConfigurationServ
628531
}
629532
}
630533

534+
//TODO: This could be moved to a helper function in the future, since it has a lot of similarities with metrics from chirpstack devices.
535+
private mapPackets(metrics: GetGatewayMetricsResponse) {
536+
const gatewayResponseDto: GatewayStatsElementDto[] = [];
537+
const packetCounts: { [timestamp: string]: { rx: number; tx: number } } = {};
538+
539+
const rxTimestamps = metrics.getRxPackets().getTimestampsList();
540+
const rxPackets = metrics
541+
.getRxPackets()
542+
.getDatasetsList()
543+
.find(e => e.getLabel() === "rx_count")
544+
.getDataList();
545+
546+
this.processPackets(rxTimestamps, rxPackets, "rx", packetCounts);
547+
548+
const txTimestamps = metrics.getTxPackets().getTimestampsList();
549+
const txPackets = metrics
550+
.getTxPackets()
551+
.getDatasetsList()
552+
.find(e => e.getLabel() === "tx_count")
553+
.getDataList();
554+
555+
this.processPackets(txTimestamps, txPackets, "tx", packetCounts);
556+
557+
Object.keys(packetCounts).forEach(timestamp => {
558+
const packetCount = packetCounts[timestamp];
559+
const dto: GatewayStatsElementDto = {
560+
timestamp,
561+
rxPacketsReceived: packetCount.rx,
562+
txPacketsEmitted: packetCount.tx,
563+
};
564+
gatewayResponseDto.push(dto);
565+
});
566+
return gatewayResponseDto;
567+
}
568+
569+
private processPackets(
570+
timestamps: Array<Timestamp>,
571+
packets: number[],
572+
key: string,
573+
packetCounts: { [timestamp: string]: { rx: number; tx: number } }
574+
) {
575+
timestamps.forEach((timestamp, index) => {
576+
const isoTimestamp = timestamp.toDate().toISOString();
577+
packetCounts[isoTimestamp] = packetCounts[isoTimestamp] || { rx: 0, tx: 0 };
578+
(packetCounts[isoTimestamp] as any)[key] = packets[index];
579+
});
580+
}
581+
582+
private async updateDtoContents(
583+
contentsDto: GatewayContentsDto | UpdateGatewayContentsDto
584+
): Promise<GatewayContentsDto | UpdateGatewayContentsDto> {
585+
if (contentsDto?.tagsString) {
586+
contentsDto.tags = JSON.parse(contentsDto.tagsString);
587+
} else {
588+
contentsDto.tags = {};
589+
}
590+
591+
contentsDto.id = contentsDto.gatewayId;
592+
593+
return contentsDto;
594+
}
595+
596+
private mapGatewayToResponseDto(gateway: DbGateway, forMap = false): GatewayResponseDto {
597+
const responseDto = gateway as unknown as GatewayResponseDto;
598+
responseDto.organizationId = gateway.organization.id;
599+
responseDto.organizationName = gateway.organization.name;
600+
601+
const commonLocation = new CommonLocationDto();
602+
commonLocation.latitude = gateway.location.coordinates[1];
603+
commonLocation.longitude = gateway.location.coordinates[0];
604+
605+
if (!forMap) {
606+
commonLocation.altitude = gateway.altitude;
607+
responseDto.tags = JSON.parse(gateway.tags);
608+
}
609+
610+
responseDto.location = commonLocation;
611+
612+
return responseDto;
613+
}
614+
615+
private getSortingForGateways(query: ListAllEntitiesDto) {
616+
let orderBy = "gateway.id";
617+
618+
if (!query.orderOn) {
619+
return orderBy;
620+
}
621+
622+
if (query.orderOn === "organizationName") {
623+
orderBy = "organization.name";
624+
} else if (query.orderOn === "status") {
625+
orderBy = "gateway.lastSeenAt";
626+
} else {
627+
orderBy = `gateway.${query.orderOn}`;
628+
}
629+
630+
return orderBy;
631+
}
632+
631633
private async checkForNotificationUnusualPackagesAlarms(gateway: GatewayResponseDto) {
632634
if (!gateway.lastSeenAt) {
633635
return;

src/services/chirpstack/device-profile.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class DeviceProfileService extends GenericChirpstackConfigurationService
5252
const deviceProfile = this.mapToChirpstackDto(dto, true);
5353

5454
Object.entries(dto.deviceProfile.tags).forEach(([key, value]) => {
55-
deviceProfile.getTagsMap().set(key, value);
55+
deviceProfile.getTagsMap().set(key, value.toString());
5656
});
5757

5858
req.setDeviceProfile(deviceProfile);

0 commit comments

Comments
 (0)