Compare commits

..

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

10 changed files with 136 additions and 243 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").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,24 +1,20 @@
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,
Request,
Res, Res,
Request,
Response, Response,
StreamableFile, StreamableFile, HttpStatus, HttpCode, BadRequestException, UseGuards
UseGuards, } from '@nestjs/common';
} 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 {
@ -26,21 +22,20 @@ 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 _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) {
@ -51,39 +46,40 @@ 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("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 // 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,5 +1,6 @@
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)

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 {
@ -41,7 +30,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)
@ -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,4 +1,4 @@
import { IsString, MaxLength, MinLength } from "class-validator"; import { IsString, MinLength, MaxLength } 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,7 +1,8 @@
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 {
@ -9,8 +10,7 @@ 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 return await this.database.use()
.use()
.select() .select()
.from(FilesGroupTable) .from(FilesGroupTable)
.where(ilike(FilesGroupTable.groupName, search)) .where(ilike(FilesGroupTable.groupName, search))
@ -22,6 +22,7 @@ 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 {