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

@ -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,8 +120,8 @@ export class InsertAdminState implements CanActivate {
return true;
}
request.headers.is_admin = true
request.headers.is_admin = true;
return true;
}
}
}

View File

@ -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")

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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")}" `,
},
);
}
}
}

View File

@ -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")

View File

@ -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;
}

View File

@ -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],

View File

@ -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)
}

View File

@ -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)
}