9 Commits

Author SHA1 Message Date
2b9d00678d Actualiser README.md 2026-04-23 17:24:40 +02:00
586cf6dc73 MAJ README.md 2026-04-23 17:15:19 +02:00
a3380ffdf5 Add README.md 2026-04-23 16:32:26 +02:00
1dcb3c35f2 MAJ update movement 2026-04-21 10:27:00 +02:00
oistig
2af5c1e015 fix 2026-04-20 15:07:30 +02:00
260fe71d4f MAJ ajout produit 2026-04-13 15:09:28 +02:00
94df917149 MAJ ajout de la méthode refresh 2025-12-11 14:54:37 +01:00
0a8258017a MAJ avec l'authentifiation 2025-12-08 12:27:05 +01:00
78e5a4e960 MAJ avec l'ajout de la gestion d'authentification 2025-12-08 12:26:46 +01:00
75 changed files with 1054 additions and 156 deletions

View File

@@ -0,0 +1,7 @@
namespace PyroFetes.DTO.Login.Request;
public class ConnectLoginDto
{
public string? Name { get; set; }
public string? Password { get; set; }
}

View File

@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
// Nécessaire pour les validations
namespace PyroFetes.DTO.Login.Request;
public class CreateLoginDto
{
[Required(ErrorMessage = "Le nom est requis.")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "L'identifiant doit faire entre 3 et 50 caractères.")]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "L'emil est requis.")]
[StringLength(50, MinimumLength = 3)]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "Le mot de passe est requis.")]
[MinLength(6, ErrorMessage = "Le mot de passe doit contenir au moins 6 caractères.")]
public string Password { get; set; } = string.Empty;
// Ajout du champ Rôle (Optionnel, par défaut "User")
// Cela te permet d'envoyer "Admin" via Swagger
public string Fonction { get; set; } = "User";
}

View File

