Compare commits

..

3 Commits

Author SHA1 Message Date
711877bf60
Add primary key to FilesForMachinesTable
Introduce a primary key field 'id' with UUID type to the FilesForMachinesTable. This ensures each record is uniquely identifiable and improves database integrity. Additionally, set a default value to a random UUID for new entries.
2024-10-08 11:30:37 +02:00
b182d740bd
Update file handling and error reporting in storage service
Changed the file saving path from "files/" to "assets/" to unify storage structure. Improved error handling and logging in the file operations to aid in debugging. Additionally, streamlined database insertion and validation logic in the files service for better efficiency and readability.
2024-10-08 11:30:26 +02:00
9d28d4f82a
Fix imports and add missing trailing comma
Standardized import quotes across files and added a missing trailing comma in storage.module.ts.
Corrected import order in files.module.ts and machines.module.ts for better readability.
2024-10-07 16:17:03 +02:00
9 changed files with 105 additions and 92 deletions

View File

@ -153,6 +153,8 @@ export const MachinesTable = pgTable("machines", {
//TODO Many to Many table betwen File en Machine
export const FilesForMachinesTable = pgTable("files_for_machines", {
id: p.uuid('id').primaryKey().notNull().defaultRandom(),
fileId: p
.uuid("file_id")
.notNull()

View File

@ -34,43 +34,52 @@ export class FilesController {
});
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);
try {
console.log(fileBuffer);
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);
console.log(_fileName, _groupId, _uploadedBy, _machineId, _isDocumentation, _isRestricted, _isAdmin);
// Vérifier que les en-têtes nécessaires sont présents
if (!_fileName || !_groupId || !_machineId) {
throw new BadRequestException("Header(s) manquant(s)");
// 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", Array(JSON.parse(_groupId.toString())))
.set("uploadedBy", _uploadedBy.toString())
.set("machineId", Array(JSON.parse(machineId.toString())))
.set("isDocumentation", false)
.set("isRestricted", false);
console.log(Params);
//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));
}
return await this.filesService.save(fileBuffer, Params);
} catch (err) {
// @ts-ignore
return res.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR).send(err)
}
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);
//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));
}
await this.filesService.save(fileBuffer, Params);
return { message: "Fichier sauvegardé avec succès" };
});
req.on("error", (err) => {
throw new BadRequestException(err.message);
// @ts-ignore
return res.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR).send(err)
});
return;
}
@Get("find")

View File

@ -1,9 +1,9 @@
import { Module } from "@nestjs/common";
import { CredentialsModule } from "../credentials/credentials.module";
import { DbModule } from "../db/db.module";
import { StorageModule } from "../storage/storage.module";
import { FilesController } from "./files.controller";
import { FilesService } from "./files.service";
import { CredentialsModule } from '../credentials/credentials.module';
@Module({
imports: [StorageModule, DbModule, CredentialsModule],

View File

@ -9,9 +9,9 @@ import {
FilesForMachinesTable,
FilesGroupTable,
FilesTable,
FilesTypeForMachine,
MachinesTable,
} from "apps/backend/src/app/db/schema";
FilesTypeForMachine, FilesTypesTable,
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";
@ -49,6 +49,7 @@ export class FilesService {
"Multiples entries found in the database.\nInformation from the first row will be used.",
);
const file = foundFiles[0];
console.log(file);
const fileBuffer = await this.storage.read(
file.checksum,
@ -62,7 +63,7 @@ export class FilesService {
);
const fileNameWithoutSpaces = file.fileName.replace(/\s/g, "_");
return new StreamableFile(fileBuffer, {
disposition: `attachment; filename="${fileNameWithoutSpaces}.${fileInformation.fileType[0].extension.toLowerCase()}"`,
disposition: `attachment; filename="${fileNameWithoutSpaces}.${fileInformation.fileType.extension.toLowerCase()}"`,
});
}
@ -80,64 +81,73 @@ 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();
try {
const _machineIds = data.get("machineId").toString().split(",");
if (machineExists.length === 0) {
throw new NotFoundException(`Machine with ID "${machineId}" not found`);
const machinesIds = new Set<string>();
for (const machineId of _machineIds) {
console.log(
`Checking if machine with ID ${machineId} exist in the database...`,
);
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);
}
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();
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`);
}
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")),
);
console.log(saveResult);
const mimeId = await this.database.use()
.select()
.from(FilesTypesTable)
.where(eq(FilesTypesTable.mime, saveResult.fileType.mime))
const inserted = await this.database
.use()
.insert(FilesTable)
.values({
fileName: data.get("fileName") as string,
checksum: saveResult.fileChecksum,
extension: saveResult.fileType[0].extension,
extension: saveResult.fileType.extension,
fileSize: saveResult.fileSize,
fileType: saveResult.fileType.mime,
fileType: mimeId[0].id,
isRestricted: Boolean(data.get("isRestricted")),
isDocumentation: Boolean(data.get("isDocumentation")),
uploadedBy: data.get("uploadedBy") as string,
})
.returning();
console.log(inserted);
for (const machineId of machinesIds) {
//TODO insert a link betwen fileId and MachineIds[]
@ -149,20 +159,10 @@ export class FilesService {
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")}" `,
},
);
return inserted[0];
} catch (err) {
throw err;
}
}
}

View File

@ -1,7 +1,7 @@
import { Module } from "@nestjs/common";
import { DbModule } from "apps/backend/src/app/db/db.module";
import { MachinesController } from "apps/backend/src/app/machines/machines.controller";
import { MachinesService } from "apps/backend/src/app/machines/machines.service";
import { DbModule } from 'apps/backend/src/app/db/db.module';
@Module({
imports: [DbModule],

View File

@ -5,6 +5,6 @@ import { StorageService } from "../storage/storage.service";
@Module({
imports: [DbModule],
providers: [StorageService],
exports: [StorageService]
exports: [StorageService],
})
export class StorageModule {}

View File

@ -16,7 +16,7 @@ import {
} from "apps/backend/src/app/db/schema";
import { IFileInformation } from "apps/backend/src/app/storage/storage.types";
import { eq } from "drizzle-orm";
import { filetypeinfo } from 'magic-bytes.js';
import { filetypeinfo } from "magic-bytes.js";
@Injectable()
export class StorageService {
@ -34,7 +34,7 @@ export class StorageService {
*/
private async saveFile(fileName: string, file: Buffer): Promise<void> {
try {
await writeFile(join(process.cwd(), "files/", fileName), file, "utf8");
await writeFile(join(process.cwd(), "assets/", fileName), file, "utf8");
} catch (err) {
console.error(err);
throw new InternalServerErrorException("File save failed !");
@ -123,7 +123,7 @@ export class StorageService {
*/
private async getFile(fileName: string): Promise<Buffer> {
try {
return await readFile(join(process.cwd(), "files/", fileName));
return await readFile(join(process.cwd(), "assets/", fileName));
} catch (err) {
throw new NotFoundException("File not found");
}
@ -245,9 +245,11 @@ export class StorageService {
await this.saveFile(info.fileName, file);
// All good we return data about the file to append it to the db.
console.log(`File "${info.fileName}" saved successfully.`);
return info;
} catch (err) {
throw new BadRequestException(err);
console.error(err);
throw err;
}
}
}

View File

@ -1,5 +1,5 @@
import { StreamableFile } from "@nestjs/common";
import { GuessedFile } from 'magic-bytes.js/dist/model/tree';
import { GuessedFile } from "magic-bytes.js/dist/model/tree";
export interface IFileInformation {
fileDisplayName: string;

0
assets/.gitkeep Normal file
View File