Compare commits
10 Commits
1818fcfe88
...
ceef9f94b6
Author | SHA1 | Date | |
---|---|---|---|
ceef9f94b6 | |||
65118e9465 | |||
66ca040807 | |||
07d65484c3 | |||
e9277ba763 | |||
7e8d6e73eb | |||
ef5349063d | |||
46b090c366 | |||
534560bae5 | |||
aecc22a733 |
@ -96,7 +96,7 @@ export class InsertAdminState implements CanActivate {
|
||||
@Inject(DbService) private readonly databaseService: DbService,
|
||||
) {}
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request : Request = context.switchToHttp().getRequest();
|
||||
const request: Request = context.switchToHttp().getRequest();
|
||||
|
||||
const authHeader = request.headers.authorization;
|
||||
if (!authHeader) {
|
||||
@ -120,7 +120,7 @@ export class InsertAdminState implements CanActivate {
|
||||
return true;
|
||||
}
|
||||
|
||||
request.headers.is_admin = true
|
||||
request.headers.is_admin = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -83,9 +83,9 @@ export const FilesTable = pgTable("files", {
|
||||
.notNull()
|
||||
.references(() => FilesTypesTable.id),
|
||||
|
||||
isRestricted: p.boolean("is_restricted").default(false).notNull(),
|
||||
isRestricted: p.boolean("is_restricted").notNull(),
|
||||
|
||||
isDocumentation: p.boolean("is_documentation").default(false).notNull(),
|
||||
isDocumentation: p.boolean("is_documentation").notNull(),
|
||||
|
||||
uploadedAt: p
|
||||
.timestamp("uploaded_at", {
|
||||
@ -151,6 +151,19 @@ export const MachinesTable = pgTable("machines", {
|
||||
//supported files format
|
||||
});
|
||||
|
||||
//TODO Many to Many table betwen File en Machine
|
||||
export const FilesForMachinesTable = pgTable("files_for_machines", {
|
||||
fileId: p
|
||||
.uuid("file_id")
|
||||
.notNull()
|
||||
.references(() => FilesTable.uuid),
|
||||
|
||||
machineId: p
|
||||
.uuid("machine_id")
|
||||
.notNull()
|
||||
.references(() => MachinesTable.id),
|
||||
});
|
||||
|
||||
export const FilesTypeForMachine = pgTable("f_type_for_machines", {
|
||||
machineId: p
|
||||
.uuid("machine_id")
|
||||
|
@ -1,85 +1,89 @@
|
||||
import { IncomingMessage } from "node:http";
|
||||
import {
|
||||
Controller,
|
||||
DefaultValuePipe,
|
||||
Get,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
Res,
|
||||
Request,
|
||||
Response,
|
||||
StreamableFile, HttpStatus, HttpCode, BadRequestException, UseGuards
|
||||
} from '@nestjs/common';
|
||||
BadRequestException,
|
||||
Controller,
|
||||
DefaultValuePipe,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
Request,
|
||||
Res,
|
||||
Response,
|
||||
StreamableFile,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { InsertAdminState } from "../auth/auth.guard";
|
||||
import { FilesService } from "./files.service";
|
||||
import { IncomingMessage } from 'node:http';
|
||||
import { InsertAdminState } from '../auth/auth.guard';
|
||||
|
||||
@Controller("files")
|
||||
export class FilesController {
|
||||
constructor(private readonly filesService: FilesService) {}
|
||||
|
||||
@UseGuards(InsertAdminState)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('new')
|
||||
async saveFile(@Req() req: IncomingMessage, @Res() res: Response) {
|
||||
let fileBuffer: Buffer = Buffer.from([]);
|
||||
req.on('data', (chunk: Buffer) => {
|
||||
fileBuffer = Buffer.concat([fileBuffer, chunk]);
|
||||
});
|
||||
@UseGuards(InsertAdminState)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("new")
|
||||
async saveFile(@Req() req: IncomingMessage, @Res() res: Response) {
|
||||
let fileBuffer: Buffer = Buffer.from([]);
|
||||
req.on("data", (chunk: Buffer) => {
|
||||
fileBuffer = Buffer.concat([fileBuffer, chunk]);
|
||||
});
|
||||
|
||||
req.on('end', async () => {
|
||||
const _fileName = req.headers['file_name'] as string;
|
||||
const _groupId = req.headers['group_id'] as string;
|
||||
const _machineId = req.headers['machine_id'];
|
||||
const _isDocumentation = req.headers['is_documentation'] as string;
|
||||
const _isRestricted = req.headers['is_restricted'] as string;
|
||||
const _isAdmin = Boolean(req.headers['is_admin'] as string | boolean);
|
||||
req.on("end", async () => {
|
||||
const _fileName = req.headers["file_name"] as string;
|
||||
const _groupId = req.headers["group_id"] as string;
|
||||
const _uploadedBy = req.headers["uploaded_by"] as string;
|
||||
const _machineId = req.headers["machine_id"];
|
||||
const _isDocumentation = req.headers["is_documentation"] as string;
|
||||
const _isRestricted = req.headers["is_restricted"] as string;
|
||||
const _isAdmin = Boolean(req.headers["is_admin"] as string | boolean);
|
||||
|
||||
// Vérifier que les en-têtes nécessaires sont présents
|
||||
if (!_fileName || !_groupId || !_machineId) {
|
||||
throw new BadRequestException("Header(s) manquant(s)");
|
||||
}
|
||||
const machineId = Array(..._machineId);
|
||||
// Vérifier que les en-têtes nécessaires sont présents
|
||||
if (!_fileName || !_groupId || !_machineId) {
|
||||
throw new BadRequestException("Header(s) manquant(s)");
|
||||
}
|
||||
const machineId = Array(..._machineId);
|
||||
|
||||
const Params = new Map()
|
||||
.set("fileName", _fileName.toString())
|
||||
.set("groupId", _groupId.toString())
|
||||
.set("machinesId", Array(..._machineId))
|
||||
const Params = new Map()
|
||||
.set("fileName", _fileName.toString())
|
||||
.set("groupId", _groupId.toString())
|
||||
.set("uploadedBy", _uploadedBy.toString())
|
||||
.set("machineId", Array(..._machineId))
|
||||
.set("isDocumentation", false)
|
||||
.set("isRestricted", false);
|
||||
|
||||
//TODO Integrate a verification if the source is an admin, if that the case then it can define isDocumentation and isRestricted else throw in case of presence of those parameters.
|
||||
if (_isAdmin) {
|
||||
Params.set("isDocumentation", Boolean(_isDocumentation))
|
||||
Params.set("isRestricted", Boolean(_isRestricted))
|
||||
}
|
||||
//TODO Integrate a verification if the source is an admin, if that the case then it can define isDocumentation and isRestricted else throw in case of presence of those parameters.
|
||||
if (_isAdmin) {
|
||||
Params.set("isDocumentation", Boolean(_isDocumentation));
|
||||
Params.set("isRestricted", Boolean(_isRestricted));
|
||||
}
|
||||
|
||||
//TODO Implement the service
|
||||
//await this.filesService.save(fileBuffer, Params);
|
||||
//TODO Implement the service
|
||||
//await this.filesService.save(fileBuffer, Params);
|
||||
|
||||
// TODO logique de sauvegarde du fichier et des données
|
||||
|
||||
// TODO logique de sauvegarde du fichier et des données
|
||||
return { message: "Fichier sauvegardé avec succès" };
|
||||
});
|
||||
|
||||
return { message: 'Fichier sauvegardé avec succès' }
|
||||
});
|
||||
req.on("error", (err) => {
|
||||
throw new BadRequestException(err.message);
|
||||
});
|
||||
}
|
||||
|
||||
req.on('error', (err) => {
|
||||
throw new BadRequestException(err.message)
|
||||
});
|
||||
}
|
||||
|
||||
@Get('find')
|
||||
async findMany(
|
||||
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||
@Query("search", new DefaultValuePipe("")) search: string,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
@Get(':fileId')
|
||||
async getFile(@Param('fileId') fileId: string) {
|
||||
return this.filesService.get(fileId);
|
||||
}
|
||||
@Get("find")
|
||||
async findMany(
|
||||
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||
@Query("search", new DefaultValuePipe("")) search: string,
|
||||
) {}
|
||||
|
||||
@Get(":fileId")
|
||||
async getFile(@Param("fileId") fileId: string) {
|
||||
return this.filesService.get(fileId);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { IsUUID, MaxLength, MinLength } from 'class-validator';
|
||||
import { DefaultValuePipe } from '@nestjs/common';
|
||||
|
||||
import { DefaultValuePipe } from "@nestjs/common";
|
||||
import { IsUUID, MaxLength, MinLength } from "class-validator";
|
||||
|
||||
export class CreateFilesDto {
|
||||
@MaxLength(128)
|
||||
@MinLength(4)
|
||||
fileName: string;
|
||||
@MaxLength(128)
|
||||
@MinLength(4)
|
||||
fileName: string;
|
||||
|
||||
@MaxLength(64)
|
||||
@MinLength(2)
|
||||
uploadedBy: string;
|
||||
@MaxLength(64)
|
||||
@MinLength(2)
|
||||
uploadedBy: string;
|
||||
|
||||
isDocumentation?: boolean;
|
||||
isRestricted?: boolean;
|
||||
isDocumentation?: boolean;
|
||||
isRestricted?: boolean;
|
||||
|
||||
@IsUUID()
|
||||
groupId: string;
|
||||
@IsUUID()
|
||||
groupId: string;
|
||||
}
|
@ -1,9 +1,20 @@
|
||||
import { Injectable, NotFoundException, StreamableFile } from '@nestjs/common';
|
||||
import { DbService } from 'apps/backend/src/app/db/db.service';
|
||||
import { FilesTable } from 'apps/backend/src/app/db/schema';
|
||||
import { StorageService } from 'apps/backend/src/app/storage/storage.service';
|
||||
import { eq, ilike } from 'drizzle-orm';
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
StreamableFile,
|
||||
} from "@nestjs/common";
|
||||
import { DbService } from "apps/backend/src/app/db/db.service";
|
||||
import {
|
||||
FilesForMachinesTable,
|
||||
FilesGroupTable,
|
||||
FilesTable,
|
||||
FilesTypeForMachine,
|
||||
MachinesTable,
|
||||
} from "apps/backend/src/app/db/schema";
|
||||
import { StorageService } from "apps/backend/src/app/storage/storage.service";
|
||||
import { data } from "autoprefixer";
|
||||
import { eq, ilike } from "drizzle-orm";
|
||||
|
||||
@Injectable()
|
||||
export class FilesService {
|
||||
@ -12,7 +23,7 @@ export class FilesService {
|
||||
private readonly database: DbService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Retrieves a file and its related information from the database and associated storage.
|
||||
*
|
||||
* @param fileId The unique identifier for the file to be retrieved.
|
||||
@ -30,8 +41,8 @@ export class FilesService {
|
||||
|
||||
if (foundFiles.length === 0)
|
||||
throw new NotFoundException("File not found", {
|
||||
description: `Identifier : ${fileId}`
|
||||
});
|
||||
description: `Identifier : ${fileId}`,
|
||||
});
|
||||
|
||||
if (foundFiles.length > 1)
|
||||
console.log(
|
||||
@ -51,7 +62,7 @@ export class FilesService {
|
||||
);
|
||||
const fileNameWithoutSpaces = file.fileName.replace(/\s/g, "_");
|
||||
return new StreamableFile(fileBuffer, {
|
||||
disposition: `attachment; filename="${fileNameWithoutSpaces}.${fileInformation.fileType.ext.toLowerCase()}"`
|
||||
disposition: `attachment; filename="${fileNameWithoutSpaces}.${fileInformation.fileType.ext.toLowerCase()}"`,
|
||||
});
|
||||
}
|
||||
|
||||
@ -68,7 +79,90 @@ export class FilesService {
|
||||
}
|
||||
|
||||
//TODO save a file
|
||||
public async save() {
|
||||
public async save(file: Buffer, data: Map<string, unknown>) {
|
||||
const _machineIds = Array.from(data.get("machineId") as string[]);
|
||||
const machinesIds = new Set<string>();
|
||||
console.log(
|
||||
`Checking if machine with ID ${_machineIds} exist in the database...`,
|
||||
);
|
||||
for (const machineId of _machineIds) {
|
||||
const machineExists = await this.database
|
||||
.use()
|
||||
.select({
|
||||
uuid: MachinesTable.id,
|
||||
})
|
||||
.from(MachinesTable)
|
||||
.where(eq(MachinesTable.id, machineId))
|
||||
.prepare("checkMachineExists")
|
||||
.execute();
|
||||
|
||||
if (machineExists.length === 0) {
|
||||
throw new NotFoundException(`Machine with ID "${machineId}" not found`);
|
||||
}
|
||||
|
||||
machinesIds.add(machineExists[0].uuid);
|
||||
}
|
||||
|
||||
const _group = data.get("groupId") as string;
|
||||
// verify that the group exist in the database
|
||||
const groupExists = await this.database
|
||||
.use()
|
||||
.select()
|
||||
.from(FilesGroupTable)
|
||||
.where(eq(FilesGroupTable.uuid, _group))
|
||||
.prepare("checkGroupExists")
|
||||
.execute();
|
||||
|
||||
if (groupExists.length === 0) {
|
||||
throw new NotFoundException(`Group with ID "${_group}" not found`);
|
||||
}
|
||||
|
||||
try {
|
||||
const saveResult = await this.storage.new(
|
||||
data.get("fileName") as string,
|
||||
file,
|
||||
_machineIds,
|
||||
Boolean(data.get("isDocumentation")),
|
||||
);
|
||||
const inserted = await this.database
|
||||
.use()
|
||||
.insert(FilesTable)
|
||||
.values({
|
||||
fileName: data.get("fileName") as string,
|
||||
checksum: saveResult.fileChecksum,
|
||||
extension: saveResult.fileType.ext,
|
||||
fileSize: saveResult.fileSize,
|
||||
fileType: saveResult.fileType.mime,
|
||||
isRestricted: Boolean(data.get("isRestricted")),
|
||||
isDocumentation: Boolean(data.get("isDocumentation")),
|
||||
uploadedBy: data.get("uploadedBy") as string,
|
||||
})
|
||||
.returning();
|
||||
|
||||
for (const machineId of machinesIds) {
|
||||
//TODO insert a link betwen fileId and MachineIds[]
|
||||
const linkRow = await this.database
|
||||
.use()
|
||||
.insert(FilesForMachinesTable)
|
||||
.values({
|
||||
fileId: inserted[0].uuid,
|
||||
machineId: machineId,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
throw new InternalServerErrorException(
|
||||
"It seems that the insertion in the database failed.",
|
||||
{
|
||||
cause:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "Internal server error"
|
||||
: {
|
||||
message: e.message,
|
||||
stack: e.stack,
|
||||
},
|
||||
description: `Nom de fichier : "${data.get("fileName")}" `,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ import {
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Post,
|
||||
Query
|
||||
} from '@nestjs/common';
|
||||
Query,
|
||||
} from "@nestjs/common";
|
||||
import { CreateGroupDto } from "apps/backend/src/app/groups/groups.dto";
|
||||
import { ISearchQuery } from "apps/backend/src/app/groups/groups.types";
|
||||
import { GroupsService } from "./groups.service";
|
||||
import { CreateGroupDto } from 'apps/backend/src/app/groups/groups.dto';
|
||||
|
||||
@Controller("groups")
|
||||
export class GroupsController {
|
||||
@ -29,9 +29,7 @@ export class GroupsController {
|
||||
|
||||
//POST a new group
|
||||
@Post("new")
|
||||
async newGroup(@Body() dto : CreateGroupDto) {
|
||||
|
||||
}
|
||||
async newGroup(@Body() dto: CreateGroupDto) {}
|
||||
|
||||
//DELETE a group
|
||||
@Delete(":groupId")
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { IsString, MinLength, MaxLength } from 'class-validator';
|
||||
import { IsString, MaxLength, MinLength } from "class-validator";
|
||||
|
||||
export class CreateGroupDto {
|
||||
@IsString()
|
||||
@MinLength(4)
|
||||
@MaxLength(64)
|
||||
groupName: string;
|
||||
@IsString()
|
||||
@MinLength(4)
|
||||
@MaxLength(64)
|
||||
groupName: string;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { DbModule } from "../db/db.module";
|
||||
import { GroupsController } from "./groups.controller";
|
||||
import { GroupsService } from "./groups.service";
|
||||
import { DbModule } from '../db/db.module';
|
||||
|
||||
@Module({
|
||||
imports: [DbModule],
|
||||
|
@ -1,29 +1,28 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DbService } from 'apps/backend/src/app/db/db.service';
|
||||
import { FilesGroupTable } from 'apps/backend/src/app/db/schema';
|
||||
import { ilike } from 'drizzle-orm';
|
||||
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { DbService } from "apps/backend/src/app/db/db.service";
|
||||
import { FilesGroupTable } from "apps/backend/src/app/db/schema";
|
||||
import { ilike } from "drizzle-orm";
|
||||
|
||||
@Injectable()
|
||||
export class GroupsService {
|
||||
constructor(private readonly database: DbService) {}
|
||||
constructor(private readonly database: DbService) {}
|
||||
|
||||
//TODO a method to fetch groups in the database by a specific search with limit, offset and a search field (can be blank)
|
||||
async getGroupsByName(limit: number, offset: number, search: string) {
|
||||
return await this.database.use()
|
||||
.select()
|
||||
.from(FilesGroupTable)
|
||||
.where(ilike(FilesGroupTable.groupName, search))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.prepare("getGroupsByName")
|
||||
.execute();
|
||||
}
|
||||
//TODO a method to fetch groups in the database by a specific search with limit, offset and a search field (can be blank)
|
||||
async getGroupsByName(limit: number, offset: number, search: string) {
|
||||
return await this.database
|
||||
.use()
|
||||
.select()
|
||||
.from(FilesGroupTable)
|
||||
.where(ilike(FilesGroupTable.groupName, search))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.prepare("getGroupsByName")
|
||||
.execute();
|
||||
}
|
||||
|
||||
//TODO The method to create a group
|
||||
//TODO The method to create a group
|
||||
|
||||
//TODO a method to delete a group and place the associated file at a null group reference
|
||||
|
||||
//TODO a method to delete a group and place the associated file at a null group reference
|
||||
|
||||
//TODO a method to get the files of a group in the database by a specific search with limit, offset and a search field (can be blank)
|
||||
//TODO a method to get the files of a group in the database by a specific search with limit, offset and a search field (can be blank)
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { DbService } from 'apps/backend/src/app/db/db.service';
|
||||
import { DbService } from "apps/backend/src/app/db/db.service";
|
||||
|
||||
@Injectable()
|
||||
export class MachinesService {
|
||||
constructor(private readonly database: DbService) {}
|
||||
constructor(private readonly database: DbService) {}
|
||||
|
||||
//TODO a method to fetch machines in the database by a specific search with limit, offset and a search field (can be blank)
|
||||
//TODO a method to fetch machines in the database by a specific search with limit, offset and a search field (can be blank)
|
||||
|
||||
//TODO The method to create a machine
|
||||
//TODO The method to create a machine
|
||||
|
||||
//TODO a method to delete a machine and delete the associated FilesTypeForMachine row
|
||||
//TODO a method to delete a machine and delete the associated FilesTypeForMachine row
|
||||
|
||||
//TODO a method to get the files of a group in the database by a specific search with limit, offset and a search field (can be blank)
|
||||
//TODO a method to get the files of a group in the database by a specific search with limit, offset and a search field (can be blank)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user