@@ -0,0 +1,8 @@
namespace PyroFetes.DTO.Login.Request;
public class UpdateLoginDto
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Password { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace PyroFetes.DTO.Login.Response;
public class GetLoginConnectDto
{
public string? Token { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace PyroFetes.DTO.Login.Response;
public class GetLoginDto
{
public int Id { get; set; }
public string? Name { get; set; } = string.Empty;
public string? Email { get; set; } = string.Empty;
public string? Password { get; set; } = string.Empty;
public string? Fonction { get; set; } = string.Empty;
}

View File

@@ -1,19 +1,23 @@
// Définition de l'espace de noms pour les DTO utilisés dans les requêtes liées aux mouvements using PyroFetes.Models;
namespace API.DTO.Movement.Request
// Assure-toi d'importer tes enums
namespace PyroFetes.DTO.Movement.Request
{ {
// DTO utilisé pour créer un nouveau mouvement
public class CreateMovementDto public class CreateMovementDto
{ {
// Date à laquelle le mouvement est enregistré public int ProductId { get; set; }
public DateTime Date { get; set; }
// Date et heure de début du mouvement public MovementType Type { get; set; }
public int Quantity { get; set; }
public DateTime Date { get; set; } = DateTime.Now;
public DateTime Start { get; set; } public DateTime Start { get; set; }
// Date et heure d'arrivée prévue du mouvement
public DateTime Arrival { get; set; } public DateTime Arrival { get; set; }
// Quantité de matériaux ou objets impliqués dans le mouvement public int? SourceWarehouseId { get; set; }
public int Quantity { get; set; } public int? DestinationWarehouseId { get; set; }
} }
} }

View File

@@ -0,0 +1,6 @@
namespace PyroFetes.DTO.Refrresh.Request;
public class RefreshTokenDto
{
public string? Token { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace PyroFetes.DTO.Refrresh.Response;
public class GetRefreshDto
{
public string? Token { get; set; }
}

View File

@@ -9,7 +9,6 @@ public class CreateBrandEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpoi
public override void Configure() public override void Configure()
{ {
Post("/brands"); Post("/brands");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateBrandDto req, CancellationToken ct) public override async Task HandleAsync(CreateBrandDto req, CancellationToken ct)

View File

@@ -13,7 +13,6 @@ public class DeleteBrandEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpoi
public override void Configure() public override void Configure()
{ {
Delete("/brands/{@id}", x => new { x.Id }); Delete("/brands/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteBrandRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteBrandRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class GetAllBrandsEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpo
public override void Configure() public override void Configure()
{ {
Get("/brands"); Get("/brands");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -14,7 +14,6 @@ public class GetBrandEndpoint(PyroFetesDbContext pyrofetesdbcontext) :Endpoint<G
public override void Configure() public override void Configure()
{ {
Get("/brands/{@id}", x => new { x.Id }); Get("/brands/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(GetBrandRequest req, CancellationToken ct) public override async Task HandleAsync(GetBrandRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class UpdateBrandEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpoi
public override void Configure() public override void Configure()
{ {
Put("/brands/{Id}"); Put("/brands/{Id}");
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateBrandDto req, CancellationToken ct) public override async Task HandleAsync(UpdateBrandDto req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class CreateClassificationEndpoint(PyroFetesDbContext pyrofetesdbcontext)
public override void Configure() public override void Configure()
{ {
Post("/classifications"); Post("/classifications");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateClassificationDto req, CancellationToken ct) public override async Task HandleAsync(CreateClassificationDto req, CancellationToken ct)

View File

@@ -13,7 +13,6 @@ public class DeleteClassificationEndpoint(PyroFetesDbContext libraryDbContext) :
public override void Configure() public override void Configure()
{ {
Delete("/classifications/{@id}", x => new { x.Id }); Delete("/classifications/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteClassificationRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteClassificationRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class GetAllClassificationsEndpoint(PyroFetesDbContext pyrofetesdbcontext
public override void Configure() public override void Configure()
{ {
Get("/classifications"); Get("/classifications");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -14,7 +14,6 @@ public class GetClassificationEndpoint(PyroFetesDbContext pyrofetesdbcontext) :E
public override void Configure() public override void Configure()
{ {
Get("/classifications/{@id}", x => new { x.Id }); Get("/classifications/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(GetClassificationRequest req, CancellationToken ct) public override async Task HandleAsync(GetClassificationRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class UpdateClassificationEndpoint(PyroFetesDbContext pyrofetesdbcontext)
public override void Configure() public override void Configure()
{ {
Put("/classifications"); Put("/classifications");
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateClassificationDto req, CancellationToken ct) public override async Task HandleAsync(UpdateClassificationDto req, CancellationToken ct)

View File

@@ -1,16 +1,14 @@
using API.DTO.Color.Request; using API.DTO.Color.Request;
using API.DTO.Color.Response; using API.DTO.Color.Response;
using FastEndpoints; using FastEndpoints;
using PyroFetes;
namespace API.Endpoints.Color; namespace PyroFetes.Endpoints.Color;
public class CreateColorEndpoint(PyroFetesDbContext pyroFetesDbContext) : Endpoint<CreateColorDto, GetColorDto> //Instanciation d'une connexion à la bdd dans un endpoint, utilise l'élément de requête CreateColorDto et l'élement de réponse GetColorDto public class CreateColorEndpoint(PyroFetesDbContext pyroFetesDbContext) : Endpoint<CreateColorDto, GetColorDto> //Instanciation d'une connexion à la bdd dans un endpoint, utilise l'élément de requête CreateColorDto et l'élement de réponse GetColorDto
{ {
public override void Configure() //Configuration de l'endpoint public override void Configure() //Configuration de l'endpoint
{ {
Post("/colors"); //Création d'un endpoint pour créer une couleur avec les données de CreateColorDto Post("/colors"); //Création d'un endpoint pour créer une couleur avec les données de CreateColorDto
AllowAnonymous(); //Laisser passer les requêtes non authentifiées
} }
public override async Task HandleAsync(CreateColorDto req, CancellationToken ct) //La méthode HandleAsync est appelée lorsqu'une requête est envoyée à l'endpoint public override async Task HandleAsync(CreateColorDto req, CancellationToken ct) //La méthode HandleAsync est appelée lorsqu'une requête est envoyée à l'endpoint

View File

@@ -13,7 +13,6 @@ public class DeleteColorEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpoi
public override void Configure() public override void Configure()
{ {
Delete("/colors/{@id}", x => new { x.Id }); Delete("/colors/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteColorRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteColorRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class GetAllColorsEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpo
public override void Configure() public override void Configure()
{ {
Get("/colors"); Get("/colors");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -14,7 +14,6 @@ public class GetColorEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpoint<
public override void Configure() public override void Configure()
{ {
Get("/colors/{@id}", x => new { x.Id}); Get("/colors/{@id}", x => new { x.Id});
AllowAnonymous();
} }
public override async Task HandleAsync(GetColorRequest req, CancellationToken ct) public override async Task HandleAsync(GetColorRequest req, CancellationToken ct)

View File

@@ -10,7 +10,6 @@ public class UpdateColorEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpoi
public override void Configure() public override void Configure()
{ {
Put("/colors/{@id}", x => new { x.Id }); Put("/colors/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateColorDto req, CancellationToken ct) public override async Task HandleAsync(UpdateColorDto req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class CreateEffectEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpo
public override void Configure() public override void Configure()
{ {
Post("/effects"); Post("/effects");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateEffectDto req, CancellationToken ct) public override async Task HandleAsync(CreateEffectDto req, CancellationToken ct)

View File

@@ -12,7 +12,6 @@ public class DeleteEffectEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpo
public override void Configure() public override void Configure()
{ {
Delete("/effects/{@id}", x => new { x.Id }); Delete("/effects/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteEffectRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteEffectRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class GetAllEffectsEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endp
public override void Configure() public override void Configure()
{ {
Get("/effects"); Get("/effects");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -14,7 +14,6 @@ public class GetEffectEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpoint
public override void Configure() public override void Configure()
{ {
Get("/effects/{@id}", x => new { x.Id }); Get("/effects/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(GetEffectRequest req, CancellationToken ct) public override async Task HandleAsync(GetEffectRequest req, CancellationToken ct)

View File

@@ -10,8 +10,7 @@ public class UpdateEffectEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpo
public override void Configure() public override void Configure()
{ {
Put("/effects/{@id}", x => new { x.Id }); Put("/effects/{@id}", x => new { x.Id });
AllowAnonymous(); }
}
public override async Task HandleAsync(UpdateEffectDto req, CancellationToken ct) public override async Task HandleAsync(UpdateEffectDto req, CancellationToken ct)
{ {

View File

@@ -0,0 +1,53 @@
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
using PasswordGenerator;
using PyroFetes.DTO.Login.Request;
using PyroFetes.DTO.Login.Response;
namespace PyroFetes.Endpoints.Login;
public class CreateLoginEndpoint(PyroFetesDbContext database) : Endpoint<CreateLoginDto, GetLoginDto>
{
public override void Configure()
{
Post("/logins");
//Roles("Admin");
AllowAnonymous();
}
public override async Task HandleAsync(CreateLoginDto req, CancellationToken ct)
{
bool exists = await database.Users.AnyAsync(x => x.Name == req.Name, ct);
if (exists)
{
AddError("Ce nom d'utilisateur est déjà utilisé.");
await Send.ErrorsAsync(400, ct);
return;
}
string? salt = new Password().IncludeLowercase().IncludeUppercase().IncludeNumeric().LengthRequired(24).Next();
Models.User login = new Models.User()
{
Name = req.Name,
Email = req.Email,
Password = BCrypt.Net.BCrypt.HashPassword(req.Password + salt),
Salt = salt,
Fonction = string.IsNullOrEmpty(req.Fonction) ? "User" : req.Fonction
};
database.Users.Add(login);
await database.SaveChangesAsync(ct);
GetLoginDto responseDto = new()
{
Id = login.Id,
Name = login.Name,
Email = login.Email,
Fonction = login.Fonction
};
await Send.OkAsync(responseDto, ct);
}
}

View File

@@ -0,0 +1,34 @@
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
namespace PyroFetes.Endpoints.Login;
public class DeleteLoginRequest
{
public int Id { get; set; }
}
public class DeleteLoginEndpoint(PyroFetesDbContext database) : Endpoint<DeleteLoginRequest>
{
public override void Configure()
{
Delete("/logins/{@Id}", x => new {x.Id});
AllowAnonymous();
}
public override async Task HandleAsync(DeleteLoginRequest req, CancellationToken ct)
{
Models.User? login = await database.Users.SingleOrDefaultAsync(x => x.Id == req.Id, ct);
if (login == null)
{
await Send.NotFoundAsync(ct);
return;
}
database.Users.Remove(login);
await database.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
}
}

View File

@@ -0,0 +1,29 @@
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
using PyroFetes.DTO.Login.Response;
namespace PyroFetes.Endpoints.Login;
public class GetAllLoginEndpoint(PyroFetesDbContext database) : EndpointWithoutRequest<List<GetLoginDto>>
{
public override void Configure()
{
Get("/logins");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken ct)
{
List<GetLoginDto> logins = await database.Users
.Select(login => new GetLoginDto()
{
Id = login.Id,
Name = login.Name,
Password = login.Password,
Fonction = login.Fonction
})
.ToListAsync(ct);
await Send.OkAsync(logins, ct);
}
}

View File

@@ -0,0 +1,40 @@
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
using PyroFetes.DTO.Login.Response;
namespace PyroFetes.Endpoints.Login;
public class GetLoginRequest
{
public int Id { get; set; }
}
public class GetLoginEndpoint(PyroFetesDbContext database) : Endpoint<GetLoginRequest, GetLoginDto>
{
public override void Configure()
{
Get("/logins/{@Id}", x => new {x.Id});
AllowAnonymous();
}
public override async Task HandleAsync(GetLoginRequest req, CancellationToken ct)
{
Models.User? login = await database.Users
.SingleOrDefaultAsync(x => x.Id == req.Id, ct);
if (login == null)
{
await Send.NotFoundAsync(ct);
return;
}
GetLoginDto responseDto = new()
{
Id = login.Id,
Name = login.Name,
Fonction = login.Fonction
};
await Send.OkAsync(responseDto, ct);
}
}

View File

@@ -0,0 +1,43 @@
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
using PasswordGenerator;
using PyroFetes.DTO.Login.Request;
using PyroFetes.DTO.Login.Response;
namespace PyroFetes.Endpoints.Login;
public class UpdateLoginEndpoint(PyroFetesDbContext database) : Endpoint<UpdateLoginDto, GetLoginDto>
{
public override void Configure()
{
Put("/logins/{@Id}", x => new {x.Id});
AllowAnonymous();
}
public override async Task HandleAsync(UpdateLoginDto req, CancellationToken ct)
{
Models.User? login = await database.Users.SingleOrDefaultAsync(x => x.Id == req.Id, ct);
if (login == null)
{
await Send.NotFoundAsync(ct);
return;
}
string? salt = new Password().IncludeLowercase().IncludeUppercase().IncludeNumeric().LengthRequired(24).Next();
login.Name = req.Name;
login.Password = BCrypt.Net.BCrypt.HashPassword(req.Password + salt);
login.Salt = salt;
await database.SaveChangesAsync(ct);
GetLoginDto responseDto = new()
{
Id = login.Id,
Name = login.Name,
Fonction = login.Fonction
};
await Send.OkAsync(responseDto, ct);
}
}

View File

@@ -0,0 +1,48 @@
using FastEndpoints;
using FastEndpoints.Security;
using Microsoft.EntityFrameworkCore;
using PyroFetes.DTO.Login.Request;
using PyroFetes.DTO.Login.Response;
namespace PyroFetes.Endpoints.Login;
public class UserLoginEndpoint(PyroFetesDbContext database) : Endpoint<ConnectLoginDto, GetLoginConnectDto>
{
public override void Configure()
{
Post("/login");
AllowAnonymous();
}
public override async Task HandleAsync(ConnectLoginDto req, CancellationToken ct)
{
Models.User? login = await database.Users.SingleOrDefaultAsync(x => x.Name == req.Name, ct);
if (login == null)
{
await Send.UnauthorizedAsync(ct);
return;
}
if (BCrypt.Net.BCrypt.Verify(req.Password + login.Salt, login.Password))
{
string jwtToken = JwtBearer.CreateToken(
o =>
{
o.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong";
o.ExpireAt = DateTime.UtcNow.AddMinutes(15);
if (login.Fonction != null) o.User.Roles.Add(login.Fonction);
o.User.Claims.Add(("Username", login.Name)!);
o.User["UserId"] = "001";
});
GetLoginConnectDto responseDto = new()
{
Token = jwtToken
};
await Send.OkAsync(responseDto, ct);
}
else await Send.UnauthorizedAsync(ct);
}
}

View File

@@ -9,7 +9,6 @@ public class CreateMaterialEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
public override void Configure() public override void Configure()
{ {
Post("/materials"); Post("/materials");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateMaterialDto req, CancellationToken ct) public override async Task HandleAsync(CreateMaterialDto req, CancellationToken ct)

View File

@@ -11,7 +11,6 @@ public class DeleteMaterialEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
public override void Configure() public override void Configure()
{ {
Delete("/materials/{@id}", x => new { x.Id }); Delete("/materials/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteMaterialRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteMaterialRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class GetAllMaterialsEndpoint(PyroFetesDbContext pyrofetesdbcontext) : En
public override void Configure() public override void Configure()
{ {
Get("/materials"); Get("/materials");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -14,7 +14,6 @@ public class GetMaterialEndpoint(PyroFetesDbContext pyrofetesdbcontext) : Endpoi
public override void Configure() public override void Configure()
{ {
Get("/materials/{@id}", x => new { x.Id }); Get("/materials/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(GetMaterialRequest req, CancellationToken ct) public override async Task HandleAsync(GetMaterialRequest req, CancellationToken ct)

View File

@@ -10,7 +10,6 @@ public class UpdateMaterialEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
public override void Configure() public override void Configure()
{ {
Put("/materials/{@id}", x => new { x.Id }); Put("/materials/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateMaterialDto req, CancellationToken ct) public override async Task HandleAsync(UpdateMaterialDto req, CancellationToken ct)

View File

@@ -1,6 +1,7 @@
using API.DTO.Movement.Request; using API.DTO.Movement.Request;
using API.DTO.Movement.Response; using API.DTO.Movement.Response;
using FastEndpoints; using FastEndpoints;
using PyroFetes.DTO.Movement.Request;
namespace PyroFetes.Endpoints.Movement; namespace PyroFetes.Endpoints.Movement;
@@ -9,7 +10,6 @@ public class CreateMovementEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
public override void Configure() public override void Configure()
{ {
Post("/movements"); Post("/movements");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateMovementDto req, CancellationToken ct) public override async Task HandleAsync(CreateMovementDto req, CancellationToken ct)
@@ -18,9 +18,11 @@ public class CreateMovementEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
Models.Movement movement = new () Models.Movement movement = new ()
{ {
Date = req.Date, Date = req.Date,
ProductId = req.ProductId,
Start = req.Start, Start = req.Start,
Arrival = req.Arrival, Arrival = req.Arrival,
Quantity = req.Quantity Quantity = req.Quantity,
Type = req.Type,
}; };
pyrofetesdbcontext.Movements.Add(movement); pyrofetesdbcontext.Movements.Add(movement);

View File

@@ -13,7 +13,6 @@ public class DeleteMovementEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
public override void Configure() public override void Configure()
{ {
Delete("/Movements/{@id}", x => new { x.Id }); Delete("/Movements/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteMovementRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteMovementRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class GetAllMovementsEndpoint(PyroFetesDbContext pyrofetesdbcontext) : En
public override void Configure() public override void Configure()
{ {
Get("/movements"); Get("/movements");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -14,7 +14,6 @@ public class GetMovementEndpoint(PyroFetesDbContext pyrofetesdbcontext) :Endpoin
public override void Configure() public override void Configure()
{ {
Get("/movements/{@id}", x => new { x.Id }); Get("/movements/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(GetMovementRequest req, CancellationToken ct) public override async Task HandleAsync(GetMovementRequest req, CancellationToken ct)

View File

@@ -9,7 +9,6 @@ public class UpdateMovementEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
public override void Configure() public override void Configure()
{ {
Put("/movements"); Put("/movements");
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateMovementDto req, CancellationToken ct) public override async Task HandleAsync(UpdateMovementDto req, CancellationToken ct)

View File

@@ -12,7 +12,6 @@ public class CreateProductEndpoint(PyroFetesDbContext db)
public override void Configure() public override void Configure()
{ {
Post("/products"); Post("/products");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateProductDto req, CancellationToken ct) public override async Task HandleAsync(CreateProductDto req, CancellationToken ct)
@@ -29,7 +28,7 @@ public class CreateProductEndpoint(PyroFetesDbContext db)
Image = req.Image!, Image = req.Image!,
Link = req.Link!, Link = req.Link!,
ProductCategoryId = req.ProductCategoryId, ProductCategoryId = req.ProductCategoryId,
ClassificationId = req.ClassificationId ClassificationId = req.ClassificationId,
}; };
db.Products.Add(product); db.Products.Add(product);

View File

@@ -14,7 +14,6 @@ public class DeleteProductEndpoint(PyroFetesDbContext db) : Endpoint<DeleteProdu
public override void Configure() public override void Configure()
{ {
Delete("/products/{@id}", x => new { x.Id }); Delete("/products/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteProductRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteProductRequest req, CancellationToken ct)

View File

@@ -12,7 +12,6 @@ public class GetAllProductsEndpoint(PyroFetesDbContext db)
public override void Configure() public override void Configure()
{ {
Get("/products"); Get("/products");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -17,7 +17,6 @@ public class GetProductEndpoint(PyroFetesDbContext db)
public override void Configure() public override void Configure()
{ {
Get("/products/{@id}", x => new { x.Id }); Get("/products/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(GetProductRequest req, CancellationToken ct) public override async Task HandleAsync(GetProductRequest req, CancellationToken ct)
@@ -25,7 +24,7 @@ public class GetProductEndpoint(PyroFetesDbContext db)
// Inclure toutes les relations : Prices + WarehouseProducts + Warehouse // Inclure toutes les relations : Prices + WarehouseProducts + Warehouse
var product = await db.Products var product = await db.Products
.Include(p => p.Prices) .Include(p => p.Prices)
.Include(p => p.WarehouseProducts) .Include(p => p.WarehouseProducts)!
.ThenInclude(wp => wp.Warehouse) .ThenInclude(wp => wp.Warehouse)
.SingleOrDefaultAsync(p => p.Id == req.Id, ct); .SingleOrDefaultAsync(p => p.Id == req.Id, ct);

View File

@@ -6,36 +6,29 @@ using PyroFetes.Models;
namespace PyroFetes.Endpoints.Product; namespace PyroFetes.Endpoints.Product;
// Endpoint permettant de mettre à jour un produit existant
public class UpdateProductEndpoint(PyroFetesDbContext db) public class UpdateProductEndpoint(PyroFetesDbContext db)
: Endpoint<UpdateProductDto, GetProductDto> : Endpoint<UpdateProductDto, GetProductDto>
{ {
public override void Configure() public override void Configure()
{ {
// Route HTTP PUT avec un paramètre d'identifiant dans l'URL
Put("/products/{@id}", x => new { x.Id }); Put("/products/{@id}", x => new { x.Id });
// Autorise les requêtes anonymes (sans authentification)
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateProductDto req, CancellationToken ct) public override async Task HandleAsync(UpdateProductDto req, CancellationToken ct)
{ {
// Recherche du produit à mettre à jour, en incluant les relations Prices et WarehouseProducts
var product = await db.Products var product = await db.Products
.Include(p => p.Prices) .Include(p => p.Prices)
.Include(p => p.WarehouseProducts) .Include(p => p.WarehouseProducts)
.SingleOrDefaultAsync(p => p.Id == req.Id, ct); .SingleOrDefaultAsync(p => p.Id == req.Id, ct);
// Si le produit n'existe pas, on retourne une réponse 404
if (product is null) if (product is null)
{ {
await Send.NotFoundAsync(ct); await Send.NotFoundAsync(ct);
return; return;
} }
// Mise à jour des propriétés principales du produit product.Reference = req.Reference;
product.Reference = req.Reference; // Converti int → string
product.Name = req.Name; product.Name = req.Name;
product.Duration = req.Duration; product.Duration = req.Duration;
product.Caliber = req.Caliber; product.Caliber = req.Caliber;
@@ -47,7 +40,6 @@ public class UpdateProductEndpoint(PyroFetesDbContext db)
product.ClassificationId = req.ClassificationId; product.ClassificationId = req.ClassificationId;
product.ProductCategoryId = req.ProductCategoryId; product.ProductCategoryId = req.ProductCategoryId;
// Mise à jour des prix fournisseurs associés
db.Prices.RemoveRange(product.Prices); db.Prices.RemoveRange(product.Prices);
foreach (var s in req.Suppliers) foreach (var s in req.Suppliers)
{ {
@@ -59,7 +51,6 @@ public class UpdateProductEndpoint(PyroFetesDbContext db)
}); });
} }
// Mise à jour des entrepôts associés
db.WarehouseProducts.RemoveRange(product.WarehouseProducts); db.WarehouseProducts.RemoveRange(product.WarehouseProducts);
foreach (var w in req.Warehouses) foreach (var w in req.Warehouses)
{ {
@@ -73,18 +64,16 @@ public class UpdateProductEndpoint(PyroFetesDbContext db)
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
// Construction de la réponse renvoyée au client
var response = new GetProductDto var response = new GetProductDto
{ {
Id = product.Id, Id = product.Id,
Reference = req.Reference, // DTO garde int pour cohérence Reference = req.Reference,
Name = req.Name, Name = req.Name,
Duration = req.Duration, Duration = req.Duration,
Caliber = req.Caliber, Caliber = req.Caliber,
ApprovalNumber = req.ApprovalNumber, ApprovalNumber = req.ApprovalNumber,
Weight = req.Weight, Weight = req.Weight,
Nec = req.Nec, Nec = req.Nec,
// Le prix de vente est pris depuis Prices
SellingPrice = req.Suppliers.FirstOrDefault()?.SellingPrice ?? 0, SellingPrice = req.Suppliers.FirstOrDefault()?.SellingPrice ?? 0,
Image = req.Image, Image = req.Image,
Link = req.Link, Link = req.Link,

View File

@@ -9,7 +9,6 @@ public class CreateProductCategoryEndpoint(PyroFetesDbContext pyrofetesdbcontext
public override void Configure() public override void Configure()
{ {
Post("/productcategories"); Post("/productcategories");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateProductCategoryDto req, CancellationToken ct) public override async Task HandleAsync(CreateProductCategoryDto req, CancellationToken ct)

View File

@@ -13,7 +13,6 @@ public class DeleteProductCategoryEndpoint(PyroFetesDbContext pyrofetesdbcontext
public override void Configure() public override void Configure()
{ {
Delete("/productcategories/{@id}", x => new { x.Id }); Delete("/productcategories/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteProductCategoryRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteProductCategoryRequest req, CancellationToken ct)

View File

@@ -4,12 +4,11 @@ using Microsoft.EntityFrameworkCore;
namespace PyroFetes.Endpoints.ProductCategory; namespace PyroFetes.Endpoints.ProductCategory;
public class GetAllProductCategoriesEndpoint(PyroFetesDbContext pyrofetesdbcontext) : EndpointWithoutRequest<List<GetProductCategoryDto>> public class GetAllProductCategoryEndpoint(PyroFetesDbContext pyrofetesdbcontext) : EndpointWithoutRequest<List<GetProductCategoryDto>>
{ {
public override void Configure() public override void Configure()
{ {
Get("/productcategories"); Get("/productcategories");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -15,7 +15,6 @@ public class GetProductCategoryEndpoint(PyroFetesDbContext pyrofetesdbcontext) :
public override void Configure() public override void Configure()
{ {
Get("/productcategory/{@id}", x => new { x.Id }); Get("/productcategory/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(GetProductCategoryRequest req, CancellationToken ct) public override async Task HandleAsync(GetProductCategoryRequest req, CancellationToken ct)

View File

@@ -10,7 +10,6 @@ public class UpdateProductCategoryEndpoint(PyroFetesDbContext pyrofetesdbcontext
public override void Configure() public override void Configure()
{ {
Put("/productcategory/{@id}", x => new { x.Id }); Put("/productcategory/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateProductCategoryDto req, CancellationToken ct) public override async Task HandleAsync(UpdateProductCategoryDto req, CancellationToken ct)

View File

@@ -0,0 +1,74 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using FastEndpoints;
using FastEndpoints.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using PyroFetes.DTO.Refrresh.Response;
namespace PyroFetes.Endpoints.Refresh;
public class RefreshTokenEndpoint(PyroFetesDbContext database) : EndpointWithoutRequest<GetRefreshDto>
{
public override void Configure()
{
Post("/refresh");
// [Authorize] est géré ici avec les rôles
Roles("Admin", "User");
}
public override async Task HandleAsync(CancellationToken ct)
{
try
{
// 1. 🚨 CORRECTION : Lire le claim 'Name' du jeton validé par le middleware
// Le claim 'Name' contient le nom d'utilisateur
string? userName = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
if (string.IsNullOrWhiteSpace(userName))
{
// Si le claim Name est manquant (ce qui ne devrait pas arriver)
await Send.UnauthorizedAsync(ct);
return;
}
// 2. 🚨 CORRECTION : Utiliser le Nom (Name) pour la recherche BDD
// Assurez-vous que la propriété de l'entité est 'Name' et qu'elle est unique
var login = await database.Users.FirstOrDefaultAsync(x => x.Name == userName, ct);
if (login == null)
{
await Send.UnauthorizedAsync(ct);
return;
}
// 3. Création du NOUVEAU jeton
string jwtToken = JwtBearer.CreateToken(
o =>
{
o.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong";
o.ExpireAt = DateTime.UtcNow.AddMinutes(15);
// Assurez-vous que les claims sont corrects pour Angular
if (login.Fonction != null) o.User.Roles.Add(login.Fonction);
// Ajouter le claim Name (Nom d'utilisateur)
o.User.Claims.Add((ClaimTypes.Name, login.Name)!);
// Si votre API mettait d'autres claims, ajoutez-les ici (ex: "Username")
// o.User.Claims.Add(("Username", login.Name)!);
});
// 4. Envoi de la réponse
GetRefreshDto responseDto = new()
{
Token = jwtToken
};
await Send.OkAsync(responseDto, ct);
}
catch (Exception)
{
await Send.UnauthorizedAsync(ct);
}
}
}

View File

@@ -12,7 +12,6 @@ public class CreateSupplierEndpoint(PyroFetesDbContext pyrofetesdbcontext)
public override void Configure() public override void Configure()
{ {
Post("/suppliers"); Post("/suppliers");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateSupplierDto req, CancellationToken ct) public override async Task HandleAsync(CreateSupplierDto req, CancellationToken ct)

View File

@@ -14,7 +14,6 @@ public class DeleteSupplierEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
public override void Configure() public override void Configure()
{ {
Delete("/suppliers/{@id}", x => new { x.Id }); Delete("/suppliers/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteSupplierRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteSupplierRequest req, CancellationToken ct)

View File

@@ -12,7 +12,6 @@ public class GetAllSuppliersEndpoint(PyroFetesDbContext pyrofetesdbcontext)
public override void Configure() public override void Configure()
{ {
Get("/suppliers"); Get("/suppliers");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -16,7 +16,6 @@ public class GetSupplierEndpoint(PyroFetesDbContext pyrofetesdbcontext)
public override void Configure() public override void Configure()
{ {
Get("/suppliers/{@id}", x => new { x.Id }); Get("/suppliers/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(GetSupplierRequest req, CancellationToken ct) public override async Task HandleAsync(GetSupplierRequest req, CancellationToken ct)

View File

@@ -11,7 +11,6 @@ public class UpdateSupplierEndpoint(PyroFetesDbContext pyrofetesdbcontext) : End
public override void Configure() public override void Configure()
{ {
Put("/suppliers/{@id}", x => new { x.Id }); Put("/suppliers/{@id}", x => new { x.Id });
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateSupplierDto req, CancellationToken ct) public override async Task HandleAsync(UpdateSupplierDto req, CancellationToken ct)

View File

@@ -11,7 +11,6 @@ public class CreateWarehouseEndpoint(PyroFetesDbContext db)
public override void Configure() public override void Configure()
{ {
Post("/warehouse"); Post("/warehouse");
AllowAnonymous();
} }
public override async Task HandleAsync(CreateWarehouseDto req, CancellationToken ct) public override async Task HandleAsync(CreateWarehouseDto req, CancellationToken ct)

View File

@@ -14,7 +14,6 @@ public class DeleteWarehouseEndpoint(PyroFetesDbContext db) : Endpoint<DeleteWar
public override void Configure() public override void Configure()
{ {
Delete("/warehouse/{id}"); Delete("/warehouse/{id}");
AllowAnonymous();
} }
public override async Task HandleAsync(DeleteWarehouseRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteWarehouseRequest req, CancellationToken ct)
{ {

View File

@@ -11,7 +11,6 @@ public class GetAllWarehouseEndpoint(PyroFetesDbContext db)
public override void Configure() public override void Configure()
{ {
Get("/warehouses"); Get("/warehouses");
AllowAnonymous();
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)

View File

@@ -17,7 +17,6 @@ public class GetWarehouseEndpoint(PyroFetesDbContext db)
{ {
// Pas de "@id" ici, juste {id} // Pas de "@id" ici, juste {id}
Get("/warehouses/{Id}"); Get("/warehouses/{Id}");
AllowAnonymous();
} }
public override async Task HandleAsync(GetWarehouseRequest req, CancellationToken ct) public override async Task HandleAsync(GetWarehouseRequest req, CancellationToken ct)

View File

@@ -13,7 +13,6 @@ public class UpdateWarehouseEndpoint(PyroFetesDbContext db)
{ {
// Utilise {id} plutôt que {@id} // Utilise {id} plutôt que {@id}
Put("/warehouses/{Id}"); Put("/warehouses/{Id}");
AllowAnonymous();
} }
public override async Task HandleAsync(UpdateWarehouseDto req, CancellationToken ct) public override async Task HandleAsync(UpdateWarehouseDto req, CancellationToken ct)

View File

@@ -2,18 +2,42 @@
namespace PyroFetes.Models; namespace PyroFetes.Models;
// 1. Définition des types possibles pour la logique métier
public enum MovementType
{
Entry, // Entrée (ex: Achat fournisseur)
Exit, // Sortie (ex: Vente, Casse)
Transfer, // Transfert entre deux entrepôts
Inventory // Ajustement manuel
}
public class Movement public class Movement
{ {
[Key] public int Id { get; set; } [Key]
[Required] public DateTime Date { get; set; } public int Id { get; set; }
[Required] public DateTime Start {get; set;}
[Required] public DateTime Arrival {get; set;}
[Required] public int Quantity {get; set;}
public List<Product>? Products { get; set; } [Required]
public DateTime Date { get; set; } = DateTime.Now;
public int? SourceWarehouseId {get; set;} public DateTime Start { get; set; }
public Warehouse? SourceWarehouse {get; set;} public DateTime Arrival { get; set; }
public int? DestinationWarehouseId {get; set;}
public Warehouse? DestinationWarehouse {get; set;} [Required]
public int Quantity { get; set; }
[Required]
public MovementType Type { get; set; }
[Required]
public int ProductId { get; set; }
public Product? Product { get; set; }
public int? SourceWarehouseId { get; set; }
public Warehouse? SourceWarehouse { get; set; }
public int? DestinationWarehouseId { get; set; }
public Warehouse? DestinationWarehouse { get; set; }
[MaxLength(500)]
public string? Comment { get; set; }
} }

View File

@@ -12,9 +12,9 @@ namespace PyroFetes.Models
[Required, MaxLength(100)] public string? ApprovalNumber { get; set; } [Required, MaxLength(100)] public string? ApprovalNumber { get; set; }
[Required] public decimal Weight { get; set; } [Required] public decimal Weight { get; set; }
[Required] public decimal Nec { get; set; } [Required] public decimal Nec { get; set; }
[Required] public string? Image { get; set; } public string? Image { get; set; }
[Required, MaxLength(200)] public string? Link { get; set; } [MaxLength(200)] public string? Link { get; set; }
[Required] public int MinimalQuantity { get; set; } public int MinimalQuantity { get; set; }
// Relations // Relations
[Required] public int ClassificationId { get; set; } [Required] public int ClassificationId { get; set; }
@@ -23,8 +23,7 @@ namespace PyroFetes.Models
[Required] public int ProductCategoryId { get; set; } [Required] public int ProductCategoryId { get; set; }
public ProductCategory? ProductCategory { get; set; } public ProductCategory? ProductCategory { get; set; }
[Required] public int MovementId {get; set;} public List<Movement>? Movements { get; set; }
public Movement? Movement {get; set;}
public List<ProductDelivery>? ProductDeliveries { get; set; } public List<ProductDelivery>? ProductDeliveries { get; set; }
public List<Brand>? Brands { get; set; } public List<Brand>? Brands { get; set; }
@@ -36,6 +35,5 @@ namespace PyroFetes.Models
public List<WarehouseProduct>? WarehouseProducts { get; set; } public List<WarehouseProduct>? WarehouseProducts { get; set; }
public List<ProductTimecode>? ProductTimecodes { get; set; } public List<ProductTimecode>? ProductTimecodes { get; set; }
} }
} }

View File

@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace PyroFetes.Models; namespace PyroFetes.Models;

View File

@@ -1,16 +1,25 @@
using FastEndpoints; using FastEndpoints;
using FastEndpoints.Security;
using FastEndpoints.Swagger; using FastEndpoints.Swagger;
using Microsoft.EntityFrameworkCore;
using PyroFetes; using PyroFetes;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args); WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Services builder.Services
.AddAuthenticationJwtBearer(s => s.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong")
.AddAuthentication();
builder.Services.AddAuthorization();
builder.Services.AddCors(options => builder.Services.AddCors(options =>
options.AddDefaultPolicy(policyBuilder => options.AddDefaultPolicy(policyBuilder =>
policyBuilder policyBuilder.WithOrigins("http://localhost:4200")
.WithOrigins("http://localhost:4200") // mettre le port Angular exact .WithMethods("GET", "POST", "PUT", "PATCH", "DELETE")
.WithMethods("GET", "POST", "PUT", "PATCH", "DELETE") .AllowAnyHeader()
.AllowAnyHeader() .AllowCredentials()
) )
); );
@@ -23,20 +32,19 @@ builder.Services.AddDbContext<PyroFetesDbContext>();
WebApplication app = builder.Build(); WebApplication app = builder.Build();
// Middleware
app.UseHttpsRedirection(); app.UseHttpsRedirection();
// CORS doit être avant les endpoints
app.UseCors(); app.UseCors();
// FastEndpoints et Swagger app.UseAuthentication();
app.UseAuthorization();
app.UseFastEndpoints(options => app.UseFastEndpoints(options =>
{ {
options.Endpoints.RoutePrefix = "API"; options.Endpoints.RoutePrefix = "API";
options.Endpoints.ShortNames = true; options.Endpoints.ShortNames = true;
}).UseSwaggerGen(); }).UseSwaggerGen();
// app.UseAuthorization();
// app.UseAuthentication();
app.Run(); app.Run();

View File

@@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RollForward>Major</RollForward>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
@@ -14,16 +15,12 @@
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.19" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.19" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.20" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.20"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.20">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.20" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.20" />
<PackageReference Include="PasswordGenerator" Version="2.1.0" /> <PackageReference Include="PasswordGenerator" Version="2.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Migrations\" />
</ItemGroup>
</Project> </Project>

View File

@@ -6,6 +6,8 @@ namespace PyroFetes;
public class PyroFetesDbContext : DbContext public class PyroFetesDbContext : DbContext
{ {
// Entities // Entities
public DbSet<Availability> Availabilities { get; set; } public DbSet<Availability> Availabilities { get; set; }
public DbSet<Brand> Brands { get; set; } public DbSet<Brand> Brands { get; set; }
@@ -55,9 +57,9 @@ public class PyroFetesDbContext : DbContext
{ {
string connectionString = string connectionString =
"Server=romaric-thibault.fr;" + "Server=romaric-thibault.fr;" +
"Database=PyroFetes;" + "Database=PyroMana;" +
"User Id=pyrofetes;" + "User Id=matheo;" +
"Password=Crablike8-Fringe-Swimmable;" + "Password=Onto9-Cage-Afflicted;" +
"TrustServerCertificate=true;"; "TrustServerCertificate=true;";
optionsBuilder.UseSqlServer(connectionString); optionsBuilder.UseSqlServer(connectionString);

281
PyroFetes/README.md Normal file
View File

@@ -0,0 +1,281 @@
# 🎆 Pyrofêtes — Backend
> API REST de gestion des entrepôts et produits pyrotechniques, développée avec **C#**, **ASP.NET Core** et **FastEndpoints**.
---
## 🛠️ Stack technique
| Technologie | Rôle |
|---|---|
| [C# / .NET 8](https://dotnet.microsoft.com/) | Langage et runtime principal |
| [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/) | Framework web |
| [FastEndpoints](https://fast-endpoints.com/) | Bibliothèque d'endpoints REST performants |
| Entity Framework Core | ORM pour l'accès à la base de données |
| SQL Server | Base de données relationnelle |
| JWT Bearer | Authentification par token |
---
## 📁 Structure du projet
```
Pyrofetes.API/
├── Models/ # Entités de la base de données (EF Core)
├── DTO/ # Objets de transfert de données
│ ├── Brand/
│ │ ├── Request/
│ │ │ ├── CreateBrandDto.cs
│ │ │ └── UpdateBrandDto.cs
│ │ └── Response/
│ │ └── GetBrandDto.cs
│ ├── Classification/
│ ├── Color/
│ ├── Effect/
│ ├── Login/
│ ├── Material/
│ ├── Movement/
│ ├── Product/
│ ├── ProductCategory/
│ ├── ProductColor/
│ ├── ProductEffect/
│ ├── ProductSupplierPrice/
│ ├── ProductWarehouse/
│ ├── Refrresh/ # Token refresh
│ ├── Supplier/
│ └── Warehouse/
├── Endpoints/ # Endpoints FastEndpoints (CRUD par domaine)
│ ├── Brand/
│ │ ├── CreateBrandEndpoint.cs
│ │ ├── DeleteBrandEndpoint.cs
│ │ ├── GetAllBrandsEndpoint.cs
│ │ ├── GetBrandEndpoint.cs
│ │ └── UpdateBrandEndpoint.cs
│ ├── Classification/
│ ├── Color/
│ ├── Effect/
│ ├── Login/
│ ├── Material/
│ ├── Movement/
│ ├── Product/
│ ├── ProductCategory/
│ ├── Refresh/ # Refresh token JWT
│ ├── Supplier/
│ └── Warehouse/
├── Data/ # Contexte Entity Framework Core
│ └── AppDbContext.cs
├── Migrations/ # Migrations EF Core
├── appsettings.json # Configuration production
├── appsettings.Development.json # Configuration développement
└── Program.cs # Point d'entrée et configuration des services
```
---
## 🚀 Installation et lancement
### Prérequis
- [.NET SDK](https://dotnet.microsoft.com/download) >= 8.0
- SQL Server en cours d'exécution (local ou distant)
- [Entity Framework CLI](https://learn.microsoft.com/en-us/ef/core/cli/dotnet)
```bash
dotnet tool install --global dotnet-ef
```
### Installation
```bash
# Cloner le dépôt
git clone https://gitea.btssio-poitiers.fr/reignem/PyroFetes-Sujet1.git
cd pyrofetes-backend
```
### Migrations et base de données
```bash
# Créer la base de données et appliquer les migrations
dotnet ef database update
```
### Lancement
```bash
dotnet run
```
L'API sera disponible sur [http://localhost:5000](http://localhost:5000)
La documentation Swagger sera accessible sur [http://localhost:5000/swagger](http://localhost:5000/swagger)
---
## ⚙️ Configuration FastEndpoints + CORS
Dans `Program.cs` :
```csharp
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAngular", policy =>
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod());
});
builder.Services.AddFastEndpoints();
builder.Services.AddJWTBearerAuth("votre-cle-secrete");
var app = builder.Build();
app.UseCors("AllowAngular");
app.UseAuthentication();
app.UseAuthorization();
app.UseFastEndpoints();
app.Run();
```
### Anatomie d'un endpoint
Chaque endpoint suit le pattern **Request → Handler → Response** de FastEndpoints :
```csharp
// Endpoints/Brand/GetAllBrandsEndpoint.cs
public class GetAllBrandsEndpoint : EndpointWithoutRequest<List<GetBrandDto>>
{
private readonly AppDbContext _db;
public GetAllBrandsEndpoint(AppDbContext db) => _db = db;
public override void Configure()
{
Get("/api/brands");
Roles("Admin", "User");
}
public override async Task HandleAsync(CancellationToken ct)
{
var brands = await _db.Brands
.Select(b => new GetBrandDto { Id = b.Id, Name = b.Name })
.ToListAsync(ct);
await SendOkAsync(brands, ct);
}
}
```
### Pattern DTO
```
DTO/
└── Brand/
├── Request/
│ ├── CreateBrandDto.cs ← payload entrant (POST)
│ └── UpdateBrandDto.cs ← payload entrant (PUT)
└── Response/
└── GetBrandDto.cs ← payload sortant (GET)
```
---
## 📡 Endpoints API
> Tous les endpoints (sauf `/api/auth/*` et `/api/auth/refresh`) requièrent un header `Authorization: Bearer <token>`.
### Authentification
| Méthode | Route | Description | Auth |
|---|---|---|---|
| `POST` | `/api/auth/login` | Connexion, retourne JWT + refresh token | ❌ |
| `POST` | `/api/auth/refresh` | Renouvellement du JWT via refresh token | ❌ |
### Marques (Brand)
| Méthode | Route | Description |
|---|---|---|
| `GET` | `/api/brands` | Liste toutes les marques |
| `GET` | `/api/brands/{id}` | Détail d'une marque |
| `POST` | `/api/brands` | Créer une marque |
| `PUT` | `/api/brands/{id}` | Modifier une marque |
| `DELETE` | `/api/brands/{id}` | Supprimer une marque |
### Produits (Product)
| Méthode | Route | Description |
|---|---|---|
| `GET` | `/api/products` | Liste tous les produits |
| `GET` | `/api/products/{id}` | Détail d'un produit |
| `POST` | `/api/products` | Créer un produit |
| `PUT` | `/api/products/{id}` | Modifier un produit |
| `DELETE` | `/api/products/{id}` | Supprimer un produit |
### Entrepôts (Warehouse)
| Méthode | Route | Description |
|---|---|---|
| `GET` | `/api/warehouses` | Liste tous les entrepôts |
| `GET` | `/api/warehouses/{id}` | Détail d'un entrepôt |
| `POST` | `/api/warehouses` | Créer un entrepôt |
| `PUT` | `/api/warehouses/{id}` | Modifier un entrepôt |
| `DELETE` | `/api/warehouses/{id}` | Supprimer un entrepôt |
### Fournisseurs (Supplier)
| Méthode | Route | Description |
|---|---|---|
| `GET` | `/api/suppliers` | Liste tous les fournisseurs |
| `GET` | `/api/suppliers/{id}` | Détail d'un fournisseur |
| `POST` | `/api/suppliers` | Créer un fournisseur |
| `PUT` | `/api/suppliers/{id}` | Modifier un fournisseur |
| `DELETE` | `/api/suppliers/{id}` | Supprimer un fournisseur |
### Autres domaines disponibles
| Domaine | Préfixe route |
|---|---|
| Classifications | `/api/classifications` |
| Couleurs | `/api/colors` |
| Effets | `/api/effects` |
| Matériaux | `/api/materials` |
| Mouvements de stock | `/api/movements` |
| Catégories produit | `/api/product-categories` |
| Couleurs produit | `/api/product-colors` |
| Effets produit | `/api/product-effects` |
| Prix fournisseur produit | `/api/product-supplier-prices` |
---
## 🔐 Authentification JWT
### Flux d'authentification
```
Client (Angular) API (FastEndpoints)
│ │
│── POST /api/auth/login ──▶│
│ │ Vérifie credentials
│◀── { token, refreshToken }│
│ │
│── GET /api/... ──────────▶│
│ Authorization: Bearer │ Valide le JWT
│◀── 200 OK ────────────────│
│ │
│── POST /api/auth/refresh ▶│ (token expiré)
│◀── { newToken } ──────────│
```
### Header requis
```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
---
## 📄 Licence
Ce projet est à usage interne — © Pyrofêtes. Tous droits réservés.

303
README.md
View File

@@ -1,50 +1,281 @@
# Gestionnaire de Stocks et Commandes # 🎆 Pyrofêtes — Backend
Cette application web permet de **suivre les stocks**, **automatiser les commandes fournisseurs** et **gérer le cycle complet dapprovisionnement**. > API REST de gestion des entrepôts et produits pyrotechniques, développée avec **C#**, **ASP.NET Core** et **FastEndpoints**.
Elle est conçue pour simplifier le travail des entreprises en offrant une vue en temps réel sur les produits, leurs fournisseurs et létat des livraisons.
--- ---
## ✨ Fonctionnalités principales ## 🛠️ Stack technique
### 1⃣ Suivi et réapprovisionnement des stocks | Technologie | Rôle |
- Définissez un **niveau minimal de stock** pour chaque produit. |---|---|
- Surveillez les **niveaux en temps réel** grâce à une interface claire. | [C# / .NET 8](https://dotnet.microsoft.com/) | Langage et runtime principal |
- Lorsquun produit atteint ou descend sous son seuil minimal, le système **génère automatiquement un bon de commande** pour le réapprovisionner. | [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/) | Framework web |
| [FastEndpoints](https://fast-endpoints.com/) | Bibliothèque d'endpoints REST performants |
### 2⃣ Gestion des fournisseurs | Entity Framework Core | ORM pour l'accès à la base de données |
- Enregistrez les informations complètes des fournisseurs : nom, adresse, coordonnées, produits fournis, délais de livraison. | SQL Server | Base de données relationnelle |
- **Associez un ou plusieurs fournisseurs** à chaque produit. | JWT Bearer | Authentification par token |
- Lorsquun bon de commande est créé, le système **propose automatiquement les fournisseurs appropriés**.
### 3⃣ Devis et bons de commande
- Créez des **devis personnalisés** : sélection des produits, quantités, prix, ajout dun logo, message ou conditions de vente.
- **Imprimez ou exportez** vos devis au format PDF.
- Générez des **bons de commande** en quelques clics, avec personnalisation (logo, conditions dachat) et exportation en PDF.
### 4⃣ Suivi des livraisons
- **Transformez un bon de commande en bon de livraison** dès lexpédition des produits par le fournisseur.
- Enregistrez toutes les informations importantes : date dexpédition, transporteur, numéro de suivi, date prévue et date effective de livraison.
- Recevez des **alertes en cas de retard**.
- Gérez la **réception des produits** et vérifiez leur conformité.
--- ---
## 🗂️ Livrables prévus ## 📁 Structure du projet
- **Modèle de données** : diagramme de classes commun à tous les groupes.
- **Interface utilisateur** : maquettes ou prototypes interactifs. ```
- **Code source commenté** pour une meilleure compréhension. Pyrofetes.API/
- **Documentation technique** : description des fonctionnalités, architecture de lapplication et API.
├── Models/ # Entités de la base de données (EF Core)
├── DTO/ # Objets de transfert de données
│ ├── Brand/
│ │ ├── Request/
│ │ │ ├── CreateBrandDto.cs
│ │ │ └── UpdateBrandDto.cs
│ │ └── Response/
│ │ └── GetBrandDto.cs
│ ├── Classification/
│ ├── Color/
│ ├── Effect/
│ ├── Login/
│ ├── Material/
│ ├── Movement/
│ ├── Product/
│ ├── ProductCategory/
│ ├── ProductColor/
│ ├── ProductEffect/
│ ├── ProductSupplierPrice/
│ ├── ProductWarehouse/
│ ├── Refrresh/ # Token refresh
│ ├── Supplier/
│ └── Warehouse/
├── Endpoints/ # Endpoints FastEndpoints (CRUD par domaine)
│ ├── Brand/
│ │ ├── CreateBrandEndpoint.cs
│ │ ├── DeleteBrandEndpoint.cs
│ │ ├── GetAllBrandsEndpoint.cs
│ │ ├── GetBrandEndpoint.cs
│ │ └── UpdateBrandEndpoint.cs
│ ├── Classification/
│ ├── Color/
│ ├── Effect/
│ ├── Login/
│ ├── Material/
│ ├── Movement/
│ ├── Product/
│ ├── ProductCategory/
│ ├── Refresh/ # Refresh token JWT
│ ├── Supplier/
│ └── Warehouse/
├── Data/ # Contexte Entity Framework Core
│ └── AppDbContext.cs
├── Migrations/ # Migrations EF Core
├── appsettings.json # Configuration production
├── appsettings.Development.json # Configuration développement
└── Program.cs # Point d'entrée et configuration des services
```
--- ---
## 👥 Équipe ## 🚀 Installation et lancement
- **Mathys**
- **Enzo** ### Prérequis
- **Cristiano**
- **Arsène** - [.NET SDK](https://dotnet.microsoft.com/download) >= 8.0
- SQL Server en cours d'exécution (local ou distant)
- [Entity Framework CLI](https://learn.microsoft.com/en-us/ef/core/cli/dotnet)
```bash
dotnet tool install --global dotnet-ef
```
### Installation
```bash
# Cloner le dépôt
git clone https://gitea.btssio-poitiers.fr/reignem/PyroFetes-Sujet1.git
cd pyrofetes-backend
```
### Migrations et base de données
```bash
# Créer la base de données et appliquer les migrations
dotnet ef database update
```
### Lancement
```bash
dotnet run
```
L'API sera disponible sur [http://localhost:5000](http://localhost:5000)
La documentation Swagger sera accessible sur [http://localhost:5000/swagger](http://localhost:5000/swagger)
--- ---
## 🚀 Objectif ## ⚙️ Configuration FastEndpoints + CORS
Fournir un outil complet pour automatiser la gestion des stocks et des commandes, réduisant les erreurs humaines, améliorant le suivi des livraisons et facilitant la communication avec les fournisseurs.
Dans `Program.cs` :
```csharp
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAngular", policy =>
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod());
});
builder.Services.AddFastEndpoints();
builder.Services.AddJWTBearerAuth("votre-cle-secrete");
var app = builder.Build();
app.UseCors("AllowAngular");
app.UseAuthentication();
app.UseAuthorization();
app.UseFastEndpoints();
app.Run();
```
### Anatomie d'un endpoint
Chaque endpoint suit le pattern **Request → Handler → Response** de FastEndpoints :
```csharp
// Endpoints/Brand/GetAllBrandsEndpoint.cs
public class GetAllBrandsEndpoint : EndpointWithoutRequest<List<GetBrandDto>>
{
private readonly AppDbContext _db;
public GetAllBrandsEndpoint(AppDbContext db) => _db = db;
public override void Configure()
{
Get("/api/brands");
Roles("Admin", "User");
}
public override async Task HandleAsync(CancellationToken ct)
{
var brands = await _db.Brands
.Select(b => new GetBrandDto { Id = b.Id, Name = b.Name })
.ToListAsync(ct);
await SendOkAsync(brands, ct);
}
}
```
### Pattern DTO
```
DTO/
└── Brand/
├── Request/
│ ├── CreateBrandDto.cs ← payload entrant (POST)
│ └── UpdateBrandDto.cs ← payload entrant (PUT)
└── Response/
└── GetBrandDto.cs ← payload sortant (GET)
```
---
## 📡 Endpoints API
> Tous les endpoints (sauf `/api/auth/*` et `/api/auth/refresh`) requièrent un header `Authorization: Bearer <token>`.
### Authentification
| Méthode | Route | Description | Auth |
|---|---|---|---|
| `POST` | `/api/auth/login` | Connexion, retourne JWT + refresh token | ❌ |
| `POST` | `/api/auth/refresh` | Renouvellement du JWT via refresh token | ❌ |
### Marques (Brand)
| Méthode | Route | Description |
|---|---|---|
| `GET` | `/api/brands` | Liste toutes les marques |
| `GET` | `/api/brands/{id}` | Détail d'une marque |
| `POST` | `/api/brands` | Créer une marque |
| `PUT` | `/api/brands/{id}` | Modifier une marque |
| `DELETE` | `/api/brands/{id}` | Supprimer une marque |
### Produits (Product)
| Méthode | Route | Description |
|---|---|---|
| `GET` | `/api/products` | Liste tous les produits |
| `GET` | `/api/products/{id}` | Détail d'un produit |
| `POST` | `/api/products` | Créer un produit |
| `PUT` | `/api/products/{id}` | Modifier un produit |
| `DELETE` | `/api/products/{id}` | Supprimer un produit |
### Entrepôts (Warehouse)
| Méthode | Route | Description |
|---|---|---|
| `GET` | `/api/warehouses` | Liste tous les entrepôts |
| `GET` | `/api/warehouses/{id}` | Détail d'un entrepôt |
| `POST` | `/api/warehouses` | Créer un entrepôt |
| `PUT` | `/api/warehouses/{id}` | Modifier un entrepôt |
| `DELETE` | `/api/warehouses/{id}` | Supprimer un entrepôt |
### Fournisseurs (Supplier)
| Méthode | Route | Description |
|---|---|---|
| `GET` | `/api/suppliers` | Liste tous les fournisseurs |
| `GET` | `/api/suppliers/{id}` | Détail d'un fournisseur |
| `POST` | `/api/suppliers` | Créer un fournisseur |
| `PUT` | `/api/suppliers/{id}` | Modifier un fournisseur |
| `DELETE` | `/api/suppliers/{id}` | Supprimer un fournisseur |
### Autres domaines disponibles
| Domaine | Préfixe route |
|---|---|
| Classifications | `/api/classifications` |
| Couleurs | `/api/colors` |
| Effets | `/api/effects` |
| Matériaux | `/api/materials` |
| Mouvements de stock | `/api/movements` |
| Catégories produit | `/api/product-categories` |
| Couleurs produit | `/api/product-colors` |
| Effets produit | `/api/product-effects` |
| Prix fournisseur produit | `/api/product-supplier-prices` |
---
## 🔐 Authentification JWT
### Flux d'authentification
```
Client (Angular) API (FastEndpoints)
│ │
│── POST /api/auth/login ──▶│
│ │ Vérifie credentials
│◀── { token, refreshToken }│
│ │
│── GET /api/... ──────────▶│
│ Authorization: Bearer │ Valide le JWT
│◀── 200 OK ────────────────│
│ │
│── POST /api/auth/refresh ▶│ (token expiré)
│◀── { newToken } ──────────│
```
### Header requis
```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
---
## 📄 Licence
Ce projet est à usage interne — © Pyrofêtes. Tous droits réservés.

18
package-lock.json generated Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "PyroFete",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"zone.js": "^0.16.0"
}
},
"node_modules/zone.js": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.16.0.tgz",
"integrity": "sha512-LqLPpIQANebrlxY6jKcYKdgN5DTXyyHAKnnWWjE5pPfEQ4n7j5zn7mOEEpwNZVKGqx3kKKmvplEmoBrvpgROTA==",
"license": "MIT"
}
}
}

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"zone.js": "^0.16.0"
}
}