storages and storage sets
This commit is contained in:
parent
e79e09065c
commit
2b420831ac
153
src/app-storage/app-storage.controller.ts
Normal file
153
src/app-storage/app-storage.controller.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import {
|
||||
Body,
|
||||
ClassSerializerInterceptor,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Patch,
|
||||
Post,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBody,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiSecurity,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { Room } from 'src/objects/building/entities/room.entity';
|
||||
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import { CurrentRoom } from 'src/shared/decorators/current-room.decorator';
|
||||
import { CurrentStorageSet } from 'src/shared/decorators/current-storage-set.decorator';
|
||||
import { CurrentStorage } from 'src/shared/decorators/current-storage.decorator';
|
||||
import { LoggedInUser } from 'src/shared/decorators/user.decorator';
|
||||
import { AuthGuard } from 'src/shared/guards/auth.guard';
|
||||
import { BuildingGuard } from 'src/shared/guards/building.guard';
|
||||
import { RoomGuard } from 'src/shared/guards/room.guard';
|
||||
import { StorageSetGuard } from 'src/shared/guards/storage-set.guard';
|
||||
import { StorageGuard } from 'src/shared/guards/storage.guard';
|
||||
import { AppStorageService } from './app-storage.service';
|
||||
import {
|
||||
StorageCreateRequestDto,
|
||||
StorageUpdateRequestDto,
|
||||
} from './dto/storage-request.dto';
|
||||
import { StorageResponseDto } from './dto/storage-response.dto';
|
||||
import {
|
||||
StorageSetCreateRequestDto,
|
||||
StorageSetUpdateRequestDto,
|
||||
} from './dto/storage-set-request.dto';
|
||||
import { StorageSetResponseDto } from './dto/storage-set-response.dto';
|
||||
|
||||
@Controller({
|
||||
path: 'storage',
|
||||
})
|
||||
@ApiTags('storage')
|
||||
@ApiSecurity('Bearer token')
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@UseGuards(AuthGuard, BuildingGuard, RoomGuard, StorageGuard)
|
||||
export class AppStorageController {
|
||||
constructor(private readonly service: AppStorageService) {}
|
||||
|
||||
@Get(':storageId')
|
||||
@ApiOperation({ summary: 'Get storage by ID' })
|
||||
@ApiOkResponse({ type: StorageResponseDto })
|
||||
async getStorage(
|
||||
@CurrentStorage() storage: Storage,
|
||||
): Promise<StorageResponseDto> {
|
||||
return this.service.formatStorageNoItems(storage);
|
||||
}
|
||||
|
||||
@Patch(':storageId')
|
||||
@ApiBody({ type: StorageUpdateRequestDto })
|
||||
@ApiOperation({ summary: 'Update storage by ID' })
|
||||
@ApiOkResponse({ type: StorageResponseDto })
|
||||
async updateStorage(
|
||||
@CurrentStorage() storage: Storage,
|
||||
@Body() body: StorageUpdateRequestDto,
|
||||
): Promise<StorageResponseDto> {
|
||||
return this.service.updateStorage(storage, body);
|
||||
}
|
||||
|
||||
@UseGuards(StorageSetGuard)
|
||||
@Get('set/:storageSetId')
|
||||
@ApiOperation({ summary: 'Get storage set' })
|
||||
@ApiOkResponse({ type: StorageSetResponseDto })
|
||||
async getStorageSet(@CurrentStorageSet() set: StorageSet) {
|
||||
return this.service.formatStorageSetNoItems(set);
|
||||
}
|
||||
|
||||
@UseGuards(StorageSetGuard)
|
||||
@Patch('set/:storageSetId')
|
||||
@ApiBody({ type: StorageSetUpdateRequestDto })
|
||||
@ApiOperation({ summary: 'Update storage set by ID' })
|
||||
@ApiOkResponse({ type: StorageSetResponseDto })
|
||||
async updateStorageSet(
|
||||
@CurrentStorageSet() set: StorageSet,
|
||||
@Body() body: StorageSetUpdateRequestDto,
|
||||
): Promise<StorageSetResponseDto> {
|
||||
return this.service.updateStorageSet(set, body);
|
||||
}
|
||||
|
||||
@UseGuards(StorageSetGuard)
|
||||
@Post('set/:storageSetId/:storageId')
|
||||
@ApiOperation({ summary: 'Move storage to storage set' })
|
||||
@ApiOkResponse({ type: StorageSetResponseDto })
|
||||
async moveStorage(
|
||||
@CurrentStorageSet() set: StorageSet,
|
||||
@CurrentStorage() storage: Storage,
|
||||
): Promise<StorageSetResponseDto> {
|
||||
return this.service.moveStorage(set, storage);
|
||||
}
|
||||
|
||||
@UseGuards(StorageSetGuard)
|
||||
@Delete('set/:storageSetId/:storageId')
|
||||
@ApiOperation({ summary: 'Remove storage from storage set' })
|
||||
@ApiOkResponse({ type: StorageSetResponseDto })
|
||||
async removeStorageFromSet(
|
||||
@CurrentStorageSet() set: StorageSet,
|
||||
@CurrentStorage() storage: Storage,
|
||||
): Promise<StorageSetResponseDto> {
|
||||
return this.service.removeFromSet(set, storage);
|
||||
}
|
||||
|
||||
@Get('room/:roomId')
|
||||
@ApiOperation({ summary: 'Get storages in room' })
|
||||
@ApiOkResponse({ type: StorageResponseDto, isArray: true })
|
||||
async getStorages(@CurrentRoom() room: Room) {
|
||||
return this.service.getStoragesInRoom(room.id);
|
||||
}
|
||||
|
||||
@Get('set/room/:roomId')
|
||||
@ApiOperation({ summary: 'Get storage sets in room' })
|
||||
@ApiOkResponse({ type: StorageSetResponseDto, isArray: true })
|
||||
async getStorageSets(@CurrentRoom() room: Room) {
|
||||
return this.service.getStorageSetsInRoom(room.id);
|
||||
}
|
||||
|
||||
@Post('set/room/:roomId')
|
||||
@ApiBody({ type: StorageCreateRequestDto })
|
||||
@ApiOperation({ summary: 'Create storage sets in room' })
|
||||
@ApiOkResponse({ type: StorageSetResponseDto, isArray: true })
|
||||
async createStorageSet(
|
||||
@LoggedInUser() user: User,
|
||||
@Body() body: StorageSetCreateRequestDto,
|
||||
@CurrentRoom() room: Room,
|
||||
) {
|
||||
return this.service.createStorageSet(user, room, body);
|
||||
}
|
||||
|
||||
@Post('room/:roomId')
|
||||
@ApiBody({ type: StorageCreateRequestDto })
|
||||
@ApiOperation({ summary: 'Add a new storage to room' })
|
||||
@ApiOkResponse({ type: StorageResponseDto })
|
||||
async addStorage(
|
||||
@LoggedInUser() user: User,
|
||||
@Body() body: StorageCreateRequestDto,
|
||||
@CurrentRoom() room: Room,
|
||||
): Promise<StorageResponseDto> {
|
||||
return this.service.createStorage(user, room, body);
|
||||
}
|
||||
}
|
15
src/app-storage/app-storage.module.ts
Normal file
15
src/app-storage/app-storage.module.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BuildingModule } from 'src/objects/building/building.module';
|
||||
import { GroupModule } from 'src/objects/group/group.module';
|
||||
import { StorageModule } from 'src/objects/storage/storage.module';
|
||||
import { UserModule } from 'src/objects/user/user.module';
|
||||
import { AuthModule } from 'src/shared/auth/auth.module';
|
||||
import { AppStorageController } from './app-storage.controller';
|
||||
import { AppStorageService } from './app-storage.service';
|
||||
|
||||
@Module({
|
||||
imports: [BuildingModule, AuthModule, UserModule, GroupModule, StorageModule],
|
||||
providers: [AppStorageService],
|
||||
controllers: [AppStorageController],
|
||||
})
|
||||
export class AppStorageModule {}
|
121
src/app-storage/app-storage.service.ts
Normal file
121
src/app-storage/app-storage.service.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import omit from 'lodash.omit';
|
||||
import pick from 'lodash.pick';
|
||||
import { BuildingService } from 'src/objects/building/building.service';
|
||||
import { Room } from 'src/objects/building/entities/room.entity';
|
||||
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||
import { StorageService } from 'src/objects/storage/storage.service';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import {
|
||||
StorageCreateRequestDto,
|
||||
StorageUpdateRequestDto,
|
||||
} from './dto/storage-request.dto';
|
||||
import {
|
||||
StorageActorResponse,
|
||||
StorageResponseDto,
|
||||
} from './dto/storage-response.dto';
|
||||
import {
|
||||
StorageSetCreateRequestDto,
|
||||
StorageSetUpdateRequestDto,
|
||||
} from './dto/storage-set-request.dto';
|
||||
import { StorageSetResponseDto } from './dto/storage-set-response.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AppStorageService {
|
||||
constructor(
|
||||
private readonly buildingService: BuildingService,
|
||||
private readonly storageService: StorageService,
|
||||
) {}
|
||||
|
||||
async getStoragesInRoom(roomId: number) {
|
||||
const storages = await this.storageService.getStoragesInRoom(roomId);
|
||||
return storages.map((storage) => this.formatStorageNoItems(storage));
|
||||
}
|
||||
|
||||
async getStorageSetsInRoom(roomId: number) {
|
||||
const sets = await this.storageService.getStorageSetsInRoom(roomId);
|
||||
return sets.map((set) => this.formatStorageSetNoItems(set));
|
||||
}
|
||||
|
||||
async createStorage(user: User, room: Room, body: StorageCreateRequestDto) {
|
||||
// TODO: validate color and location
|
||||
const newStorage = await this.storageService.saveStorage({
|
||||
...body,
|
||||
addedBy: user,
|
||||
room,
|
||||
});
|
||||
|
||||
return this.formatStorageNoItems(newStorage);
|
||||
}
|
||||
|
||||
async createStorageSet(
|
||||
user: User,
|
||||
room: Room,
|
||||
body: StorageSetCreateRequestDto,
|
||||
) {
|
||||
// TODO: validate color and location
|
||||
const newStorageSet = await this.storageService.saveStorageSet({
|
||||
...body,
|
||||
addedBy: user,
|
||||
room,
|
||||
});
|
||||
|
||||
return this.formatStorageSetNoItems(newStorageSet);
|
||||
}
|
||||
|
||||
async updateStorage(storage: Storage, body: StorageUpdateRequestDto) {
|
||||
Object.assign(storage, body);
|
||||
await this.storageService.saveStorage(storage);
|
||||
return this.formatStorageNoItems(storage);
|
||||
}
|
||||
|
||||
async updateStorageSet(
|
||||
storageSet: StorageSet,
|
||||
body: StorageSetUpdateRequestDto,
|
||||
) {
|
||||
Object.assign(storageSet, body);
|
||||
await this.storageService.saveStorageSet(storageSet);
|
||||
return this.formatStorageSetNoItems(storageSet);
|
||||
}
|
||||
|
||||
async moveStorage(set: StorageSet, storage: Storage) {
|
||||
storage.set = set;
|
||||
|
||||
await this.storageService.saveStorage(storage);
|
||||
|
||||
set.storages = [...(set.storages || []), storage];
|
||||
|
||||
return this.formatStorageSetNoItems(set);
|
||||
}
|
||||
|
||||
async removeFromSet(set: StorageSet, storage: Storage) {
|
||||
storage.set = null;
|
||||
await this.storageService.saveStorage(storage);
|
||||
set.storages = (set.storages || []).filter(
|
||||
(entry) => entry.id !== storage.id,
|
||||
);
|
||||
return this.formatStorageSetNoItems(set);
|
||||
}
|
||||
|
||||
formatActor(input: User): StorageActorResponse {
|
||||
return pick(input, ['name', 'sub', 'color']);
|
||||
}
|
||||
|
||||
formatStorageNoItems(storage: Storage): StorageResponseDto {
|
||||
return {
|
||||
...omit(storage, ['room', 'set', 'items']),
|
||||
addedBy: storage.addedBy && this.formatActor(storage.addedBy),
|
||||
};
|
||||
}
|
||||
|
||||
formatStorageSetNoItems(set: StorageSet): StorageSetResponseDto {
|
||||
return {
|
||||
...omit(set, ['room']),
|
||||
addedBy: set.addedBy && this.formatActor(set.addedBy),
|
||||
storages: set.storages?.length
|
||||
? set.storages.map((storage) => this.formatStorageNoItems(storage))
|
||||
: [],
|
||||
};
|
||||
}
|
||||
}
|
38
src/app-storage/dto/storage-request.dto.ts
Normal file
38
src/app-storage/dto/storage-request.dto.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';
|
||||
import {
|
||||
IsEnum,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { StorageType } from 'src/objects/storage/enums/storage-type.enum';
|
||||
|
||||
export class StorageCreateRequestDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(32)
|
||||
displayName: string;
|
||||
|
||||
@ApiProperty({ type: String, enum: StorageType })
|
||||
@IsEnum(StorageType)
|
||||
type: StorageType;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
location: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
locationDescription?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
color: string;
|
||||
}
|
||||
|
||||
export class StorageUpdateRequestDto extends PartialType(
|
||||
StorageCreateRequestDto,
|
||||
) {}
|
19
src/app-storage/dto/storage-response.dto.ts
Normal file
19
src/app-storage/dto/storage-response.dto.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ApiPropertyOptional, OmitType, PickType } from '@nestjs/swagger';
|
||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
|
||||
export class StorageActorResponse extends PickType(User, [
|
||||
'sub',
|
||||
'name',
|
||||
'color',
|
||||
]) {}
|
||||
|
||||
export class StorageResponseDto extends OmitType(Storage, [
|
||||
'room',
|
||||
'items',
|
||||
'set',
|
||||
'addedBy',
|
||||
]) {
|
||||
@ApiPropertyOptional({ type: StorageActorResponse })
|
||||
addedBy: StorageActorResponse;
|
||||
}
|
38
src/app-storage/dto/storage-set-request.dto.ts
Normal file
38
src/app-storage/dto/storage-set-request.dto.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';
|
||||
import {
|
||||
IsEnum,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { StorageSetType } from 'src/objects/storage/enums/storage-set-type.enum';
|
||||
|
||||
export class StorageSetCreateRequestDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(32)
|
||||
displayName: string;
|
||||
|
||||
@ApiProperty({ type: String, enum: StorageSetType })
|
||||
@IsEnum(StorageSetType)
|
||||
type: StorageSetType;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
location: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
locationDescription?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
color: string;
|
||||
}
|
||||
|
||||
export class StorageSetUpdateRequestDto extends PartialType(
|
||||
StorageSetCreateRequestDto,
|
||||
) {}
|
18
src/app-storage/dto/storage-set-response.dto.ts
Normal file
18
src/app-storage/dto/storage-set-response.dto.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
|
||||
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
||||
import {
|
||||
StorageActorResponse,
|
||||
StorageResponseDto,
|
||||
} from './storage-response.dto';
|
||||
|
||||
export class StorageSetResponseDto extends OmitType(StorageSet, [
|
||||
'room',
|
||||
'addedBy',
|
||||
'storages',
|
||||
]) {
|
||||
@ApiPropertyOptional({ type: StorageActorResponse })
|
||||
addedBy: StorageActorResponse;
|
||||
|
||||
@ApiProperty({ type: StorageResponseDto, isArray: true })
|
||||
storages: StorageResponseDto[];
|
||||
}
|
@ -3,6 +3,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AppBuildingModule } from './app-building/app-building.module';
|
||||
import { AppGroupModule } from './app-group/app-group.module';
|
||||
import { AppStorageModule } from './app-storage/app-storage.module';
|
||||
import { AppUser } from './app-user/app-user.module';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
@ -42,6 +43,7 @@ import { SecretsModule } from './shared/secrets/secrets.module';
|
||||
AppUser,
|
||||
AppGroupModule,
|
||||
AppBuildingModule,
|
||||
AppStorageModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
@ -89,6 +89,34 @@ export class BuildingService {
|
||||
});
|
||||
}
|
||||
|
||||
async getRoomByIdAndUserSub(roomId: number, sub: string, relations = []) {
|
||||
return this.roomRepository.findOne({
|
||||
where: {
|
||||
id: roomId,
|
||||
building: {
|
||||
groups: {
|
||||
members: {
|
||||
sub,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
relations,
|
||||
});
|
||||
}
|
||||
|
||||
async getRoomByBuilding(buildingId: number, roomId: number, relations = []) {
|
||||
return this.roomRepository.findOne({
|
||||
where: {
|
||||
id: roomId,
|
||||
building: {
|
||||
id: buildingId,
|
||||
},
|
||||
},
|
||||
relations,
|
||||
});
|
||||
}
|
||||
|
||||
async getRoomsOnFloorByBuildingAndUserSub(
|
||||
buildingId: number,
|
||||
floorNumber: number,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import {
|
||||
Column,
|
||||
@ -12,39 +13,49 @@ import { StoredItem } from './stored-item.entity';
|
||||
|
||||
@Entity()
|
||||
export class StoredItemTransaction {
|
||||
@ApiProperty()
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ApiProperty({ type: String, enum: TransactionType })
|
||||
@Column({ type: String, default: TransactionType.ACQUIRED })
|
||||
type: TransactionType;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
price?: number;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true, default: 'EUR', length: 3 })
|
||||
currency?: string;
|
||||
|
||||
@ApiProperty({ type: () => StoredItem })
|
||||
@ManyToOne(() => StoredItem, {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
})
|
||||
storedItem: StoredItem;
|
||||
|
||||
@ApiPropertyOptional({ type: () => User })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
actor?: User;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
notes?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ type: 'datetime', nullable: true })
|
||||
actionAt?: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import {
|
||||
Column,
|
||||
@ -13,48 +14,62 @@ import { StoredItem } from './stored-item.entity';
|
||||
|
||||
@Entity()
|
||||
export class Item {
|
||||
@ApiProperty()
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
displayName: string;
|
||||
|
||||
@ApiProperty({ type: String, enum: ItemType })
|
||||
@Column({ type: String, default: ItemType.ITEM })
|
||||
type: ItemType;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
barcode?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ default: false })
|
||||
consumable: boolean;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
image?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
weight?: number;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
url?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
notes?: string;
|
||||
|
||||
@ApiPropertyOptional({ type: () => StoredItem, isArray: true })
|
||||
@OneToMany(() => StoredItem, (store) => store.item)
|
||||
instances?: StoredItem[];
|
||||
|
||||
@ApiProperty({ type: () => User })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
addedBy: User;
|
||||
|
||||
@ApiProperty()
|
||||
@Column({ default: false })
|
||||
public: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Room } from 'src/objects/building/entities/room.entity';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import {
|
||||
@ -14,42 +15,53 @@ import { Storage } from './storage.entity';
|
||||
|
||||
@Entity()
|
||||
export class StorageSet {
|
||||
@ApiProperty()
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
displayName: string;
|
||||
|
||||
@ApiProperty({ type: () => Room })
|
||||
@ManyToOne(() => Room, {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
})
|
||||
room: Room;
|
||||
|
||||
@ApiProperty({ type: String, enum: StorageSetType })
|
||||
@Column({ type: String })
|
||||
type: StorageSetType;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
location: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
locationDescription: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
color: string;
|
||||
|
||||
@ApiProperty({ type: () => Storage, isArray: true })
|
||||
@OneToMany(() => Storage, (storage) => storage.set)
|
||||
storages: Storage[];
|
||||
|
||||
@ApiProperty({ type: () => User })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
addedBy: User;
|
||||
|
||||
@ApiProperty()
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Room } from 'src/objects/building/entities/room.entity';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import {
|
||||
@ -15,30 +16,38 @@ import { StoredItem } from './stored-item.entity';
|
||||
|
||||
@Entity()
|
||||
export class Storage {
|
||||
@ApiProperty()
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
displayName: string;
|
||||
|
||||
@ApiProperty({ type: () => Room })
|
||||
@ManyToOne(() => Room, {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
})
|
||||
room: Room;
|
||||
|
||||
@ApiProperty({ type: String, enum: StorageType })
|
||||
@Column({ type: String })
|
||||
type: StorageType;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
location: string;
|
||||
|
||||
@Column()
|
||||
locationDescription: string;
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
locationDescription?: string;
|
||||
|
||||
@ApiProperty({ type: () => StoredItem, isArray: true })
|
||||
@OneToMany(() => StoredItem, (item) => item.storage)
|
||||
items: StoredItem[];
|
||||
|
||||
@ApiProperty({ type: () => StorageSet })
|
||||
@ManyToOne(() => StorageSet, {
|
||||
nullable: true,
|
||||
onDelete: 'SET NULL',
|
||||
@ -46,18 +55,22 @@ export class Storage {
|
||||
})
|
||||
set?: StorageSet;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
color: string;
|
||||
|
||||
@ApiProperty({ type: () => User })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
addedBy: User;
|
||||
|
||||
@ApiProperty()
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
@ -1,29 +1,36 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { StoredItemTransaction } from './item-transaction.entity';
|
||||
import { Item } from './item.entity';
|
||||
import { Storage } from './storage.entity';
|
||||
|
||||
@Entity()
|
||||
export class StoredItem {
|
||||
@ApiProperty()
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ApiProperty()
|
||||
@Column()
|
||||
displayName: string;
|
||||
|
||||
@ApiProperty({ type: () => Item })
|
||||
@ManyToOne(() => Item, {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
})
|
||||
item: Item;
|
||||
|
||||
@ApiPropertyOptional({ type: () => Storage })
|
||||
@ManyToOne(() => Storage, {
|
||||
nullable: true,
|
||||
onDelete: 'SET NULL',
|
||||
@ -31,27 +38,41 @@ export class StoredItem {
|
||||
})
|
||||
storage?: Storage;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true })
|
||||
notes?: string;
|
||||
|
||||
@ApiProperty({ type: () => User })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
addedBy: User;
|
||||
|
||||
@ApiPropertyOptional({ type: () => StoredItemTransaction, isArray: true })
|
||||
@OneToMany(
|
||||
() => StoredItemTransaction,
|
||||
(transaction) => transaction.storedItem,
|
||||
)
|
||||
transactions?: StoredItemTransaction[];
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
expiresAt?: Date;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
acquiredAt?: Date;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
consumedAt?: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
@ -27,4 +27,163 @@ export class StorageService {
|
||||
private readonly userService: UserService,
|
||||
private readonly buildingService: BuildingService,
|
||||
) {}
|
||||
|
||||
async getStorageByIdAndSub(id: number, sub: string, relations = []) {
|
||||
return this.storageRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
room: {
|
||||
building: {
|
||||
groups: {
|
||||
members: {
|
||||
sub,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
relations,
|
||||
});
|
||||
}
|
||||
|
||||
async getStorageByIdAndBuilding(
|
||||
id: number,
|
||||
buildingId: number,
|
||||
relations = [],
|
||||
) {
|
||||
return this.storageRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
room: {
|
||||
building: {
|
||||
id: buildingId,
|
||||
},
|
||||
},
|
||||
},
|
||||
relations,
|
||||
});
|
||||
}
|
||||
|
||||
async getStorageByIdAndRoom(id: number, roomId: number, relations = []) {
|
||||
return this.storageRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
room: {
|
||||
id: roomId,
|
||||
},
|
||||
},
|
||||
relations,
|
||||
});
|
||||
}
|
||||
|
||||
async getStorageSetByIdAndSub(id: number, sub: string, relations = []) {
|
||||
return this.storageSetRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
room: {
|
||||
building: {
|
||||
groups: {
|
||||
members: {
|
||||
sub,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: ['storages', 'storages.addedBy', ...relations],
|
||||
});
|
||||
}
|
||||
|
||||
async getStorageSetByIdAndBuilding(
|
||||
id: number,
|
||||
buildingId: number,
|
||||
relations = [],
|
||||
) {
|
||||
return this.storageSetRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
room: {
|
||||
building: {
|
||||
id: buildingId,
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: ['storages', 'storages.addedBy', ...relations],
|
||||
});
|
||||
}
|
||||
|
||||
async getStorageSetByIdAndRoom(id: number, roomId: number, relations = []) {
|
||||
return this.storageSetRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
room: {
|
||||
id: roomId,
|
||||
},
|
||||
},
|
||||
relations: ['storages', 'storages.addedBy', ...relations],
|
||||
});
|
||||
}
|
||||
|
||||
async getStoragesInRoom(roomId: number, relations = []) {
|
||||
return this.storageRepository.find({
|
||||
where: {
|
||||
room: {
|
||||
id: roomId,
|
||||
},
|
||||
},
|
||||
relations,
|
||||
});
|
||||
}
|
||||
|
||||
async getStorageSetsInRoom(roomId: number, relations = []) {
|
||||
return this.storageSetRepository.find({
|
||||
where: {
|
||||
room: {
|
||||
id: roomId,
|
||||
},
|
||||
},
|
||||
relations: ['storages', 'storages.addedBy', ...relations],
|
||||
});
|
||||
}
|
||||
|
||||
async getItemsInStorage(storageId: number, relations = []) {
|
||||
return this.storedItemRepository.find({
|
||||
where: {
|
||||
storage: {
|
||||
id: storageId,
|
||||
},
|
||||
},
|
||||
relations: ['item', ...relations],
|
||||
});
|
||||
}
|
||||
|
||||
async saveStorage(data: Partial<Storage>) {
|
||||
const newStorage = new Storage();
|
||||
Object.assign(newStorage, data);
|
||||
return this.storageRepository.save(newStorage);
|
||||
}
|
||||
|
||||
async saveItem(data: Partial<Item>) {
|
||||
const newItem = new Item();
|
||||
Object.assign(newItem, data);
|
||||
return this.itemRepository.save(newItem);
|
||||
}
|
||||
|
||||
async saveStoredItem(data: Partial<StoredItem>) {
|
||||
const newStoredItem = new StoredItem();
|
||||
Object.assign(newStoredItem, data);
|
||||
return this.storedItemRepository.save(newStoredItem);
|
||||
}
|
||||
|
||||
async saveStorageSet(data: Partial<StorageSet>) {
|
||||
const newStorageSet = new StorageSet();
|
||||
Object.assign(newStorageSet, data);
|
||||
return this.storageSetRepository.save(newStorageSet);
|
||||
}
|
||||
|
||||
async saveStoredItemTransaction(data: Partial<StoredItemTransaction>) {
|
||||
const newStoredItemTransaction = new StoredItemTransaction();
|
||||
Object.assign(newStoredItemTransaction, data);
|
||||
return this.transactionRepository.save(newStoredItemTransaction);
|
||||
}
|
||||
}
|
||||
|
13
src/shared/decorators/current-building.decorator.ts
Normal file
13
src/shared/decorators/current-building.decorator.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { Building } from 'src/objects/building/entities/building.entity';
|
||||
|
||||
/**
|
||||
* Get the building from the response.
|
||||
*/
|
||||
export const CurrentBuilding = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const response = ctx.switchToHttp().getResponse() as Response;
|
||||
return response.locals.building as Building;
|
||||
},
|
||||
);
|
13
src/shared/decorators/current-room.decorator.ts
Normal file
13
src/shared/decorators/current-room.decorator.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { Room } from 'src/objects/building/entities/room.entity';
|
||||
|
||||
/**
|
||||
* Get the room from the response.
|
||||
*/
|
||||
export const CurrentRoom = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const response = ctx.switchToHttp().getResponse() as Response;
|
||||
return response.locals.room as Room;
|
||||
},
|
||||
);
|
13
src/shared/decorators/current-storage-set.decorator.ts
Normal file
13
src/shared/decorators/current-storage-set.decorator.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
||||
|
||||
/**
|
||||
* Get the storage from the response.
|
||||
*/
|
||||
export const CurrentStorageSet = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const response = ctx.switchToHttp().getResponse() as Response;
|
||||
return response.locals.storageSet as StorageSet;
|
||||
},
|
||||
);
|
13
src/shared/decorators/current-storage.decorator.ts
Normal file
13
src/shared/decorators/current-storage.decorator.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||
|
||||
/**
|
||||
* Get the storage from the response.
|
||||
*/
|
||||
export const CurrentStorage = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const response = ctx.switchToHttp().getResponse() as Response;
|
||||
return response.locals.storage as Storage;
|
||||
},
|
||||
);
|
44
src/shared/guards/building.guard.ts
Normal file
44
src/shared/guards/building.guard.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { BuildingService } from 'src/objects/building/building.service';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class BuildingGuard implements CanActivate {
|
||||
constructor(private readonly buildingService: BuildingService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const http = context.switchToHttp();
|
||||
const request = http.getRequest() as Request;
|
||||
const response = http.getResponse();
|
||||
|
||||
const user = response.locals.user as User;
|
||||
if (!user) return false;
|
||||
|
||||
if (
|
||||
request.params.buildingId == null &&
|
||||
request.body?.buildingId == null &&
|
||||
request.query?.buildingId == null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const buildingId = parseInt(
|
||||
request.params.buildingId ||
|
||||
request.body?.buildingId ||
|
||||
request.query?.buildingId,
|
||||
10,
|
||||
);
|
||||
|
||||
if (!buildingId || isNaN(buildingId)) return false;
|
||||
|
||||
const buildingAccess = await this.buildingService.getBuildingByIdAndUserSub(
|
||||
buildingId,
|
||||
user.sub,
|
||||
);
|
||||
if (!buildingAccess) return false;
|
||||
response.locals.building = buildingAccess;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
52
src/shared/guards/room.guard.ts
Normal file
52
src/shared/guards/room.guard.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { BuildingService } from 'src/objects/building/building.service';
|
||||
import { Room } from 'src/objects/building/entities/room.entity';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class RoomGuard implements CanActivate {
|
||||
constructor(private readonly buildingService: BuildingService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const http = context.switchToHttp();
|
||||
const request = http.getRequest() as Request;
|
||||
const response = http.getResponse();
|
||||
|
||||
const user = response.locals.user as User;
|
||||
if (!user) return false;
|
||||
|
||||
if (
|
||||
request.params.roomId == null &&
|
||||
request.body?.roomId == null &&
|
||||
request.query?.roomId == null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const roomId = parseInt(
|
||||
request.params.roomId || request.body?.roomId || request.query?.roomId,
|
||||
10,
|
||||
);
|
||||
|
||||
if (!roomId || isNaN(roomId)) return false;
|
||||
|
||||
let roomAccess: Room;
|
||||
if (response.locals.building) {
|
||||
roomAccess = await this.buildingService.getRoomByBuilding(
|
||||
roomId,
|
||||
response.locals.building.id,
|
||||
);
|
||||
} else {
|
||||
roomAccess = await this.buildingService.getRoomByIdAndUserSub(
|
||||
roomId,
|
||||
user.sub,
|
||||
);
|
||||
}
|
||||
|
||||
if (!roomAccess) return false;
|
||||
response.locals.room = roomAccess;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
62
src/shared/guards/storage-set.guard.ts
Normal file
62
src/shared/guards/storage-set.guard.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
||||
import { StorageService } from 'src/objects/storage/storage.service';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class StorageSetGuard implements CanActivate {
|
||||
constructor(private readonly storageService: StorageService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const http = context.switchToHttp();
|
||||
const request = http.getRequest() as Request;
|
||||
const response = http.getResponse();
|
||||
|
||||
const user = response.locals.user as User;
|
||||
if (!user) return false;
|
||||
|
||||
if (
|
||||
request.params.storageSetId == null &&
|
||||
request.body?.storageSetId == null &&
|
||||
request.query?.storageSetId == null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const storageSetId = parseInt(
|
||||
request.params.storageSetId ||
|
||||
request.body?.storageSetId ||
|
||||
request.query?.storageSetId,
|
||||
10,
|
||||
);
|
||||
|
||||
if (!storageSetId || isNaN(storageSetId)) return false;
|
||||
|
||||
let storageSetAccess: StorageSet;
|
||||
if (response.locals.room) {
|
||||
storageSetAccess = await this.storageService.getStorageSetByIdAndRoom(
|
||||
storageSetId,
|
||||
response.locals.room.id,
|
||||
['addedBy'],
|
||||
);
|
||||
} else if (response.locals.building) {
|
||||
storageSetAccess = await this.storageService.getStorageSetByIdAndBuilding(
|
||||
storageSetId,
|
||||
response.locals.building.id,
|
||||
['addedBy'],
|
||||
);
|
||||
} else {
|
||||
storageSetAccess = await this.storageService.getStorageSetByIdAndSub(
|
||||
storageSetId,
|
||||
user.sub,
|
||||
['addedBy'],
|
||||
);
|
||||
}
|
||||
|
||||
if (!storageSetAccess) return false;
|
||||
response.locals.storageSet = storageSetAccess;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
62
src/shared/guards/storage.guard.ts
Normal file
62
src/shared/guards/storage.guard.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||
import { StorageService } from 'src/objects/storage/storage.service';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class StorageGuard implements CanActivate {
|
||||
constructor(private readonly storageService: StorageService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const http = context.switchToHttp();
|
||||
const request = http.getRequest() as Request;
|
||||
const response = http.getResponse();
|
||||
|
||||
const user = response.locals.user as User;
|
||||
if (!user) return false;
|
||||
|
||||
if (
|
||||
request.params.storageId == null &&
|
||||
request.body?.storageId == null &&
|
||||
request.query?.storageId == null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const storageId = parseInt(
|
||||
request.params.storageId ||
|
||||
request.body?.storageId ||
|
||||
request.query?.storageId,
|
||||
10,
|
||||
);
|
||||
|
||||
if (!storageId || isNaN(storageId)) return false;
|
||||
|
||||
let storageAccess: Storage;
|
||||
if (response.locals.room) {
|
||||
storageAccess = await this.storageService.getStorageByIdAndRoom(
|
||||
storageId,
|
||||
response.locals.room.id,
|
||||
['addedBy'],
|
||||
);
|
||||
} else if (response.locals.building) {
|
||||
storageAccess = await this.storageService.getStorageByIdAndBuilding(
|
||||
storageId,
|
||||
response.locals.building.id,
|
||||
['addedBy'],
|
||||
);
|
||||
} else {
|
||||
storageAccess = await this.storageService.getStorageByIdAndSub(
|
||||
storageId,
|
||||
user.sub,
|
||||
['addedBy'],
|
||||
);
|
||||
}
|
||||
|
||||
if (!storageAccess) return false;
|
||||
response.locals.storage = storageAccess;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user