Skip to content

Commit df1ab80

Browse files
greatly improved datapoints for data added
1 parent b83e3c5 commit df1ab80

2 files changed

Lines changed: 305 additions & 8 deletions

File tree

src/devices/devices.controller.ts

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {
22
BadRequestException,
33
Controller,
44
Get,
5+
NotImplementedException,
56
Param,
7+
Post,
68
Req,
79
UseGuards,
810
} from '@nestjs/common';
@@ -15,6 +17,7 @@ import {
1517
ApiNotFoundResponse,
1618
ApiOkResponse,
1719
ApiParam,
20+
ApiQuery,
1821
ApiSecurity,
1922
ApiUnauthorizedResponse,
2023
} from '@nestjs/swagger';
@@ -25,7 +28,7 @@ import { DeviceDto } from './dto/device.dto';
2528
@ApiBearerAuth('bearerAuth')
2629
@ApiSecurity('apiKey')
2730
export class DevicesController {
28-
constructor(private readonly devicesService: DevicesService) {}
31+
constructor(private readonly devicesService: DevicesService) { }
2932

3033
@Get()
3134
@UseGuards(JwtAuthGuard)
@@ -54,7 +57,7 @@ export class DevicesController {
5457
},
5558
})
5659
findAll(@Req() req) {
57-
return this.devicesService.findAll(req.user);
60+
return this.devicesService.findAll(req.user, req.headers.authorization);
5861
}
5962

6063
@Get(':dev_eui')
@@ -104,6 +107,85 @@ export class DevicesController {
104107
if (!devEui?.trim()) {
105108
throw new BadRequestException('dev_eui is required');
106109
}
107-
return this.devicesService.findOne(req.user, devEui);
110+
return this.devicesService.findOne(req.user, devEui, req.headers.authorization);
111+
}
112+
113+
@Get(':dev_eui/data')
114+
@UseGuards(JwtAuthGuard)
115+
@ApiParam({ name: 'dev_eui', description: 'Device dev_eui' })
116+
@ApiParam({ name: 'skip (0)', description: 'Number of records to skip for pagination', required: false })
117+
@ApiParam({ name: 'take (144)', description: 'Number of records to take for pagination', required: false })
118+
data(@Req() req, @Param('dev_eui') devEui: string) {
119+
if (!devEui?.trim()) {
120+
throw new BadRequestException('dev_eui is required');
121+
}
122+
const skip = parseInt(req.query.skip, 10) || 0;
123+
const take = parseInt(req.query.take, 10) || 144;
124+
return this.devicesService.findData(req.user, devEui, skip, take, req.headers.authorization);
125+
}
126+
127+
@Get(':dev_eui/data-within-range')
128+
@UseGuards(JwtAuthGuard)
129+
@ApiParam({ name: 'dev_eui', description: 'Device dev_eui' })
130+
@ApiQuery({
131+
name: 'start',
132+
required: false,
133+
description: 'ISO 8601 date/time. Defaults to 24 hours before end/now.',
134+
schema: {
135+
type: 'string',
136+
format: 'date-time',
137+
default: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
138+
},
139+
example: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
140+
})
141+
@ApiQuery({
142+
name: 'end',
143+
required: false,
144+
description: 'ISO 8601 date/time. Defaults to now.',
145+
schema: {
146+
type: 'string',
147+
format: 'date-time',
148+
default: new Date().toISOString(),
149+
},
150+
example: new Date().toISOString(),
151+
})
152+
@ApiParam({ name: 'skip (0)', description: 'Number of records to skip for pagination', required: false })
153+
@ApiParam({ name: 'take (144)', description: 'Number of records to take for pagination', required: false })
154+
dataWithinRange(@Req() req, @Param('dev_eui') devEui: string) {
155+
if (!devEui?.trim()) {
156+
throw new BadRequestException('dev_eui is required');
157+
}
158+
const skip = parseInt(req.query.skip, 10) || 0;
159+
const take = parseInt(req.query.take, 10) || 144;
160+
const start = req.query.start || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
161+
const end = req.query.end || new Date().toISOString();
162+
return this.devicesService.findDataWithinRange(req.user, devEui, req.headers.authorization, start, end, skip, take);
163+
}
164+
165+
@Get(':dev_eui/latest-data')
166+
@UseGuards(JwtAuthGuard)
167+
@ApiParam({ name: 'dev_eui', description: 'Device dev_eui' })
168+
latestData(@Req() req, @Param('dev_eui') devEui: string) {
169+
if (!devEui?.trim()) {
170+
throw new BadRequestException('dev_eui is required');
171+
}
172+
return this.devicesService.findLatestData(req.user, devEui, req.headers.authorization);
173+
}
174+
175+
@Get(':dev_eui/latest-primary-data')
176+
@UseGuards(JwtAuthGuard)
177+
@ApiParam({ name: 'dev_eui', description: 'Device dev_eui' })
178+
latestPrimaryData(@Req() req, @Param('dev_eui') devEui: string) {
179+
if (!devEui?.trim()) {
180+
throw new BadRequestException('dev_eui is required');
181+
}
182+
return this.devicesService.findLatestData(req.user, devEui, req.headers.authorization, true);
183+
}
184+
185+
@Post(':dev_eui')
186+
@UseGuards(JwtAuthGuard)
187+
@ApiParam({ name: 'dev_eui', description: 'Device dev_eui' })
188+
create(@Req() req, @Param('dev_eui') devEui: string) {
189+
throw NotImplementedException;
108190
}
109191
}

