Compare commits
10 Commits
1818fcfe88
...
ceef9f94b6
Author | SHA1 | Date | |
---|---|---|---|
ceef9f94b6 | |||
65118e9465 | |||
66ca040807 | |||
07d65484c3 | |||
e9277ba763 | |||
7e8d6e73eb | |||
ef5349063d | |||
46b090c366 | |||
534560bae5 | |||
aecc22a733 |
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,9 +83,9 @@ export const FilesTable = pgTable("files", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => FilesTypesTable.id),
|
.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
|
uploadedAt: p
|
||||||
.timestamp("uploaded_at", {
|
.timestamp("uploaded_at", {
|
||||||
@ -151,6 +151,19 @@ 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")
|
||||||
|
@ -1,85 +1,89 @@
|
|||||||
|
import { IncomingMessage } from "node:http";
|
||||||
import {
|
import {
|
||||||
Controller,
|
BadRequestException,
|
||||||
DefaultValuePipe,
|
Controller,
|
||||||
Get,
|
DefaultValuePipe,
|
||||||
Param,
|
Get,
|
||||||
ParseIntPipe,
|
HttpCode,
|
||||||
Post,
|
HttpStatus,
|
||||||
Query,
|
Param,
|
||||||
Req,
|
ParseIntPipe,
|
||||||
Res,
|
Post,
|
||||||
Request,
|
Query,
|
||||||
Response,
|
Req,
|
||||||
StreamableFile, HttpStatus, HttpCode, BadRequestException, UseGuards
|
Request,
|
||||||
} from '@nestjs/common';
|
Res,
|
||||||
|
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 _machineId = req.headers['machine_id'];
|
const _uploadedBy = req.headers["uploaded_by"] as string;
|
||||||
const _isDocumentation = req.headers['is_documentation'] as string;
|
const _machineId = req.headers["machine_id"];
|
||||||
const _isRestricted = req.headers['is_restricted'] as string;
|
const _isDocumentation = req.headers["is_documentation"] as string;
|
||||||
const _isAdmin = Boolean(req.headers['is_admin'] as string | boolean);
|
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
|
// 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("machinesId", Array(..._machineId))
|
.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.
|
//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) => {
|
||||||
});
|
throw new BadRequestException(err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
req.on('error', (err) => {
|
@Get("find")
|
||||||
throw new BadRequestException(err.message)
|
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('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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
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;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
import { Injectable, NotFoundException, StreamableFile } from '@nestjs/common';
|
import {
|
||||||
import { DbService } from 'apps/backend/src/app/db/db.service';
|
Injectable,
|
||||||
import { FilesTable } from 'apps/backend/src/app/db/schema';
|
InternalServerErrorException,
|
||||||
import { StorageService } from 'apps/backend/src/app/storage/storage.service';
|
NotFoundException,
|
||||||
import { eq, ilike } from 'drizzle-orm';
|
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()
|
@Injectable()
|
||||||
export class FilesService {
|
export class FilesService {
|
||||||
@ -12,7 +23,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.
|
||||||
@ -30,8 +41,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(
|
||||||
@ -51,7 +62,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()}"`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +79,90 @@ export class FilesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO save a file
|
//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")}" `,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,9 +29,7 @@ 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")
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { IsString, MinLength, MaxLength } from 'class-validator';
|
import { IsString, MaxLength, MinLength } from "class-validator";
|
||||||
|
|
||||||
export class CreateGroupDto {
|
export class CreateGroupDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(4)
|
@MinLength(4)
|
||||||
@MaxLength(64)
|
@MaxLength(64)
|
||||||
groupName: string;
|
groupName: string;
|
||||||
}
|
}
|
||||||
|
@ -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],
|
||||||
|
@ -1,29 +1,28 @@
|
|||||||
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.use()
|
return await this.database
|
||||||
.select()
|
.use()
|
||||||
.from(FilesGroupTable)
|
.select()
|
||||||
.where(ilike(FilesGroupTable.groupName, search))
|
.from(FilesGroupTable)
|
||||||
.limit(limit)
|
.where(ilike(FilesGroupTable.groupName, search))
|
||||||
.offset(offset)
|
.limit(limit)
|
||||||
.prepare("getGroupsByName")
|
.offset(offset)
|
||||||
.execute();
|
.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)
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user