Compare commits

...

6 Commits

Author SHA1 Message Date
4547a22f5c
Add Swagger metadata to DTOs and controllers
Enhanced API documentation by adding Swagger decorators like @ApiProperty, @ApiTags, and @ApiBearerAuth to DTOs and controllers. This will improve clarity and usability of the API for developers using Swagger.
2024-10-17 14:21:23 +02:00
1020d6283d
Refactor code to improve readability and consistency
Reformatted import statements and function parameters for better readability. Improved alignment and punctuation to enhance code consistency and maintainability.
2024-10-17 12:26:51 +02:00
0330358139
Add missing semicolons for consistency
This commit adds missing semicolons to type definitions in schema.ts for consistent code style and better readability. No functional changes were made to the existing code logic.
2024-10-17 12:25:11 +02:00
38634132ba
Add search method to Files Service
Implemented a method to search for files in the database by limit, offset, and search term. This method enhances the service by providing a way to paginate and filter file results.
2024-10-17 12:21:58 +02:00
7b4792b612
Fix data type in pagination schema
Updated the `data` property in the pagination schema from a single object to an array of objects to accurately represent paginated data. This change ensures that the schema aligns with the actual data structure used in the application.
2024-10-17 12:21:21 +02:00
989ec71e2e
Fix data type in pagination schema
Updated the `data` property in the pagination schema from a single object to an array of objects to accurately represent paginated data. This change ensures that the schema aligns with the actual data structure used in the application.
2024-10-17 12:21:14 +02:00
13 changed files with 103 additions and 37 deletions

View File