src/devices/devices.service.ts

Lines changed: 220 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import type { TableRow } from '../types/supabase';
1010

1111
@Injectable()
1212
export class DevicesService {
13-
constructor(private readonly supabaseService: SupabaseService) {}
1413

15-
async findAll(jwtPayload: any): Promise<TableRow<'cw_devices'>[]> {
14+
constructor(private readonly supabaseService: SupabaseService) { }
15+
16+
async findAll(jwtPayload: any, authHeader: string): Promise<TableRow<'cw_devices'>[]> {
17+
const accessToken = this.getAccessToken(authHeader);
18+
const client = this.supabaseService.getClient(accessToken);
1619
const userId = this.getUserId(jwtPayload);
17-
const client = this.supabaseService.getClient();
1820

1921
const { data, error } = await client
2022
.from('cw_devices')
@@ -32,15 +34,16 @@ export class DevicesService {
3234
async findOne(
3335
jwtPayload: any,
3436
devEui: string,
37+
authHeader: string,
3538
): Promise<TableRow<'cw_devices'>> {
39+
const accessToken = this.getAccessToken(authHeader);
40+
const client = this.supabaseService.getClient(accessToken);
3641
const userId = this.getUserId(jwtPayload);
3742
const normalizedDevEui = devEui?.trim();
3843
if (!normalizedDevEui) {
3944
throw new BadRequestException('dev_eui is required');
4045
}
4146

42-
const client =
43-
this.supabaseService.getAdminClient() ?? this.supabaseService.getClient();
4447
const { data, error } = await client
4548
.from('cw_devices')
4649
.select('*')
@@ -59,11 +62,223 @@ export class DevicesService {
5962
return data;
6063
}
6164

65+
public async findData(jwtPayload: any, devEui: string, skip: number = 0, take: number = 144, authHeader: string) {
66+
const accessToken = this.getAccessToken(authHeader);
67+
const client = this.supabaseService.getClient(accessToken);
68+
const userId = this.getUserId(jwtPayload);
69+
const normalizedDevEui = devEui?.trim();
70+
if (!normalizedDevEui) {
71+
throw new BadRequestException('dev_eui is required');
72+
}
73+
74+
const { data: device, error: deviceError } = await client
75+
.from('cw_devices')
76+
.select('*')
77+
.eq('user_id', userId)
78+
.eq('dev_eui', normalizedDevEui)
79+
.maybeSingle();
80+
81+
if (deviceError) {
82+
throw new InternalServerErrorException('Failed to fetch device');
83+
}
84+
85+
if (!device) {
86+
throw new NotFoundException('Device not found');
87+
}
88+
89+
const { data: deviceType, error: deviceTypeError } = await client
90+
.from('cw_device_type')
91+
.select('*')
92+
.eq('id', device.type)
93+
.maybeSingle();
94+
95+
if (deviceTypeError) {
96+
throw new InternalServerErrorException('Failed to fetch device type');
97+
}
98+
99+
if (!deviceType) {
100+
throw new NotFoundException('Device type not found');
101+
}
102+
103+
const { data: latestData, error: dataError } = await client
104+
.from(deviceType.data_table_v2)
105+
.select('*')
106+
.eq('dev_eui', normalizedDevEui)
107+
.order('created_at', { ascending: false })
108+
.range(skip, skip + take - 1)
109+
.limit(take);
110+
111+
if (dataError) {
112+
throw new InternalServerErrorException('Failed to fetch Data');
113+
}
114+
115+
if (!latestData || latestData.length === 0) {
116+
throw new NotFoundException('Data not found');
117+
}
118+
119+
return latestData;
120+
}
121+
122+
public async findDataWithinRange(
123+
jwtPayload: any,
124+
devEui: string,
125+
authHeader: string,
126+
start: Date | string = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
127+
end: Date | string = new Date().toISOString(),
128+
skip: number = 0,
129+
take: number = 144,
130+
) {
131+
const accessToken = this.getAccessToken(authHeader);
132+
const client = this.supabaseService.getClient(accessToken);
133+
const userId = this.getUserId(jwtPayload);
134+
const normalizedDevEui = devEui?.trim();
135+
if (!normalizedDevEui) {
136+
throw new BadRequestException('dev_eui is required');
137+
}
138+
139+
const { data: device, error: deviceError } = await client
140+
.from('cw_devices')
141+
.select('*')
142+
.eq('user_id', userId)
143+
.eq('dev_eui', normalizedDevEui)
144+
.maybeSingle();
145+
146+
if (deviceError) {
147+
throw new InternalServerErrorException('Failed to fetch device');
148+
}
149+
150+
if (!device) {
151+
throw new NotFoundException('Device not found');
152+
}
153+
154+
const { data: deviceType, error: deviceTypeError } = await client
155+
.from('cw_device_type')
156+
.select('*')
157+
.eq('id', device.type)
158+
.maybeSingle();
159+
160+
if (deviceTypeError) {
161+
throw new InternalServerErrorException('Failed to fetch device type');
162+
}
163+
164+
if (!deviceType) {
165+
throw new NotFoundException('Device type not found');
166+
}
167+
168+
const startDate = new Date(start);
169+
const endDate = new Date(end);
170+
171+
const { data: latestData, error: dataError } = await client
172+
.from(deviceType.data_table_v2)
173+
.select('*')
174+
.eq('dev_eui', normalizedDevEui)
175+
.gte('created_at', startDate.toISOString())
176+
.lte('created_at', endDate.toISOString())
177+
.order('created_at', { ascending: false })
178+
.range(skip, skip + take - 1);
179+
180+
if (dataError) {
181+
throw new InternalServerErrorException('Failed to fetch Data');
182+
}
183+
184+
if (!latestData || latestData.length === 0) {
185+
throw new NotFoundException('Data not found');
186+
}
187+
188+
return latestData;
189+
}
190+
191+
public async findLatestData(jwtPayload: any, devEui: string, authHeader: string, primaryAndSecondaryOnly = false) {
192+
const accessToken = this.getAccessToken(authHeader);
193+
const client = this.supabaseService.getClient(accessToken);
194+
const userId = this.getUserId(jwtPayload);
195+
const normalizedDevEui = devEui?.trim();
196+
if (!normalizedDevEui) {
197+
throw new BadRequestException('dev_eui is required');
198+
}
199+
200+
const { data: device, error: deviceError } = await client
201+
.from('cw_devices')
202+
.select('*')
203+
.eq('user_id', userId)
204+
.eq('dev_eui', normalizedDevEui)
205+
.maybeSingle();
206+
207+
if (deviceError) {
208+
throw new InternalServerErrorException('Failed to fetch device');
209+
}
210+
211+
if (!device) {
212+
throw new NotFoundException('Device not found');
213+
}
214+
215+
const { data: deviceType, error: deviceTypeError } = await client
216+
.from('cw_device_type')
217+
.select('*')
218+
.eq('id', device.type)
219+
.maybeSingle();
220+
221+
if (deviceTypeError) {
222+
throw new InternalServerErrorException('Failed to fetch device type');
223+
}
224+
225+
if (!deviceType) {
226+
throw new NotFoundException('Device type not found');
227+
}
228+
229+
const { data: latestData, error: dataError } = await client
230+
.from(deviceType.data_table_v2)
231+
.select('*')
232+
.eq('dev_eui', normalizedDevEui)
233+
.order('created_at', { ascending: false })
234+
.limit(1)
235+
.maybeSingle();
236+
237+
if (dataError) {
238+
throw new InternalServerErrorException('Failed to fetch latest data');
239+
}
240+
241+
if (!latestData) {
242+
throw new NotFoundException('Latest data not found');
243+
}
244+
245+
if (primaryAndSecondaryOnly) {
246+
const primaryField = deviceType.primary_data_v2;
247+
const secondaryField = deviceType.secondary_data_v2;
248+
if (!primaryField || !secondaryField) {
249+
throw new NotFoundException('Primary or secondary data field not defined for this device type');
250+
}
251+
return {
252+
dev_eui: normalizedDevEui,
253+
created_at: latestData.created_at,
254+
[primaryField]: latestData[primaryField],
255+
[secondaryField]: latestData[secondaryField],
256+
};
257+
}
258+
259+
return latestData;
260+
}
261+
262+
/*********************************************************************
263+
*
264+
* Private functions to handle common tasks such as extracting user ID from JWT payload,
265+
*
266+
********************************************************************/
267+
62268
private getUserId(jwtPayload: any): string {
63269
const userId = jwtPayload?.sub;
64270
if (typeof userId !== 'string' || !userId.trim()) {
65271
throw new UnauthorizedException('Invalid bearer token');
66272
}
67273
return userId;
68274
}
275+
276+
private getAccessToken(authHeader: string): string {
277+
const rawHeader = authHeader?.trim() ?? '';
278+
const [scheme, token] = rawHeader.split(' ');
279+
if (scheme?.toLowerCase() !== 'bearer' || !token) {
280+
throw new UnauthorizedException('Missing bearer token');
281+
}
282+
return token;
283+
}
69284
}

0 commit comments

Comments
 (0)