Vente Crud +Auth
Authentication with JWT in Spring Boot, Angular, and MySQL
id
: Unique identifier for the sale.idClient
: Reference to the client making the sale.idProduit
: Reference to the product being sold.date
: Date of the sale.quantite
: Quantity of the product sold.
Backend (Spring Boot)
Vente Entity
package com.example.demo.model;
import jakarta.persistence.*;
import java.util.Date;
@Entity
public class Vente {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "id_client", nullable = false)
private Client client;
@ManyToOne
@JoinColumn(name = "id_produit", nullable = false)
private Produit produit;
@Temporal(TemporalType.DATE)
private Date date;
private int quantite;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
public Produit getProduit() {
return produit;
}
public void setProduit(Produit produit) {
this.produit = produit;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public int getQuantite() {
return quantite;
}
public void setQuantite(int quantite) {
this.quantite = quantite;
}
}
VenteRepository
Create a repository interface for the Vente entity to handle database interactions.
package com.example.demo.repository;
import com.example.demo.model.Vente;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface VenteRepository extends JpaRepository<Vente, Long> {
List<Vente> findByClientId(Long clientId); // Find sales by client ID
}
VenteController
Create a controller to manage CRUD operations for the Vente entity. We will use annotations such as @GetMapping, @PostMapping, @PutMapping, and @DeleteMapping for handling different HTTP requests.
package com.example.demo.controller;
import com.example.demo.model.Vente;
import com.example.demo.repository.VenteRepository;
import com.example.demo.repository.ClientRepository;
import com.example.demo.repository.ProduitRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/api/ventes")
public class VenteController {
@Autowired
private VenteRepository venteRepository;
@Autowired
private ClientRepository clientRepository;
@Autowired
private ProduitRepository produitRepository;
// Get all sales for an authenticated client
@GetMapping
public List<Vente> getVentesByClient(@AuthenticationPrincipal Client client) {
return venteRepository.findByClientId(client.getId());
}
// Create a new sale
@PostMapping
public ResponseEntity<Vente> createVente(@RequestBody Vente vente, @AuthenticationPrincipal Client client) {
vente.setClient(client);
vente.setDate(new Date());
return ResponseEntity.ok(venteRepository.save(vente));
}
// Get a specific sale by ID
@GetMapping("/{id}")
public ResponseEntity<Vente> getVenteById(@PathVariable Long id, @AuthenticationPrincipal Client client) {
return venteRepository.findById(id)
.filter(vente -> vente.getClient().getId().equals(client.getId()))
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// Update a sale
@PutMapping("/{id}")
public ResponseEntity<Vente> updateVente(@PathVariable Long id, @RequestBody Vente venteDetails, @AuthenticationPrincipal Client client) {
return venteRepository.findById(id)
.filter(vente -> vente.getClient().getId().equals(client.getId()))
.map(vente -> {
vente.setProduit(venteDetails.getProduit());
vente.setQuantite(venteDetails.getQuantite());
vente.setDate(venteDetails.getDate());
return ResponseEntity.ok(venteRepository.save(vente));
})
.orElse(ResponseEntity.notFound().build());
}
// Delete a sale
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteVente(@PathVariable Long id, @AuthenticationPrincipal Client client) {
return venteRepository.findById(id)
.filter(vente -> vente.getClient().getId().equals(client.getId()))
.map(vente -> {
venteRepository.delete(vente);
return ResponseEntity.noContent().build();
})
.orElse(ResponseEntity.notFound().build());
}
}
Security Considerations
Ensure that only authenticated users can access the VenteController by configuring Spring Security appropriately. We have already integrated JWT-based authentication in earlier steps. The @AuthenticationPrincipal annotation will inject the authenticated Client into the controller methods, ensuring that users can only access their own sales data.Frontend (Angular)
VenteService
Create an Angular service to handle HTTP requests to the backend.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Vente } from './vente.model';
@Injectable({
providedIn: 'root'
})
export class VenteService {
private apiUrl = 'http://localhost:8080/api/ventes';
constructor(private http: HttpClient) { }
// Get all sales for the authenticated client
getVentes(): Observable<Vente[]> {
return this.http.get<Vente[]>(this.apiUrl);
}
// Create a new sale
createVente(vente: Vente): Observable<Vente> {
return this.http.post<Vente>(this.apiUrl, vente);
}
// Get a sale by ID
getVenteById(id: number): Observable<Vente> {
return this.http.get<Vente>(`${this.apiUrl}/${id}`);
}
// Update a sale
updateVente(id: number, vente: Vente): Observable<Vente> {
return this.http.put<Vente>(`${this.apiUrl}/${id}`, vente);
}
// Delete a sale
deleteVente(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
VenteComponent for Displaying Sales
display the list of sales and provide buttons for creating, updating, and deleting sales.
import { Component, OnInit } from '@angular/core';
import { VenteService } from '../vente.service';
import { Vente } from '../vente.model';
@Component({
selector: 'app-vente',
templateUrl: './vente.component.html',
styleUrls: ['./vente.component.css']
})
export class VenteComponent implements OnInit {
ventes: Vente[] = [];
constructor(private venteService: VenteService) { }
ngOnInit(): void {
this.loadVentes();
}
// Load all sales
loadVentes() {
this.venteService.getVentes().subscribe(
(data: Vente[]) => {
this.ventes = data;
},
error => {
console.error('Error loading sales', error);
}
);
}
// Delete a sale
deleteVente(id: number) {
this.venteService.deleteVente(id).subscribe(
() => {
this.loadVentes(); // Reload sales after deletion
},
error => {
console.error('Error deleting sale', error);
}
);
}
}
VenteComponent HTML
The HTML will loop through the sales and display them in a table format. We will also provide buttons for updating and deleting each sale.
<div class="container">
<h2>Sales</h2>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Product</th>
<th>Quantity</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let vente of ventes">
<td>{{ vente.id }}</td>
<td>{{ vente.produit.nom }}</td>
<td>{{ vente.quantite }}</td>
<td>{{ vente.date | date }}</td>
<td>
<button (click)="editVente(vente.id)" class="btn btn-primary">Edit</button>
<button (click)="deleteVente(vente.id)" class="btn btn
<button (click)="deleteVente(vente.id)" class="btn btn-danger">Delete</button>
</td>
</tr>
</tbody>
</table>
<!-- Button to add a new sale -->
<button class="btn btn-success" (click)="createVente()">Add New Sale</button>
</div>
Add/Edit Vente Component Logic
import { Component, OnInit } from '@angular/core';
import { VenteService } from '../vente.service';
import { Vente } from '../vente.model';
import { Router, ActivatedRoute } from '@angular/router';
import { ProduitService } from '../produit.service'; // Assuming we have a service to get products
@Component({
selector: 'app-vente-form',
templateUrl: './vente-form.component.html',
styleUrls: ['./vente-form.component.css']
})
export class VenteFormComponent implements OnInit {
vente: Vente = new Vente(); // The sale being created/edited
produits: any[] = []; // List of products for selection in the form
editMode: boolean = false; // Flag to distinguish between add and edit modes
constructor(
private venteService: VenteService,
private produitService: ProduitService,
private router: Router,
private route: ActivatedRoute
) { }
ngOnInit(): void {
this.loadProduits(); // Load available products for the sale form
// Check if we're in edit mode (if there's an ID in the route params)
const id = this.route.snapshot.paramMap.get('id');
if (id) {
this.editMode = true;
this.venteService.getVenteById(Number(id)).subscribe(
(data: Vente) => {
this.vente = data;
},
error => {
console.error('Error loading sale', error);
}
);
}
}
// Load available products to populate the dropdown
loadProduits() {
this.produitService.getProduits().subscribe(
(data: any[]) => {
this.produits = data;
},
error => {
console.error('Error loading products', error);
}
);
}
// Save the sale (either creating or updating)
saveVente() {
if (this.editMode) {
this.venteService.updateVente(this.vente.id, this.vente).subscribe(
() => {
this.router.navigate(['/ventes']); // Redirect to sales list after successful save
},
error => {
console.error('Error updating sale', error);
}
);
} else {
this.venteService.createVente(this.vente).subscribe(
() => {
this.router.navigate(['/ventes']); // Redirect to sales list after successful save
},
error => {
console.error('Error creating sale', error);
}
);
}
}
}
VenteFormComponent HTML
<div class="container">
<h2 *ngIf="!editMode">Add New Sale</h2>
<h2 *ngIf="editMode">Edit Sale</h2>
<form (ngSubmit)="saveVente()">
<div class="form-group">
<label for="produit">Product</label>
<select id="produit" [(ngModel)]="vente.produit" name="produit" class="form-control" required>
<option *ngFor="let produit of produits" [ngValue]="produit">{{ produit.nom }}</option>
</select>
</div>
<div class="form-group">
<label for="quantite">Quantity</label>
<input type="number" id="quantite" [(ngModel)]="vente.quantite" name="quantite" class="form-control" required>
</div>
<div class="form-group">
<label for="date">Date</label>
<input type="date" id="date" [(ngModel)]="vente.date" name="date" class="form-control" required>
</div>
<button type="submit" class="btn btn-success">{{ editMode ? 'Update' : 'Create' }}</button>
<button type="button" class="btn btn-secondary" (click)="router.navigate(['/ventes'])">Cancel</button>
</form>
</div>
Routing and Navigation
update the app-routing.module.ts to add routes for the VenteComponent and VenteFormComponent.Update Routes:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { VenteComponent } from './vente/vente.component';
import { VenteFormComponent } from './vente-form/vente-form.component';
import { AuthGuard } from './auth.guard'; // Assuming an AuthGuard is implemented
const routes: Routes = [
{ path: 'ventes', component: VenteComponent, canActivate: [AuthGuard] },
{ path: 'ventes/new', component: VenteFormComponent, canActivate: [AuthGuard] },
{ path: 'ventes/edit/:id', component: VenteFormComponent, canActivate: [AuthGuard] },
{ path: '', redirectTo: '/ventes', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Navigation
<div class="container">
<h2>Sales</h2>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Product</th>
<th>Quantity</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let vente of ventes">
<td>{{ vente.id }}</td>
<td>{{ vente.produit.nom }}</td>
<td>{{ vente.quantite }}</td>
<td>{{ vente.date | date }}</td>
<td>
<button (click)="router.navigate(['/ventes/edit', vente.id])" class="btn btn-primary">Edit</button>
<button (click)="deleteVente(vente.id)" class="btn btn-danger">Delete</button>
</td>
</tr>
</tbody>
</table>
<button class="btn btn-success" (click)="router.navigate(['/ventes/new'])">Add New Sale</button>
</div>