@ -1,8 +1,10 @@
import { Controller, Get } from "@nestjs/common"; import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service"; import { AppService } from "./app.service";
import { ApiTags } from '@nestjs/swagger';
@Controller() @Controller()
@ApiTags("useless")
export class AppController { export class AppController {
constructor(private readonly appService: AppService) {} constructor(private readonly appService: AppService) {}

View File

@ -13,7 +13,9 @@ import {
import { SignInDto, SignUpDto } from "apps/backend/src/app/auth/auth.dto"; import { SignInDto, SignUpDto } from "apps/backend/src/app/auth/auth.dto";
import { AuthService } from "apps/backend/src/app/auth/auth.service"; import { AuthService } from "apps/backend/src/app/auth/auth.service";
import { UserGuard } from "./auth.guard"; import { UserGuard } from "./auth.guard";
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('User authentification')
@Controller("auth") @Controller("auth")
export class AuthController { export class AuthController {
constructor(private readonly authService: AuthService) {} constructor(private readonly authService: AuthService) {}
@ -33,6 +35,7 @@ export class AuthController {
return this.authService.doLogin(dto); return this.authService.doLogin(dto);
} }
//GET me -- Get current user data via jwt //GET me -- Get current user data via jwt
@ApiBearerAuth()
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Get("me") @Get("me")
@UseGuards(UserGuard) @UseGuards(UserGuard)
@ -46,6 +49,7 @@ export class AuthController {
return userData; return userData;
} }
//DELETE me //DELETE me
@ApiBearerAuth()
@HttpCode(HttpStatus.FOUND) @HttpCode(HttpStatus.FOUND)
@Delete("me") @Delete("me")
@UseGuards(UserGuard) @UseGuards(UserGuard)

View File

@ -6,6 +6,7 @@ import {
MaxLength, MaxLength,
MinLength, MinLength,
} from "class-validator"; } from "class-validator";
import { ApiProperty } from '@nestjs/swagger';
export class SignUpDto { export class SignUpDto {
/* /*
@ -22,11 +23,17 @@ export class SignUpDto {
lastName: string; lastName: string;
**/ **/
@ApiProperty({
example: 'jean@paul.fr',
})
@MaxLength(32) @MaxLength(32)
@IsEmail() @IsEmail()
@IsNotEmpty() @IsNotEmpty()
email: string; email: string;
@ApiProperty({
example: 'zSEs-6ze$',
})
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsStrongPassword({ @IsStrongPassword({
@ -36,12 +43,16 @@ export class SignUpDto {
} }
export class SignInDto { export class SignInDto {
@MaxLength(32) @MaxLength(32)@ApiProperty({
example: 'jean@paul.fr',
})
@IsEmail() @IsEmail()
@IsNotEmpty() @IsNotEmpty()
email: string; email: string;
@IsString() @IsString()@ApiProperty({
example: 'zSEs-6ze$',
})
@IsNotEmpty() @IsNotEmpty()
@IsStrongPassword({ @IsStrongPassword({
minLength: 6, minLength: 6,

View File

@ -11,7 +11,9 @@ import {
} from "@nestjs/common"; } from "@nestjs/common";
import { AdminGuard } from "apps/backend/src/app/auth/auth.guard"; import { AdminGuard } from "apps/backend/src/app/auth/auth.guard";
import { AuthorsService } from "apps/backend/src/app/authors/authors.service"; import { AuthorsService } from "apps/backend/src/app/authors/authors.service";
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('File authors')
@Controller("authors") @Controller("authors")
export class AuthorsController { export class AuthorsController {
constructor(private readonly authorService: AuthorsService) {} constructor(private readonly authorService: AuthorsService) {}
@ -25,8 +27,8 @@ export class AuthorsController {
return await this.authorService.find(limit, offset, search); return await this.authorService.find(limit, offset, search);
} }
//TODO DTO //TODO Refactor
@ApiBearerAuth()
@UseGuards(AdminGuard) @UseGuards(AdminGuard)
@Delete(":autor") @Delete(":autor")
async deleteAuthor(@Param("author") author: string) { async deleteAuthor(@Param("author") author: string) {

View File

@ -103,7 +103,7 @@ export const FilesTable = pgTable("files", {
}) })
.notNull(), .notNull(),
}); });
export type IFileTable = typeof FilesTable.$inferSelect export type IFileTable = typeof FilesTable.$inferSelect;
export const FilesGroupTable = pgTable("f_groups", { export const FilesGroupTable = pgTable("f_groups", {
uuid: p.uuid("uuid").unique().primaryKey().defaultRandom().notNull(), uuid: p.uuid("uuid").unique().primaryKey().defaultRandom().notNull(),
@ -115,8 +115,7 @@ export const FilesGroupTable = pgTable("f_groups", {
.unique() .unique()
.notNull(), .notNull(),
}); });
export type IFileGroupTable = typeof FilesGroupTable.$inferSelect export type IFileGroupTable = typeof FilesGroupTable.$inferSelect;
//TODO Files types //TODO Files types
export const FilesTypesTable = pgTable("f_types", { export const FilesTypesTable = pgTable("f_types", {
@ -137,7 +136,7 @@ export const FilesTypesTable = pgTable("f_types", {
.unique() .unique()
.notNull(), .notNull(),
}); });
export type IFilesTypesTable = typeof FilesTypesTable.$inferSelect export type IFilesTypesTable = typeof FilesTypesTable.$inferSelect;
export const MachinesTable = pgTable("machines", { export const MachinesTable = pgTable("machines", {
id: p.uuid("id").unique().primaryKey().defaultRandom().notNull(), id: p.uuid("id").unique().primaryKey().defaultRandom().notNull(),
@ -156,7 +155,7 @@ export const MachinesTable = pgTable("machines", {
//supported files format //supported files format
}); });
export type IMachinesTable = typeof MachinesTable.$inferSelect export type IMachinesTable = typeof MachinesTable.$inferSelect;
//TODO Many to Many table betwen File en Machine //TODO Many to Many table betwen File en Machine
export const FilesForMachinesTable = pgTable("files_for_machines", { export const FilesForMachinesTable = pgTable("files_for_machines", {
@ -172,7 +171,7 @@ export const FilesForMachinesTable = pgTable("files_for_machines", {
.notNull() .notNull()
.references(() => MachinesTable.id), .references(() => MachinesTable.id),
}); });
export type IFilesForMachinesTable = typeof FilesForMachinesTable.$inferSelect export type IFilesForMachinesTable = typeof FilesForMachinesTable.$inferSelect;
export const FilesTypeForMachine = pgTable("f_type_for_machines", { export const FilesTypeForMachine = pgTable("f_type_for_machines", {
id: p.uuid("id").unique().primaryKey().defaultRandom().notNull(), id: p.uuid("id").unique().primaryKey().defaultRandom().notNull(),
@ -186,11 +185,11 @@ export const FilesTypeForMachine = pgTable("f_type_for_machines", {
.notNull() .notNull()
.references(() => FilesTypesTable.id), .references(() => FilesTypesTable.id),
}); });
export type IFilesTypeForMachine = typeof FilesTypeForMachine.$inferSelect export type IFilesTypeForMachine = typeof FilesTypeForMachine.$inferSelect;
export interface IWithCount<T> { export interface IWithCount<T> {
count: number; count: number;
limit: number; limit: number;
currentOffset: number; currentOffset: number;
data: T data: T[];
} }

View File

@ -23,7 +23,9 @@ import {
import { CreateFileTypeDto } from "apps/backend/src/app/files/files.dto"; import { CreateFileTypeDto } from "apps/backend/src/app/files/files.dto";
import { AdminGuard, InsertAdminState } from "../auth/auth.guard"; import { AdminGuard, InsertAdminState } from "../auth/auth.guard";
import { FilesService } from "./files.service"; import { FilesService } from "./files.service";
import { ApiBearerAuth, ApiSecurity, ApiTags } from '@nestjs/swagger';
@ApiTags('Files')
@Controller("files") @Controller("files")
export class FilesController { export class FilesController {
constructor(private readonly filesService: FilesService) {} constructor(private readonly filesService: FilesService) {}
@ -129,6 +131,7 @@ export class FilesController {
return await this.filesService.getAllFilesTypes(); return await this.filesService.getAllFilesTypes();
} }
@ApiBearerAuth()
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.CREATED)
@UseGuards(AdminGuard) @UseGuards(AdminGuard)
@Post("types/new") @Post("types/new")
@ -136,6 +139,7 @@ export class FilesController {
return await this.filesService.createFileType(body.name, body.mime); return await this.filesService.createFileType(body.name, body.mime);
} }
@ApiBearerAuth()
@HttpCode(HttpStatus.ACCEPTED) @HttpCode(HttpStatus.ACCEPTED)
@UseGuards(AdminGuard) @UseGuards(AdminGuard)
@Delete("types/:typeId") @Delete("types/:typeId")
@ -149,6 +153,7 @@ export class FilesController {
return await this.filesService.get(fileId); return await this.filesService.get(fileId);
} }
@ApiBearerAuth()
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UseGuards(AdminGuard) @UseGuards(AdminGuard)
@Delete(":fileId") @Delete(":fileId")

View File

@ -1,27 +1,26 @@
import { DefaultValuePipe } from "@nestjs/common"; import { DefaultValuePipe } from "@nestjs/common";
import { IsUUID, MaxLength, MinLength } from "class-validator"; import { IsUUID, MaxLength, MinLength } from "class-validator";
import { ApiBearerAuth, ApiProperty } from '@nestjs/swagger';
export class CreateFilesDto {
@MaxLength(128)
@MinLength(4)
fileName: string;
@MaxLength(64)
@MinLength(2)
uploadedBy: string;
isDocumentation?: boolean;
isRestricted?: boolean;
@IsUUID()
groupId: string;
}
export class CreateFileTypeDto { export class CreateFileTypeDto {
@ApiProperty({
description: "Admin uniquement, nom d'affichage.",
examples: [
".scad",
"jpg"
]
})
@MaxLength(128) @MaxLength(128)
@MinLength(3) @MinLength(3)
name: string; name: string;
@ApiProperty({
description: "Admin uniquement, Multipurpose Internet Mail Extensions (MIME)",
examples: [
"application/x-openscad",
"image/jpeg"
]
})
@MaxLength(64) @MaxLength(64)
@MinLength(4) @MinLength(4)
mime: string; mime: string;

View File

@ -12,10 +12,12 @@ import {
FilesTable, FilesTable,
FilesTypeForMachine, FilesTypeForMachine,
FilesTypesTable, FilesTypesTable,
IFileTable,
IWithCount,
MachinesTable, MachinesTable,
} from "apps/backend/src/app/db/schema"; } from "apps/backend/src/app/db/schema";
import { StorageService } from "apps/backend/src/app/storage/storage.service"; import { StorageService } from "apps/backend/src/app/storage/storage.service";
import { eq, ilike } from "drizzle-orm"; import { count, eq, ilike } from "drizzle-orm";
@Injectable() @Injectable()
export class FilesService { export class FilesService {
@ -124,17 +126,27 @@ export class FilesService {
} }
/** /**
* Searches for files in the database using the specified search field, limit, and offset. * Searches for files in the database based on the provided search field, limit, and offset.
* *
* @param {number} limit - The maximum number of results to return. * @param {number} limit - The maximum number of results to return.
* @param {number} offset - The number of results to skip before starting to return results. * @param {number} offset - The offset for the results.
* @param {string} searchField - The field used to search for matching file names. * @param {string} searchField - The value to search for within the file names.
* * @return {Promise<IWithCount<IFileTable>>} A promise that resolves to an object containing the count of files that match the search criteria, the provided limit, the current offset, and the matching data.
* @return {Promise<object>} A promise that resolves to the search results.
*/ */
public async search(limit: number, offset: number, searchField: string) { public async search(
limit: number,
offset: number,
searchField: string,
): Promise<IWithCount<IFileTable>> {
try { try {
return await this.database const countResult = await this.database
.use()
.select({ count: count() })
.from(FilesTable)
.where(ilike(FilesTable.fileName, String(`%${searchField}%`)))
.prepare("searchFilesCount")
.execute();
const dataResult = await this.database
.use() .use()
.select() .select()
.from(FilesTable) .from(FilesTable)
@ -143,6 +155,12 @@ export class FilesService {
.offset(offset) .offset(offset)
.prepare("searchFiles") .prepare("searchFiles")
.execute(); .execute();
return {
count: countResult[0].count,
limit: limit,
currentOffset: offset,
data: dataResult,
};
} catch (error) { } catch (error) {
throw new InternalServerErrorException(error); throw new InternalServerErrorException(error);
} }

View File

@ -14,7 +14,9 @@ import { AdminGuard } from "apps/backend/src/app/auth/auth.guard";
import { CreateGroupDto } from "apps/backend/src/app/groups/groups.dto"; 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 { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('File groups')
@Controller("groups") @Controller("groups")
export class GroupsController { export class GroupsController {
constructor(private readonly groupsService: GroupsService) {} constructor(private readonly groupsService: GroupsService) {}
@ -33,6 +35,7 @@ export class GroupsController {
return await this.groupsService.newGroup(body.groupName); return await this.groupsService.newGroup(body.groupName);
} }
@ApiBearerAuth()
@UseGuards(AdminGuard) @UseGuards(AdminGuard)
@Delete(":groupId") @Delete(":groupId")
async deleteGroup(@Param("groupId") groupId: string) { async deleteGroup(@Param("groupId") groupId: string) {

View File

@ -1,6 +1,11 @@
import { IsString, MaxLength, MinLength } from "class-validator"; import { IsString, MaxLength, MinLength } from "class-validator";
import { ApiProperty } from '@nestjs/swagger';
export class CreateGroupDto { export class CreateGroupDto {
@ApiProperty({
description: "Nom unique.",
example: "Numérique en communs"
})
@IsString() @IsString()
@MinLength(4) @MinLength(4)
@MaxLength(64) @MaxLength(64)

View File

@ -19,7 +19,9 @@ import {
TypeDto, TypeDto,
} from "apps/backend/src/app/machines/machines.dto"; } from "apps/backend/src/app/machines/machines.dto";
import { MachinesService } from "apps/backend/src/app/machines/machines.service"; import { MachinesService } from "apps/backend/src/app/machines/machines.service";
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Machines')
@Controller("machines") @Controller("machines")
export class MachinesController { export class MachinesController {
constructor(private readonly machineService: MachinesService) {} constructor(private readonly machineService: MachinesService) {}

View File

@ -1,16 +1,27 @@
import { IsUUID, MaxLength, MinLength } from "class-validator"; import { IsUUID, MaxLength, MinLength } from "class-validator";
import { ApiProperty } from '@nestjs/swagger';
export class CreateMachineDto { export class CreateMachineDto {
@ApiProperty({
example: "Découpeuse laser portable"
})
@MaxLength(128) @MaxLength(128)
@MinLength(4) @MinLength(4)
machineName: string; machineName: string;
@ApiProperty({
example: "Découpe au laser"
})
@MaxLength(64) @MaxLength(64)
@MinLength(2) @MinLength(2)
machineType: string; machineType: string;
} }
export class TypeDto { export class TypeDto {
@ApiProperty({
description: "Un identifiant unique présent en base de donnée qui représente un MIME",
example: "dfd0fbb1-2bf3-4dbe-b86d-89b1bff5106c"
})
@IsUUID() @IsUUID()
fileTypeId: string; fileTypeId: string;
} }

View File

@ -10,6 +10,11 @@ async function bootstrap() {
.setTitle("Fab Explorer") .setTitle("Fab Explorer")
.setDescription("Définition de l'api du FabLab Explorer") .setDescription("Définition de l'api du FabLab Explorer")
.setVersion("1.0") .setVersion("1.0")
.addBearerAuth({
type: 'http',
scheme: 'bearer',
in: 'header',
})
.build(); .build();
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);