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, @Inject(DbService) private readonly databaseService: DbService,
) {} ) {}
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
const request: Request = context.switchToHttp().getRequest(); const request : Request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization; const authHeader = request.headers.authorization;
if (!authHeader) { if (!authHeader) {
@ -120,8 +120,8 @@ 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").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 uploadedAt: p
.timestamp("uploaded_at", { .timestamp("uploaded_at", {
@ -151,19 +151,6 @@ 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,89 +1,85 @@
import { IncomingMessage } from "node:http";
import { import {
BadRequestException, Controller,
Controller, DefaultValuePipe,
DefaultValuePipe, Get,
Get, Param,
HttpCode, ParseIntPipe,
HttpStatus, Post,
Param, Query,
ParseIntPipe, Req,
Post, Res,
Query, Request,
Req, Response,
Request, StreamableFile, HttpStatus, HttpCode, BadRequestException, UseGuards
Res, } from '@nestjs/common';
Response,
StreamableFile,
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 {
constructor(private readonly filesService: FilesService) {} constructor(private readonly filesService: FilesService) {}
@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 _uploadedBy = req.headers["uploaded_by"] as string; const _machineId = req.headers['machine_id'];
const _machineId = req.headers["machine_id"]; const _isDocumentation = req.headers['is_documentation'] as string;
const _isDocumentation = req.headers["is_documentation"] as string; const _isRestricted = req.headers['is_restricted'] as string;
const _isRestricted = req.headers["is_restricted"] as string; const _isAdmin = Boolean(req.headers['is_admin'] as string | boolean);
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) {
throw new BadRequestException("Header(s) manquant(s)"); throw new BadRequestException("Header(s) manquant(s)");
} }
const machineId = Array(..._machineId); const machineId = Array(..._machineId);
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("uploadedBy", _uploadedBy.toString()) .set("machinesId", Array(..._machineId))
.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
return { message: "Fichier sauvegardé avec succès" }; // TODO logique de sauvegarde du fichier et des données
});
req.on("error", (err) => { return { message: 'Fichier sauvegardé avec succès' }
throw new BadRequestException(err.message); });
});
}
@Get("find") req.on('error', (err) => {
async findMany( throw new BadRequestException(err.message)
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number, });
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number, }
@Query("search", new DefaultValuePipe("")) search: string,
) {} @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 { export class CreateFilesDto {
@MaxLength(128) @MaxLength(128)
@MinLength(4) @MinLength(4)
fileName: string; fileName: string;
@MaxLength(64) @MaxLength(64)
@MinLength(2) @MinLength(2)
uploadedBy: string; uploadedBy: string;
isDocumentation?: boolean; isDocumentation?: boolean;
isRestricted?: boolean; isRestricted?: boolean;
@IsUUID() @IsUUID()
groupId: string; groupId: string;
} }

View File

@ -1,20 +1,9 @@
import { import { Injectable, NotFoundException, StreamableFile } from '@nestjs/common';
Injectable, import { DbService } from 'apps/backend/src/app/db/db.service';
InternalServerErrorException, import { FilesTable } from 'apps/backend/src/app/db/schema';
NotFoundException, import { StorageService } from 'apps/backend/src/app/storage/storage.service';
StreamableFile, import { eq, ilike } from 'drizzle-orm';
} 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 {
@ -23,7 +12,7 @@ export class FilesService {
private readonly database: DbService, private readonly database: DbService,
) {} ) {}
/** /**
* Retrieves a file and its related information from the database and associated storage. * Retrieves a file and its related information from the database and associated storage.
* *
* @param fileId The unique identifier for the file to be retrieved. * @param fileId The unique identifier for the file to be retrieved.
@ -41,8 +30,8 @@ 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)
console.log( console.log(
@ -62,7 +51,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()}"`
}); });
} }
@ -79,90 +68,7 @@ export class FilesService {
} }
//TODO save a file //TODO save a file
public async save(file: Buffer, data: Map<string, unknown>) { public async save() {
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,7 +29,9 @@ 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,8 +1,8 @@
import { IsString, MaxLength, MinLength } from "class-validator"; import { IsString, MinLength, MaxLength } from 'class-validator';
export class CreateGroupDto { export class CreateGroupDto {
@IsString() @IsString()
@MinLength(4) @MinLength(4)
@MaxLength(64) @MaxLength(64)
groupName: string; groupName: string;
} }

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,28 +1,29 @@
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 {
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) //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 return await this.database.use()
.use() .select()
.select() .from(FilesGroupTable)
.from(FilesGroupTable) .where(ilike(FilesGroupTable.groupName, search))
.where(ilike(FilesGroupTable.groupName, search)) .limit(limit)
.limit(limit) .offset(offset)
.offset(offset) .prepare("getGroupsByName")
.prepare("getGroupsByName") .execute();
.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 { 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 {
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)
} }