Compare commits

...

10 Commits

Author SHA1 Message Date
ceef9f94b6
Update schema to remove default values and add new table
Removed default values for 'isRestricted' and 'isDocumentation' columns in the FilesTable. Added a new intermediary table 'FilesForMachinesTable' to establish a many-to-many relationship between 'File' and 'Machine'.
2024-10-07 12:01:29 +02:00
65118e9465
Fix indentation and quote style in MachinesService
Corrected inconsistent use of single and double quotes and adjusted indentation for enhanced readability and code style consistency. No functional changes were made in this commit.
2024-10-07 12:01:20 +02:00
66ca040807
Refactor indentation and import statements.
Aligned the indentation to use tabs consistently and updated import statements to use double quotes for consistency throughout the file. These changes improve code readability and maintain uniform style.
2024-10-07 12:01:13 +02:00
07d65484c3
Add DbModule import to GroupsModule
The DbModule import was moved to ensure proper load order and maintain consistency within the module imports. This change will help in avoiding potential dependency injection issues.
2024-10-07 12:01:07 +02:00
e9277ba763
Reorder imports in groups.dto.ts
Rearranged the imports in `groups.dto.ts` to maintain alphabetical order and used double quotes for consistency with the rest of the project. This change does not alter functionality but improves code readability and maintainability.
2024-10-07 12:01:01 +02:00
7e8d6e73eb
Refactor import statements and clean up code
Standardized import quotes in groups controller and reorganized import statements for better readability. Removed unnecessary line breaks in newGroup method.
2024-10-07 12:00:54 +02:00
ef5349063d
Enhance files service to save file data
Implemented detailed logic to check machine and group existence before saving files. Added comprehensive error handling to provide clear feedback when database operations fail. Improved file saving functionality with associations between files and machines.
2024-10-07 12:00:46 +02:00
46b090c366
Align import and property formatting in files.dto.ts
Change import statements to use double quotes for consistency and adjust property indents to match standard. These modifications improve code readability and maintain uniform coding style throughout the file.
2024-10-07 12:00:37 +02:00
534560bae5
Refactor files.controller.ts and add uploaded_by header
Reformat imports and modify `saveFile` method to include the `uploaded_by` header. Ensure that `_uploadedBy` is checked and added to the parameters alongside other headers.
2024-10-07 12:00:30 +02:00
aecc22a733
Fix syntax issues in auth.guard.ts
Corrected spacing around type annotation and added missing semicolon in the `canActivate` method. These changes ensure better code consistency and adhere to TypeScript style guidelines.
2024-10-07 12:00:23 +02:00
10 changed files with 243 additions and 136 deletions

View File

@ -120,7 +120,7 @@ export class InsertAdminState implements CanActivate {
return true; return true;
} }
request.headers.is_admin = true request.headers.is_admin = true;
return true; return true;
} }

View File

