Peace 1 month ago
parent b294df9cf5
commit 51b37886e0
  1. 2
      backend/.env
  2. 2
      backend/dist/tsconfig.build.tsbuildinfo
  3. 1
      backend/dist/users/users.controller.js
  4. 2
      backend/dist/users/users.controller.js.map
  5. 1
      backend/src/sensors/entities/sensor-data.entity.ts
  6. 3
      backend/src/sensors/entities/sensor-group.entity.ts
  7. 3
      backend/src/sensors/entities/sensor.entity.ts
  8. 108
      backend/src/sensors/sensors.controller.ts
  9. 31
      backend/src/sensors/sensors.service.ts
  10. 2
      backend/src/users/entities/user.entity.ts

@ -1,6 +1,6 @@
NODE_ENV=development NODE_ENV=development
DB_HOST=localhost DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD=init00!! DB_PASSWORD=init00!!

File diff suppressed because one or more lines are too long

@ -35,6 +35,7 @@ __decorate([
(0, common_1.Patch)('password'), (0, common_1.Patch)('password'),
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard), (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard),
(0, swagger_1.ApiBearerAuth)(), (0, swagger_1.ApiBearerAuth)(),
(0, swagger_1.ApiOperation)({ summary: '비밀번호 변경' }),
(0, swagger_1.ApiOkResponse)({ description: '성공', type: user_info_response_dto_1.UserInfoResponseDto }), (0, swagger_1.ApiOkResponse)({ description: '성공', type: user_info_response_dto_1.UserInfoResponseDto }),
openapi.ApiResponse({ status: 200, type: require("./dto/user-info-response.dto").UserInfoResponseDto }), openapi.ApiResponse({ status: 200, type: require("./dto/user-info-response.dto").UserInfoResponseDto }),
__param(0, (0, common_1.Request)()), __param(0, (0, common_1.Request)()),

@ -1 +1 @@
{"version":3,"file":"users.controller.js","sourceRoot":"","sources":["../../src/users/users.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA6E;AAC7E,mDAA+C;AAC/C,mEAA8D;AAC9D,2DAAuD;AAEvD,6CAAwE;AACxE,yEAAmE;AAI5D,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,WAAyB;QAAzB,gBAAW,GAAX,WAAW,CAAc;IAAG,CAAC;IAMpD,AAAN,KAAK,CAAC,cAAc,CACP,GAAgB,EACnB,GAAsB;QAE9B,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC5D,OAAO,MAAM,IAAI,CAAC,WAAW,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxE,CAAC;CACF,CAAA;AAdY,0CAAe;AAOpB;IAJL,IAAA,cAAK,EAAC,UAAU,CAAC;IACjB,IAAA,kBAAS,EAAC,6BAAY,CAAC;IACvB,IAAA,uBAAa,GAAE;IACf,IAAA,uBAAa,EAAC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,4CAAmB,EAAE,CAAC;;IAE7D,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,uCAAiB;;qDAI/B;0BAbU,eAAe;IAF3B,IAAA,iBAAO,EAAC,KAAK,CAAC;IACd,IAAA,mBAAU,EAAC,OAAO,CAAC;qCAEwB,4BAAY;GAD3C,eAAe,CAc3B"} {"version":3,"file":"users.controller.js","sourceRoot":"","sources":["../../src/users/users.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA6E;AAC7E,mDAA+C;AAC/C,mEAA8D;AAC9D,2DAAuD;AAEvD,6CAAsF;AACtF,yEAAmE;AAI5D,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,WAAyB;QAAzB,gBAAW,GAAX,WAAW,CAAc;IAAG,CAAC;IAOpD,AAAN,KAAK,CAAC,cAAc,CACP,GAAgB,EACnB,GAAsB;QAE9B,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC5D,OAAO,MAAM,IAAI,CAAC,WAAW,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxE,CAAC;CACF,CAAA;AAfY,0CAAe;AAQpB;IALL,IAAA,cAAK,EAAC,UAAU,CAAC;IACjB,IAAA,kBAAS,EAAC,6BAAY,CAAC;IACvB,IAAA,uBAAa,GAAE;IACf,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IACpC,IAAA,uBAAa,EAAC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,4CAAmB,EAAE,CAAC;;IAE7D,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,uCAAiB;;qDAI/B;0BAdU,eAAe;IAF3B,IAAA,iBAAO,EAAC,KAAK,CAAC;IACd,IAAA,mBAAU,EAAC,OAAO,CAAC;qCAEwB,4BAAY;GAD3C,eAAe,CAe3B"}

@ -9,6 +9,7 @@ import {
import { Sensor } from './sensor.entity'; import { Sensor } from './sensor.entity';
@Entity() @Entity()
@Index(['sensor', 'recordedAt'])
export class SensorData { export class SensorData {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;

@ -1,6 +1,6 @@
import { TimestampedEntity } from 'src/common/entities/timestamped.entity'; import { TimestampedEntity } from 'src/common/entities/timestamped.entity';
import { User } from 'src/users/entities/user.entity'; import { User } from 'src/users/entities/user.entity';
import { Column, Entity, ManyToMany, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { Column, Entity, Index, ManyToMany, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Sensor } from './sensor.entity'; import { Sensor } from './sensor.entity';
@Entity() @Entity()
@ -14,6 +14,7 @@ export class SensorGroup extends TimestampedEntity {
@Column() @Column()
description: string; description: string;
@Index()
@ManyToMany(() => User, (user) => user.sensorGroups) @ManyToMany(() => User, (user) => user.sensorGroups)
users: User[]; users: User[];

@ -1,5 +1,5 @@
import { TimestampedEntity } from 'src/common/entities/timestamped.entity'; import { TimestampedEntity } from 'src/common/entities/timestamped.entity';
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { SensorGroup } from './sensor-group.entity'; import { SensorGroup } from './sensor-group.entity';
import { SensorData } from './sensor-data.entity'; import { SensorData } from './sensor-data.entity';
@ -14,6 +14,7 @@ export class Sensor extends TimestampedEntity {
@Column() @Column()
unit: string; unit: string;
@Index()
@ManyToOne(() => SensorGroup, (group) => group.sensors, { onDelete: 'CASCADE' }) // FK Onwer @ManyToOne(() => SensorGroup, (group) => group.sensors, { onDelete: 'CASCADE' }) // FK Onwer
group: SensorGroup; group: SensorGroup;

@ -1,24 +1,108 @@
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; import {
Body,
Controller,
Request,
Post,
UseGuards,
Get,
ParseIntPipe,
Param,
Query,
} from '@nestjs/common';
import { SensorsService } from './sensors.service'; import { SensorsService } from './sensors.service';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { Sensor } from './entities/sensor.entity'; import { Sensor } from './entities/sensor.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';
import { SensorGroupResponseDto } from './dto/sensor-group-response.dto';
import { AuthRequest } from 'src/common/interfaces/auth-request.interface';
import { CreateSensorGroupDto } from './dto/create-sensor-group.dto';
import { SensorResponseDto } from './dto/sensor-response.dto';
import { CreateSensorDto } from './dto/create-sensor.dto';
import { SensorDataResponseDto } from './dto/sensor-data-response.dto';
import { CreateSensorDataDto } from './dto/create-sensor-data.dto';
@ApiTags('센서') @ApiTags('센서')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('sensors') @Controller('sensors')
export class SensorsController { export class SensorsController {
constructor(private readonly sensorService: SensorsService) {} constructor(private readonly sensorService: SensorsService) {}
@Get() //#region Group
@ApiOperation({ summary: '센서 목록 조회' }) @Post('sensor-groups')
@ApiOkResponse({ description: '센서 목록', type: [Sensor] }) @ApiOperation({ summary: '센서 그룹 생성' })
async findAll(): Promise<Sensor[]> { @ApiOkResponse({ description: '성공', type: SensorGroupResponseDto })
return this.findAll(); async createGroup(
@Request() req: AuthRequest,
@Body() dto: CreateSensorGroupDto,
): Promise<SensorGroupResponseDto> {
return this.sensorService.createGroup(req.user.userId, dto);
} }
@Get(':id') @Get('sensor-groups')
@ApiOperation({ summary: '특정 센서 조회' }) @ApiOperation({ summary: '센서 그룹 목록' })
@ApiOkResponse({ description: '센서', type: [Sensor] }) @ApiOkResponse({ description: '성공', type: [SensorGroupResponseDto] })
async findOne(@Param('id', ParseIntPipe) id: number): Promise<Sensor | null> { async myGroup(@Request() req: AuthRequest): Promise<SensorGroupResponseDto[]> {
return this.findOne(id); return this.sensorService.findMyGroups(req.user.userId);
} }
//#endregion
//#region Sensor
@Post('sensors')
@ApiOperation({ summary: '센서 생성' })
@ApiOkResponse({ description: '성공', type: SensorResponseDto })
async createSensor(
@Request() req: AuthRequest,
@Body() dto: CreateSensorDto,
): Promise<SensorResponseDto> {
return this.sensorService.createSensor(req.user.userId, dto);
}
@Get('sensor-groups/:groupId/sensors')
@ApiOperation({ summary: '센서 목록' })
@ApiOkResponse({ description: '성공', type: [SensorResponseDto] })
async sensorsInGroup(
@Request() req: AuthRequest,
@Param('groupId', ParseIntPipe) groupId: number,
): Promise<SensorResponseDto[]> {
return this.sensorService.findSensorsInGroup(req.user.userId, groupId);
}
//#endregion
//#region Data
@Post('sensors/:sensorId/data')
@ApiOperation({ summary: '센서 데이터 생성' })
@ApiOkResponse({ description: '성공', type: SensorDataResponseDto })
async createData(
@Request() req: AuthRequest,
@Param('sensorId', ParseIntPipe) sensorId: number,
@Body() dto: CreateSensorDataDto,
): Promise<SensorDataResponseDto> {
return this.sensorService.createSensorData(req.user.userId, sensorId, dto);
}
@Get('sensors/:sensorId/data')
@ApiOperation({ summary: '센서 데이터 조회' })
@ApiQuery({ name: 'from', required: false, description: 'ISO날짜' })
@ApiQuery({ name: 'to', required: false, description: 'ISO날짜' })
@ApiQuery({ name: 'limit', required: false, description: '조회 개수 (기본50, 최대 500)' })
@ApiOkResponse({ description: '성공', type: [SensorDataResponseDto] })
async getData(
@Request() req: AuthRequest,
@Param('sensorId', ParseIntPipe) sensorId: number,
@Query('from') from?: string,
@Query('to') to?: string,
@Query('limit') limit?: string,
): Promise<SensorDataResponseDto[]> {
const parsedFrom = from ? new Date(from) : undefined;
const parsedTo = to ? new Date(to) : undefined;
const parsedLimit = limit ? Number(limit) : undefined;
return this.sensorService.findSensorData(req.user.userId, sensorId, {
from: parsedFrom,
to: parsedTo,
limit: parsedLimit,
});
}
//#endregion
} }

@ -137,12 +137,31 @@ export class SensorsService {
recordedAt: saved.recordedAt, recordedAt: saved.recordedAt,
}; };
} }
//#endregion
async findAll(): Promise<Sensor[]> {
return this.sensorRepo.find({ relations: ['group'] });
}
async findOne(id: number): Promise<Sensor | null> { async findSensorData(
return this.sensorRepo.findOne({ where: { id }, relations: ['group'] }); currentUserId: number,
sensorId: number,
opts: { from?: Date; to?: Date; limit?: number },
): Promise<SensorDataResponseDto[]> {
await this.assertMemeberOfSensor(currentUserId, sensorId);
const take = Math.min(Math.max(opts.limit ?? 50, 1), 500); // 1 ~ 500개
const data = this.dataRepo
.createQueryBuilder('d')
.select(['d.id', 'd.value', 'd.recordedAt'])
.where('data.sensorId = :sensorId', { sensorId });
if (opts.from || opts.to) {
data.andWhere('data.recordedAt BETWEEN :from AND :to', {
from: opts.from ?? new Date(0),
to: opts.to ?? new Date(),
});
}
data.orderBy('data.recordedAt', 'DESC').take(take);
const list = await data.getRawMany<SensorData>();
return list.map((d) => ({ id: d.id, value: d.value, recordedAt: d.recordedAt }));
} }
//#endregion
} }

@ -5,6 +5,7 @@ import {
Column, Column,
DeleteDateColumn, DeleteDateColumn,
Entity, Entity,
Index,
JoinColumn, JoinColumn,
JoinTable, JoinTable,
ManyToMany, ManyToMany,
@ -35,5 +36,6 @@ export class User extends TimestampedEntity {
@ManyToMany(() => SensorGroup, (group) => group.users) @ManyToMany(() => SensorGroup, (group) => group.users)
@JoinTable() // Join table Owner @JoinTable() // Join table Owner
@Index()
sensorGroups: SensorGroup[]; sensorGroups: SensorGroup[];
} }

Loading…
Cancel
Save