Compare commits

..

No commits in common. "ceef9f94b660509e2e6fdbf1fbeccfde7ac7c113" and "1818fcfe888ee810b7184d4edd85ce62159ca589" have entirely different histories.

10 changed files with 136 additions and 243 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").notNull(),
isRestricted: p.boolean("is_restricted").default(false).notNull(),
isDocumentation: p.boolean("is_documentation").notNull(),
isDocumentation: p.boolean("is_documentation").default(false).notNull(),
uploadedAt: p
.timestamp("uploaded_at", {
@ -151,19 +151,6 @@ 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,89 +1,85 @@
import { IncomingMessage } from "node:http";
import {
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";
Controller,
DefaultValuePipe,
Get,
Param,
ParseIntPipe,
Post,
Query,
Req,
Res,
Request,
Response,
StreamableFile, HttpStatus, HttpCode, BadRequestException, UseGuards
} from '@nestjs/common';
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 _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);
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);
// 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("uploadedBy", _uploadedBy.toString())
.set("machineId", Array(..._machineId))
.set("isDocumentation", false)
.set("isRestricted", false);
const Params = new Map()
.set("fileName", _fileName.toString())
.set("groupId", _groupId.toString())
.set("machinesId", Array(..._machineId))
//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
return { message: "Fichier sauvegardé avec succès" };
});
// TODO logique de sauvegarde du fichier et des données
req.on("error", (err) => {
throw new BadRequestException(err.message);
});
}
return { message: 'Fichier sauvegardé avec succès' }
});
@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,
) {}
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(":fileId")
async getFile(@Param("fileId") fileId: string) {
return this.filesService.get(fileId);
}
}

View File

@ -1,18 +1,19 @@
import { DefaultValuePipe } from "@nestjs/common";
import { IsUUID, MaxLength, MinLength } from "class-validator";
import { IsUUID, MaxLength, MinLength } from 'class-validator';
import { DefaultValuePipe } from '@nestjs/common';
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,20 +1,9 @@
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";
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';
@Injectable()
export class FilesService {
@ -23,7 +12,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.
@ -41,8 +30,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(
@ -62,7 +51,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()}"`
});
}
@ -79,90 +68,7 @@ export class FilesService {
}
//TODO save a file
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();
public async save() {
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";
import { CreateGroupDto } from "apps/backend/src/app/groups/groups.dto";
Query
} from '@nestjs/common';
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,7 +29,9 @@ 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, MaxLength, MinLength } from "class-validator";
import { IsString, MinLength, MaxLength } 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,28 +1,29 @@
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 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 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)
}

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