@ -83,9 +83,9 @@ export const FilesTable = pgTable("files", {
.notNull() .notNull()
.references(() => FilesTypesTable.id), .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 uploadedAt: p
.timestamp("uploaded_at", { .timestamp("uploaded_at", {
@ -151,6 +151,19 @@ export const MachinesTable = pgTable("machines", {
//supported files format //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", { export const FilesTypeForMachine = pgTable("f_type_for_machines", {
machineId: p machineId: p
.uuid("machine_id") .uuid("machine_id")

View File

@ -1,20 +1,24 @@
import { IncomingMessage } from "node:http";
import { import {
BadRequestException,
Controller, Controller,
DefaultValuePipe, DefaultValuePipe,
Get, Get,
HttpCode,
HttpStatus,
Param, Param,
ParseIntPipe, ParseIntPipe,
Post, Post,
Query, Query,
Req, Req,
Res,
Request, Request,
Res,
Response, Response,
StreamableFile, HttpStatus, HttpCode, BadRequestException, UseGuards StreamableFile,
} from '@nestjs/common'; UseGuards,
} from "@nestjs/common";
import { InsertAdminState } from "../auth/auth.guard";
import { FilesService } from "./files.service"; import { FilesService } from "./files.service";
import { IncomingMessage } from 'node:http';
import { InsertAdminState } from '../auth/auth.guard';
@Controller("files") @Controller("files")
export class FilesController { export class FilesController {
@ -22,20 +26,21 @@ export class FilesController {
@UseGuards(InsertAdminState) @UseGuards(InsertAdminState)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post('new') @Post("new")
async saveFile(@Req() req: IncomingMessage, @Res() res: Response) { async saveFile(@Req() req: IncomingMessage, @Res() res: Response) {
let fileBuffer: Buffer = Buffer.from([]); let fileBuffer: Buffer = Buffer.from([]);
req.on('data', (chunk: Buffer) => { req.on("data", (chunk: Buffer) => {
fileBuffer = Buffer.concat([fileBuffer, chunk]); fileBuffer = Buffer.concat([fileBuffer, chunk]);
}); });
req.on('end', async () => { req.on("end", async () => {
const _fileName = req.headers['file_name'] as string; const _fileName = req.headers["file_name"] as string;
const _groupId = req.headers['group_id'] as string; const _groupId = req.headers["group_id"] as string;
const _machineId = req.headers['machine_id']; const _uploadedBy = req.headers["uploaded_by"] as string;
const _isDocumentation = req.headers['is_documentation'] as string; const _machineId = req.headers["machine_id"];
const _isRestricted = req.headers['is_restricted'] as string; const _isDocumentation = req.headers["is_documentation"] as string;
const _isAdmin = Boolean(req.headers['is_admin'] as string | boolean); 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 // Vérifier que les en-têtes nécessaires sont présents
if (!_fileName || !_groupId || !_machineId) { if (!_fileName || !_groupId || !_machineId) {
@ -46,40 +51,39 @@ export class FilesController {
const Params = new Map() const Params = new Map()
.set("fileName", _fileName.toString()) .set("fileName", _fileName.toString())
.set("groupId", _groupId.toString()) .set("groupId", _groupId.toString())
.set("machinesId", Array(..._machineId)) .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. //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) { if (_isAdmin) {
Params.set("isDocumentation", Boolean(_isDocumentation)) Params.set("isDocumentation", Boolean(_isDocumentation));
Params.set("isRestricted", Boolean(_isRestricted)) Params.set("isRestricted", Boolean(_isRestricted));
} }
//TODO Implement the service //TODO Implement the service
//await this.filesService.save(fileBuffer, Params); //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) => { req.on("error", (err) => {
throw new BadRequestException(err.message) throw new BadRequestException(err.message);
}); });
} }
@Get('find') @Get("find")
async findMany( async findMany(
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number, @Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number, @Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
@Query("search", new DefaultValuePipe("")) search: string, @Query("search", new DefaultValuePipe("")) search: string,
) { ) {}
} @Get(":fileId")
async getFile(@Param("fileId") fileId: string) {
@Get(':fileId')
async getFile(@Param('fileId') fileId: string) {
return this.filesService.get(fileId); return this.filesService.get(fileId);
} }
} }

View File

@ -1,6 +1,5 @@
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 { export class CreateFilesDto {
@MaxLength(128) @MaxLength(128)

View File

@ -1,9 +1,20 @@
import { Injectable, NotFoundException, StreamableFile } from '@nestjs/common'; import {
import { DbService } from 'apps/backend/src/app/db/db.service'; Injectable,
import { FilesTable } from 'apps/backend/src/app/db/schema'; InternalServerErrorException,
import { StorageService } from 'apps/backend/src/app/storage/storage.service'; NotFoundException,
import { eq, ilike } from 'drizzle-orm'; 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() @Injectable()
export class FilesService { export class FilesService {
@ -30,7 +41,7 @@ export class FilesService {
if (foundFiles.length === 0) if (foundFiles.length === 0)
throw new NotFoundException("File not found", { throw new NotFoundException("File not found", {
description: `Identifier : ${fileId}` description: `Identifier : ${fileId}`,
}); });
if (foundFiles.length > 1) if (foundFiles.length > 1)
@ -51,7 +62,7 @@ export class FilesService {
); );
const fileNameWithoutSpaces = file.fileName.replace(/\s/g, "_"); const fileNameWithoutSpaces = file.fileName.replace(/\s/g, "_");
return new StreamableFile(fileBuffer, { 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 //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")}" `,
},
);
}
} }
} }

View File

@ -7,11 +7,11 @@ import {
Param, Param,
ParseIntPipe, ParseIntPipe,
Post, Post,
Query Query,
} from '@nestjs/common'; } from "@nestjs/common";
import { CreateGroupDto } from "apps/backend/src/app/groups/groups.dto";
import { ISearchQuery } from "apps/backend/src/app/groups/groups.types"; import { ISearchQuery } from "apps/backend/src/app/groups/groups.types";
import { GroupsService } from "./groups.service"; import { GroupsService } from "./groups.service";
import { CreateGroupDto } from 'apps/backend/src/app/groups/groups.dto';
@Controller("groups") @Controller("groups")
export class GroupsController { export class GroupsController {
@ -29,9 +29,7 @@ export class GroupsController {
//POST a new group //POST a new group
@Post("new") @Post("new")
async newGroup(@Body() dto : CreateGroupDto) { async newGroup(@Body() dto: CreateGroupDto) {}
}
//DELETE a group //DELETE a group
@Delete(":groupId") @Delete(":groupId")

View File

@ -1,4 +1,4 @@
import { IsString, MinLength, MaxLength } from 'class-validator'; import { IsString, MaxLength, MinLength } from "class-validator";
export class CreateGroupDto { export class CreateGroupDto {
@IsString() @IsString()

View File

@ -1,7 +1,7 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { DbModule } from "../db/db.module";
import { GroupsController } from "./groups.controller"; import { GroupsController } from "./groups.controller";
import { GroupsService } from "./groups.service"; import { GroupsService } from "./groups.service";
import { DbModule } from '../db/db.module';
@Module({ @Module({
imports: [DbModule], imports: [DbModule],

View File

@ -1,8 +1,7 @@
import { Injectable } from '@nestjs/common'; 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";
import { FilesGroupTable } from 'apps/backend/src/app/db/schema'; import { FilesGroupTable } from "apps/backend/src/app/db/schema";
import { ilike } from 'drizzle-orm'; import { ilike } from "drizzle-orm";
@Injectable() @Injectable()
export class GroupsService { export class GroupsService {
@ -10,7 +9,8 @@ export class GroupsService {
//TODO a method to fetch groups in the database by a specific search with limit, offset and a search field (can be blank) //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) { async getGroupsByName(limit: number, offset: number, search: string) {
return await this.database.use() return await this.database
.use()
.select() .select()
.from(FilesGroupTable) .from(FilesGroupTable)
.where(ilike(FilesGroupTable.groupName, search)) .where(ilike(FilesGroupTable.groupName, search))
@ -22,7 +22,6 @@ export class GroupsService {
//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)

View File

@ -1,5 +1,5 @@
import { Injectable } from "@nestjs/common"; 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() @Injectable()
export class MachinesService { export class MachinesService {