Add project files.
This commit is contained in:
52
Controllers/AuthController.cs
Normal file
52
Controllers/AuthController.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using LogisticsApp.Server.Services;
|
||||
using LogisticsApp.Server.DTOs;
|
||||
|
||||
namespace LogisticsApp.Server.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public AuthController(IAuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<ApiResponse<LoginResponse>>> Login(LoginRequest request)
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Username and password are required"));
|
||||
}
|
||||
|
||||
var result = await _authService.LoginAsync(request);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return Unauthorized(new ApiResponse<string>(false, "Invalid username or password"));
|
||||
}
|
||||
|
||||
Response.Cookies.Append("auth_token", result.Token, new CookieOptions
|
||||
{
|
||||
HttpOnly = false,
|
||||
Secure = false,
|
||||
SameSite = SameSiteMode.Strict,
|
||||
Expires = result.Expires
|
||||
});
|
||||
|
||||
return Ok(new ApiResponse<LoginResponse>(true, "Login successful", result));
|
||||
}
|
||||
|
||||
[HttpPost("logout")]
|
||||
public ActionResult<ApiResponse<string>> Logout()
|
||||
{
|
||||
Response.Cookies.Delete("auth_token");
|
||||
return Ok(new ApiResponse<string>(true, "Logout successful"));
|
||||
}
|
||||
}
|
||||
}
|
||||
205
Controllers/OrdersController.cs
Normal file
205
Controllers/OrdersController.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using LogisticsApp.Server.Data;
|
||||
using LogisticsApp.Server.DTOs;
|
||||
using LogisticsApp.Server.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LogisticsApp.Server.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class OrdersController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public OrdersController(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/orders
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<ApiResponse<PagedResult<Order>>>> GetOrders([FromQuery] OrderFilters filters)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _context.Orders.AsQueryable();
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(filters.ClientName))
|
||||
{
|
||||
query = query.Where(o => o.ClientName.ToLower().Contains(filters.ClientName.ToLower()));
|
||||
}
|
||||
|
||||
if (filters.MinCost.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.OrderCost >= filters.MinCost.Value);
|
||||
}
|
||||
|
||||
if (filters.MaxCost.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.OrderCost <= filters.MaxCost.Value);
|
||||
}
|
||||
|
||||
if (filters.OrderDate.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.OrderDate.Date == filters.OrderDate.Value.Date);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(filters.Status))
|
||||
{
|
||||
query = query.Where(o => o.Status == filters.Status);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
|
||||
var orders = await query
|
||||
.OrderByDescending(o => o.OrderDate)
|
||||
.Skip((filters.Page - 1) * filters.PageSize)
|
||||
.Take(filters.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var result = new PagedResult<Order>
|
||||
{
|
||||
Items = orders,
|
||||
TotalCount = totalCount,
|
||||
Page = filters.Page,
|
||||
PageSize = filters.PageSize,
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)filters.PageSize)
|
||||
};
|
||||
|
||||
return Ok(new ApiResponse<PagedResult<Order>>(true, "Orders retrieved successfully", result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/orders/5
|
||||
[Authorize]
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<Order>>> GetOrder(int id)
|
||||
{
|
||||
var order = await _context.Orders.FindAsync(id);
|
||||
|
||||
if (order == null)
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Order not found"));
|
||||
}
|
||||
|
||||
return Ok(new ApiResponse<Order>(true, "Order retrieved successfully", order));
|
||||
}
|
||||
|
||||
// PUT: api/orders/5
|
||||
[Authorize]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<Order>>> PutOrder(int id, Order order)
|
||||
{
|
||||
if (id != order.Id)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "ID mismatch"));
|
||||
}
|
||||
|
||||
|
||||
var validStatuses = new[] { "pending", "in_progress", "completed", "cancelled" };
|
||||
if (!validStatuses.Contains(order.Status))
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Invalid status"));
|
||||
}
|
||||
|
||||
|
||||
if (order.OrderCost <= 0)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Order cost must be positive"));
|
||||
}
|
||||
|
||||
|
||||
_context.Entry(order).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!OrderExists(id))
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Order not found"));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new ApiResponse<Order>(true, "Order updated successfully", order));
|
||||
}
|
||||
|
||||
// POST: api/orders
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ApiResponse<Order>>> PostOrder(Order order)
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(order.ClientName) || order.ClientName.Length > 200)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Client name is required and must be less than 200 characters"));
|
||||
}
|
||||
|
||||
if (order.OrderCost <= 0)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Order cost must be positive"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(order.Address) || order.Address.Length > 500)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Address is required and must be less than 500 characters"));
|
||||
}
|
||||
|
||||
var validStatuses = new[] { "pending", "in_progress", "completed", "cancelled" };
|
||||
if (!validStatuses.Contains(order.Status))
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Invalid status"));
|
||||
}
|
||||
|
||||
_context.Orders.Add(order);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetOrder", new { id = order.Id },
|
||||
new ApiResponse<Order>(true, "Order created successfully", order));
|
||||
}
|
||||
|
||||
// DELETE: api/orders/5
|
||||
[Authorize]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<Order>>> DeleteOrder(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var order = await _context.Orders.FindAsync(id);
|
||||
if (order == null)
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Order not found"));
|
||||
}
|
||||
|
||||
_context.Orders.Remove(order);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(new ApiResponse<string>(true, "Order deleted successfully"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private bool OrderExists(int id)
|
||||
{
|
||||
return _context.Orders.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
214
Controllers/VehiclesController.cs
Normal file
214
Controllers/VehiclesController.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
|
||||
using LogisticsApp.Server.Data;
|
||||
using LogisticsApp.Server.DTOs;
|
||||
using LogisticsApp.Server.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LogisticsApp.Server.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class VehiclesController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public VehiclesController(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/vehicles
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<ApiResponse<PagedResult<Vehicle>>>> GetVehicles([FromQuery] VehicleFilters filters)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _context.Vehicles.AsQueryable();
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(filters.DriverName))
|
||||
{
|
||||
query = query.Where(v => v.DriverName.ToLower().Contains(filters.DriverName.ToLower()));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(filters.VehicleType))
|
||||
{
|
||||
query = query.Where(v => v.VehicleType.ToLower().Contains(filters.VehicleType.ToLower()));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(filters.LicensePlate))
|
||||
{
|
||||
query = query.Where(v => v.LicensePlate.ToLower().Contains(filters.LicensePlate.ToLower()));
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var vehicles = await query
|
||||
.OrderBy(v => v.DriverName)
|
||||
.Skip((filters.Page - 1) * filters.PageSize)
|
||||
.Take(filters.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var result = new PagedResult<Vehicle>
|
||||
{
|
||||
Items = vehicles,
|
||||
TotalCount = totalCount,
|
||||
Page = filters.Page,
|
||||
PageSize = filters.PageSize,
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)filters.PageSize)
|
||||
};
|
||||
|
||||
return Ok(new ApiResponse<PagedResult<Vehicle>>(true, "Vehicles retrieved successfully", result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/vehicles/5
|
||||
[Authorize]
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<Vehicle>>> GetVehicle(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var vehicle = await _context.Vehicles
|
||||
.FirstOrDefaultAsync(v => v.Id == id);
|
||||
|
||||
if (vehicle == null)
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Vehicle not found"));
|
||||
}
|
||||
|
||||
return Ok(new ApiResponse<Vehicle>(true, "Vehicle retrieved successfully", vehicle));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// POST: api/vehicles
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ApiResponse<Vehicle>>> PostVehicle(Vehicle vehicle)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(vehicle.DriverName) || vehicle.DriverName.Length > 100)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Driver name is required and must be less than 100 characters"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(vehicle.VehicleType) || vehicle.VehicleType.Length > 50)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Vehicle type is required and must be less than 50 characters"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(vehicle.LicensePlate) || vehicle.LicensePlate.Length > 20)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "License plate is required and must be less than 20 characters"));
|
||||
}
|
||||
|
||||
vehicle.CreatedAt = DateTime.UtcNow;
|
||||
_context.Vehicles.Add(vehicle);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetVehicle", new { id = vehicle.Id },
|
||||
new ApiResponse<Vehicle>(true, "Vehicle created successfully", vehicle));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// PUT: api/vehicles/5
|
||||
[Authorize]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<Vehicle>>> PutVehicle(int id, Vehicle vehicle)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (id != vehicle.Id)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "ID mismatch"));
|
||||
}
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(vehicle.DriverName) || vehicle.DriverName.Length > 100)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Driver name is required and must be less than 100 characters"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(vehicle.VehicleType) || vehicle.VehicleType.Length > 50)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Vehicle type is required and must be less than 50 characters"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(vehicle.LicensePlate) || vehicle.LicensePlate.Length > 20)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "License plate is required and must be less than 20 characters"));
|
||||
}
|
||||
|
||||
_context.Entry(vehicle).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!VehicleExists(id))
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Vehicle not found"));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new ApiResponse<Vehicle>(true, "Vehicle updated successfully", vehicle));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE: api/vehicles/5
|
||||
[Authorize]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<string>>> DeleteVehicle(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var vehicle = await _context.Vehicles.FindAsync(id);
|
||||
if (vehicle == null)
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Vehicle not found"));
|
||||
}
|
||||
|
||||
_context.Vehicles.Remove(vehicle);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(new ApiResponse<string>(true, "Vehicle deleted successfully"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private bool VehicleExists(int id)
|
||||
{
|
||||
return _context.Vehicles.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
231
Controllers/WaybillEntriesController.cs
Normal file
231
Controllers/WaybillEntriesController.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
|
||||
using LogisticsApp.Server.Data;
|
||||
using LogisticsApp.Server.DTOs;
|
||||
using LogisticsApp.Server.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LogisticsApp.Server.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class WaybillEntriesController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public WaybillEntriesController(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/waybillentries
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<ApiResponse<List<WaybillEntry>>>> GetWaybillEntries()
|
||||
{
|
||||
try
|
||||
{
|
||||
var entries = await _context.WaybillEntries
|
||||
.OrderByDescending(w => w.Date)
|
||||
.ThenBy(w => w.StartTime)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(new ApiResponse<List<WaybillEntry>>(true, "Waybill entries retrieved successfully", entries));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/waybillentries/5
|
||||
[Authorize]
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<WaybillEntry>>> GetWaybillEntry(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var waybillEntry = await _context.WaybillEntries
|
||||
.FirstOrDefaultAsync(w => w.Id == id);
|
||||
|
||||
if (waybillEntry == null)
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Waybill entry not found"));
|
||||
}
|
||||
|
||||
return Ok(new ApiResponse<WaybillEntry>(true, "Waybill entry retrieved successfully", waybillEntry));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// POST: api/waybillentries
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ApiResponse<WaybillEntry>>> PostWaybillEntry(WaybillEntry waybillEntry)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
if (waybillEntry.VehicleId <= 0)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Valid VehicleId is required"));
|
||||
}
|
||||
|
||||
if (waybillEntry.OrderId <= 0)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Valid OrderId is required"));
|
||||
}
|
||||
|
||||
|
||||
var vehicleExists = await _context.Vehicles.AnyAsync(v => v.Id == waybillEntry.VehicleId);
|
||||
if (!vehicleExists)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Vehicle not found"));
|
||||
}
|
||||
|
||||
var orderExists = await _context.Orders.AnyAsync(o => o.Id == waybillEntry.OrderId);
|
||||
if (!orderExists)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Order not found"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(waybillEntry.StartTime) || !IsValidTimeFormat(waybillEntry.StartTime))
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "StartTime must be in HH:mm format"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(waybillEntry.EndTime) || !IsValidTimeFormat(waybillEntry.EndTime))
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "EndTime must be in HH:mm format"));
|
||||
}
|
||||
|
||||
waybillEntry.CreatedAt = DateTime.UtcNow;
|
||||
_context.WaybillEntries.Add(waybillEntry);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var createdEntry = await _context.WaybillEntries
|
||||
.FirstOrDefaultAsync(w => w.Id == waybillEntry.Id);
|
||||
|
||||
return CreatedAtAction("GetWaybillEntry", new { id = waybillEntry.Id },
|
||||
new ApiResponse<WaybillEntry>(true, "Waybill entry created successfully", createdEntry));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// PUT: api/waybillentries/5
|
||||
[Authorize]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<WaybillEntry>>> PutWaybillEntry(int id, WaybillEntry waybillEntry)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (id != waybillEntry.Id)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "ID mismatch"));
|
||||
}
|
||||
|
||||
|
||||
if (waybillEntry.VehicleId <= 0)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Valid VehicleId is required"));
|
||||
}
|
||||
|
||||
if (waybillEntry.OrderId <= 0)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Valid OrderId is required"));
|
||||
}
|
||||
|
||||
var vehicleExists = await _context.Vehicles.AnyAsync(v => v.Id == waybillEntry.VehicleId);
|
||||
if (!vehicleExists)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Vehicle not found"));
|
||||
}
|
||||
|
||||
var orderExists = await _context.Orders.AnyAsync(o => o.Id == waybillEntry.OrderId);
|
||||
if (!orderExists)
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "Order not found"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(waybillEntry.StartTime) || !IsValidTimeFormat(waybillEntry.StartTime))
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "StartTime must be in HH:mm format"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(waybillEntry.EndTime) || !IsValidTimeFormat(waybillEntry.EndTime))
|
||||
{
|
||||
return BadRequest(new ApiResponse<string>(false, "EndTime must be in HH:mm format"));
|
||||
}
|
||||
|
||||
_context.Entry(waybillEntry).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!WaybillEntryExists(id))
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Waybill entry not found"));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var updatedEntry = await _context.WaybillEntries
|
||||
.FirstOrDefaultAsync(w => w.Id == waybillEntry.Id);
|
||||
|
||||
return Ok(new ApiResponse<WaybillEntry>(true, "Waybill entry updated successfully", updatedEntry));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE: api/waybillentries/5
|
||||
[Authorize]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<string>>> DeleteWaybillEntry(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var waybillEntry = await _context.WaybillEntries.FindAsync(id);
|
||||
if (waybillEntry == null)
|
||||
{
|
||||
return NotFound(new ApiResponse<string>(false, "Waybill entry not found"));
|
||||
}
|
||||
|
||||
_context.WaybillEntries.Remove(waybillEntry);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(new ApiResponse<string>(true, "Waybill entry deleted successfully"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ApiResponse<string>(false, $"Internal server error: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private bool WaybillEntryExists(int id)
|
||||
{
|
||||
return _context.WaybillEntries.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
private bool IsValidTimeFormat(string time)
|
||||
{
|
||||
return System.Text.RegularExpressions.Regex.IsMatch(time, @"^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$");
|
||||
}
|
||||
}
|
||||
}
|
||||
26
DTOs/ApiResponse.cs
Normal file
26
DTOs/ApiResponse.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
namespace LogisticsApp.Server.DTOs
|
||||
{
|
||||
public class ApiResponse<T>
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public T? Data { get; set; }
|
||||
|
||||
public ApiResponse(bool success, string message, T? data = default)
|
||||
{
|
||||
Success = success;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public class PagedResult<T>
|
||||
{
|
||||
public List<T> Items { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
}
|
||||
}
|
||||
8
DTOs/LoginRequest.cs
Normal file
8
DTOs/LoginRequest.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace LogisticsApp.Server.DTOs
|
||||
{
|
||||
public class LoginRequest
|
||||
{
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
10
DTOs/LoginResponse.cs
Normal file
10
DTOs/LoginResponse.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace LogisticsApp.Server.DTOs
|
||||
{
|
||||
public class LoginResponse
|
||||
{
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Role { get; set; } = string.Empty;
|
||||
public DateTime Expires { get; set; }
|
||||
}
|
||||
}
|
||||
13
DTOs/OrderFilters.cs
Normal file
13
DTOs/OrderFilters.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace LogisticsApp.Server.DTOs
|
||||
{
|
||||
public class OrderFilters
|
||||
{
|
||||
public string? ClientName { get; set; }
|
||||
public decimal? MinCost { get; set; }
|
||||
public decimal? MaxCost { get; set; }
|
||||
public DateTime? OrderDate { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public int Page { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 50;
|
||||
}
|
||||
}
|
||||
11
DTOs/VehicleFilters.cs
Normal file
11
DTOs/VehicleFilters.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace LogisticsApp.Server.DTOs
|
||||
{
|
||||
public class VehicleFilters
|
||||
{
|
||||
public string? DriverName { get; set; }
|
||||
public string? VehicleType { get; set; }
|
||||
public string? LicensePlate { get; set; }
|
||||
public int Page { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 50;
|
||||
}
|
||||
}
|
||||
71
Data/ApplicationDbContext.cs
Normal file
71
Data/ApplicationDbContext.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using LogisticsApp.Server.Models;
|
||||
|
||||
namespace LogisticsApp.Server.Data
|
||||
{
|
||||
public class ApplicationDbContext : DbContext
|
||||
{
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<Order> Orders { get; set; }
|
||||
public DbSet<Vehicle> Vehicles { get; set; }
|
||||
public DbSet<WaybillEntry> WaybillEntries { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
foreach (var property in entityType.GetProperties())
|
||||
{
|
||||
if (property.ClrType == typeof(DateTime))
|
||||
{
|
||||
modelBuilder.Entity(entityType.ClrType)
|
||||
.Property<DateTime>(property.Name)
|
||||
.HasConversion(
|
||||
v => v.Kind == DateTimeKind.Unspecified
|
||||
? DateTime.SpecifyKind(v, DateTimeKind.Utc)
|
||||
: v.ToUniversalTime(),
|
||||
v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
|
||||
}
|
||||
else if (property.ClrType == typeof(DateTime?))
|
||||
{
|
||||
modelBuilder.Entity(entityType.ClrType)
|
||||
.Property<DateTime?>(property.Name)
|
||||
.HasConversion(
|
||||
v => !v.HasValue
|
||||
? v
|
||||
: (v.Value.Kind == DateTimeKind.Unspecified
|
||||
? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc)
|
||||
: v.Value.ToUniversalTime()),
|
||||
v => v.HasValue
|
||||
? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc)
|
||||
: v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
modelBuilder.Entity<Order>()
|
||||
.HasIndex(o => o.Status);
|
||||
|
||||
modelBuilder.Entity<Order>()
|
||||
.HasIndex(o => o.OrderDate);
|
||||
|
||||
modelBuilder.Entity<Order>()
|
||||
.HasIndex(o => o.ClientName);
|
||||
|
||||
modelBuilder.Entity<Vehicle>()
|
||||
.HasIndex(v => v.LicensePlate);
|
||||
|
||||
modelBuilder.Entity<WaybillEntry>()
|
||||
.HasIndex(w => new { w.VehicleId, w.Date });
|
||||
|
||||
modelBuilder.Entity<User>()
|
||||
.HasIndex(u => u.Username)
|
||||
.IsUnique();
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Data/DbInitializer.cs
Normal file
82
Data/DbInitializer.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
using LogisticsApp.Server.Models;
|
||||
|
||||
namespace LogisticsApp.Server.Data
|
||||
{
|
||||
public static class DbInitializer
|
||||
{
|
||||
public static async Task Initialize(ApplicationDbContext context)
|
||||
{
|
||||
|
||||
if (context.Users.Any() || context.Orders.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var user = new User
|
||||
{
|
||||
Username = "admin",
|
||||
PasswordHash = BCrypt.Net.BCrypt.HashPassword("admin123"),
|
||||
Role = "admin",
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
context.Users.Add(user);
|
||||
|
||||
user = new User
|
||||
{
|
||||
Username = "user",
|
||||
PasswordHash = BCrypt.Net.BCrypt.HashPassword("user123"),
|
||||
Role = "user",
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
context.Users.Add(user);
|
||||
|
||||
var orders = new[]
|
||||
{
|
||||
new Order { Id = 1, ClientName = "Иванов Иван Иванович", OrderCost = 15000, OrderDate = DateTime.Parse("2025-01-15"), Status = "completed", Address = "ул. Ленина, 10", Description = "Доставка строительных материалов", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 2, ClientName = "ООО 'СтройМир'", OrderCost = 25000, OrderDate = DateTime.Parse("2025-01-16"), Status = "in_progress", Address = "пр. Мира, 25", Description = "Перевозка оборудования", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 3, ClientName = "ЗАО 'ТехноПром'", OrderCost = 18000, OrderDate = DateTime.Parse("2025-01-17"), Status = "completed", Address = "ул. Промышленная, 45", Description = "Доставка офисной мебели", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 4, ClientName = "Сергеев Алексей Петрович", OrderCost = 12000, OrderDate = DateTime.Parse("2025-01-18"), Status = "in_progress", Address = "пр. Космонавтов, 78", Description = "Переезд квартиры", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 5, ClientName = "ИП Козлова Марина Сергеевна", OrderCost = 22000, OrderDate = DateTime.Parse("2025-01-19"), Status = "in_progress", Address = "ул. Торговая, 12", Description = "Доставка товаров для магазина", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 6, ClientName = "ООО 'СтройГрад'", OrderCost = 35000, OrderDate = DateTime.Parse("2025-01-20"), Status = "pending", Address = "ул. Строителей, 33", Description = "Перевозка кирпича", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 7, ClientName = "Федоров Дмитрий Иванович", OrderCost = 8000, OrderDate = DateTime.Parse("2025-01-21"), Status = "completed", Address = "ул. Садовая, 15", Description = "Перевозка бытовой техники", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 8, ClientName = "ООО 'ПромСнаб'", OrderCost = 28000, OrderDate = DateTime.Parse("2025-01-22"), Status = "cancelled", Address = "пр. Заводской, 67", Description = "Доставка станков", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 9, ClientName = "Александрова Ольга Викторовна", OrderCost = 9500, OrderDate = DateTime.Parse("2025-01-23"), Status = "cancelled", Address = "ул. Центральная, 89", Description = "Перевозка личных вещей", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 10, ClientName = "ООО 'ТоргСервис'", OrderCost = 19500, OrderDate = DateTime.Parse("2025-01-24"), Status = "in_progress", Address = "ул. Коммерческая, 56", Description = "Доставка продуктов питания", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 11, ClientName = "Васильев Павел Сергеевич", OrderCost = 11000, OrderDate = DateTime.Parse("2025-01-25"), Status = "completed", Address = "ул. Молодежная, 23", Description = "Переезд на новую квартиру", CreatedAt = DateTime.UtcNow },
|
||||
new Order { Id = 12, ClientName = "ЗАО 'ПромИнвест'", OrderCost = 42000, OrderDate = DateTime.Parse("2025-01-26"), Status = "pending", Address = "пр. Индустриальный, 44", Description = "Перевозка промышленного оборудования", CreatedAt = DateTime.UtcNow }
|
||||
};
|
||||
context.Orders.AddRange(orders);
|
||||
|
||||
|
||||
var vehicles = new[]
|
||||
{
|
||||
new Vehicle { Id = 1, DriverName = "Петров Петр Петрович", VehicleType = "Газель", LicensePlate = "А123БВ77", CreatedAt = DateTime.UtcNow },
|
||||
new Vehicle { Id = 2, DriverName = "Сидоров Алексей Владимирович", VehicleType = "Камаз", LicensePlate = "В456ГД77", CreatedAt = DateTime.UtcNow },
|
||||
new Vehicle { Id = 3, DriverName = "Кузнецов Михаил Иванович", VehicleType = "Газель", LicensePlate = "С789ЕЖ77", CreatedAt = DateTime.UtcNow },
|
||||
new Vehicle { Id = 4, DriverName = "Николаев Андрей Сергеевич", VehicleType = "ЗИЛ", LicensePlate = "Д321ФГ77", CreatedAt = DateTime.UtcNow },
|
||||
new Vehicle { Id = 5, DriverName = "Морозов Виктор Павлович", VehicleType = "Камаз", LicensePlate = "Е654ХЦ77", CreatedAt = DateTime.UtcNow },
|
||||
new Vehicle { Id = 6, DriverName = "Орлов Денис Александрович", VehicleType = "Газель", LicensePlate = "Ж987ЧШ77", CreatedAt = DateTime.UtcNow },
|
||||
new Vehicle { Id = 7, DriverName = "Белов Артем Игоревич", VehicleType = "Фургон", LicensePlate = "З159ЩР77", CreatedAt = DateTime.UtcNow },
|
||||
new Vehicle { Id = 8, DriverName = "Громов Сергей Викторович", VehicleType = "ЗИЛ", LicensePlate = "И753ЪЫ77", CreatedAt = DateTime.UtcNow }
|
||||
};
|
||||
context.Vehicles.AddRange(vehicles);
|
||||
|
||||
|
||||
var waybillEntries = new[]
|
||||
{
|
||||
new WaybillEntry { VehicleId = 6, OrderId = 1, StartTime = "20:00", EndTime = "22:00", Date = new DateOnly(2025, 11, 21), CreatedAt = DateTime.UtcNow },
|
||||
new WaybillEntry { VehicleId = 6, OrderId = 4, StartTime = "14:00", EndTime = "16:00", Date = new DateOnly(2025, 11, 21), CreatedAt = DateTime.UtcNow },
|
||||
new WaybillEntry { VehicleId = 6, OrderId = 2, StartTime = "10:00", EndTime = "12:00", Date = new DateOnly(2025, 11, 21), CreatedAt = DateTime.UtcNow },
|
||||
new WaybillEntry { VehicleId = 2, OrderId = 2, StartTime = "20:00", EndTime = "22:00", Date = new DateOnly(2025, 11, 21), CreatedAt = DateTime.UtcNow },
|
||||
new WaybillEntry { VehicleId = 2, OrderId = 4, StartTime = "08:00", EndTime = "09:00", Date = new DateOnly(2025, 11, 21), CreatedAt = DateTime.UtcNow },
|
||||
new WaybillEntry { VehicleId = 1, OrderId = 1, StartTime = "08:00", EndTime = "11:00", Date = new DateOnly(2025, 11, 21), CreatedAt = DateTime.UtcNow },
|
||||
new WaybillEntry { VehicleId = 2, OrderId = 4, StartTime = "08:00", EndTime = "10:00", Date = new DateOnly(2025, 11, 23), CreatedAt = DateTime.UtcNow }
|
||||
};
|
||||
context.WaybillEntries.AddRange(waybillEntries);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
LogisticsApp.Server.csproj
Normal file
27
LogisticsApp.Server.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
LogisticsApp.Server.http
Normal file
6
LogisticsApp.Server.http
Normal file
@@ -0,0 +1,6 @@
|
||||
@LogisticsApp.Server_HostAddress = http://localhost:5155
|
||||
|
||||
GET {{LogisticsApp.Server_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
25
LogisticsApp.Server.sln
Normal file
25
LogisticsApp.Server.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36121.58 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LogisticsApp.Server", "LogisticsApp.Server.csproj", "{BF240DA8-0F2B-443A-98F6-47565A946F26}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BF240DA8-0F2B-443A-98F6-47565A946F26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BF240DA8-0F2B-443A-98F6-47565A946F26}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BF240DA8-0F2B-443A-98F6-47565A946F26}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BF240DA8-0F2B-443A-98F6-47565A946F26}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8F81766E-91B3-450A-84EA-45D3E4D33C17}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
60
Middleware/JwtMiddleware.cs
Normal file
60
Middleware/JwtMiddleware.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text;
|
||||
|
||||
namespace LogisticsApp.Server.Middleware
|
||||
{
|
||||
public class JwtMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public JwtMiddleware(RequestDelegate next, IConfiguration configuration)
|
||||
{
|
||||
_next = next;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last()
|
||||
?? context.Request.Cookies["auth_token"];
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
AttachUserToContext(context, token);
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private void AttachUserToContext(HttpContext context, string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key not configured"));
|
||||
|
||||
tokenHandler.ValidateToken(token, new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidIssuer = _configuration["Jwt:Issuer"],
|
||||
ValidAudience = _configuration["Jwt:Audience"],
|
||||
ClockSkew = TimeSpan.Zero
|
||||
}, out SecurityToken validatedToken);
|
||||
|
||||
var jwtToken = (JwtSecurityToken)validatedToken;
|
||||
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "nameid").Value);
|
||||
|
||||
|
||||
context.Items["UserId"] = userId;
|
||||
context.Items["User"] = jwtToken.Claims.First(x => x.Type == "name").Value;
|
||||
context.Items["Role"] = jwtToken.Claims.First(x => x.Type == "role").Value;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
203
Migrations/20251128103633_fix.Designer.cs
generated
Normal file
203
Migrations/20251128103633_fix.Designer.cs
generated
Normal file
@@ -0,0 +1,203 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using LogisticsApp.Server.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LogisticsApp.Server.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20251128103633_fix")]
|
||||
partial class fix
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.Order", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Address")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<string>("ClientName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("OrderCost")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("OrderDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientName");
|
||||
|
||||
b.HasIndex("OrderDate");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("Orders");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.Vehicle", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DriverName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("LicensePlate")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<string>("VehicleType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LicensePlate");
|
||||
|
||||
b.ToTable("Vehicles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.WaybillEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<string>("EndTime")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("character varying(5)");
|
||||
|
||||
b.Property<int>("OrderId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StartTime")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("character varying(5)");
|
||||
|
||||
b.Property<int>("VehicleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrderId");
|
||||
|
||||
b.HasIndex("VehicleId", "Date");
|
||||
|
||||
b.ToTable("WaybillEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.WaybillEntry", b =>
|
||||
{
|
||||
b.HasOne("LogisticsApp.Server.Models.Order", "Order")
|
||||
.WithMany()
|
||||
.HasForeignKey("OrderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("LogisticsApp.Server.Models.Vehicle", "Vehicle")
|
||||
.WithMany()
|
||||
.HasForeignKey("VehicleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Order");
|
||||
|
||||
b.Navigation("Vehicle");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Migrations/20251128103633_fix.cs
Normal file
150
Migrations/20251128103633_fix.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LogisticsApp.Server.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class fix : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Orders",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ClientName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
OrderCost = table.Column<decimal>(type: "numeric(18,2)", nullable: false),
|
||||
OrderDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
Address = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
|
||||
Description = table.Column<string>(type: "text", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Orders", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Username = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
||||
Role = table.Column<string>(type: "text", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Vehicles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DriverName = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
VehicleType = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
LicensePlate = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Vehicles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WaybillEntries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
VehicleId = table.Column<int>(type: "integer", nullable: false),
|
||||
OrderId = table.Column<int>(type: "integer", nullable: false),
|
||||
StartTime = table.Column<string>(type: "character varying(5)", maxLength: 5, nullable: false),
|
||||
EndTime = table.Column<string>(type: "character varying(5)", maxLength: 5, nullable: false),
|
||||
Date = table.Column<DateOnly>(type: "date", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WaybillEntries", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_WaybillEntries_Orders_OrderId",
|
||||
column: x => x.OrderId,
|
||||
principalTable: "Orders",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_WaybillEntries_Vehicles_VehicleId",
|
||||
column: x => x.VehicleId,
|
||||
principalTable: "Vehicles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_ClientName",
|
||||
table: "Orders",
|
||||
column: "ClientName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_OrderDate",
|
||||
table: "Orders",
|
||||
column: "OrderDate");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_Status",
|
||||
table: "Orders",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_Username",
|
||||
table: "Users",
|
||||
column: "Username",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Vehicles_LicensePlate",
|
||||
table: "Vehicles",
|
||||
column: "LicensePlate");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WaybillEntries_OrderId",
|
||||
table: "WaybillEntries",
|
||||
column: "OrderId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WaybillEntries_VehicleId_Date",
|
||||
table: "WaybillEntries",
|
||||
columns: new[] { "VehicleId", "Date" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "WaybillEntries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Orders");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Vehicles");
|
||||
}
|
||||
}
|
||||
}
|
||||
200
Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
200
Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using LogisticsApp.Server.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LogisticsApp.Server.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.Order", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Address")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<string>("ClientName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("OrderCost")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("OrderDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientName");
|
||||
|
||||
b.HasIndex("OrderDate");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("Orders");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.Vehicle", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DriverName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("LicensePlate")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<string>("VehicleType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LicensePlate");
|
||||
|
||||
b.ToTable("Vehicles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.WaybillEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<string>("EndTime")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("character varying(5)");
|
||||
|
||||
b.Property<int>("OrderId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StartTime")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("character varying(5)");
|
||||
|
||||
b.Property<int>("VehicleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrderId");
|
||||
|
||||
b.HasIndex("VehicleId", "Date");
|
||||
|
||||
b.ToTable("WaybillEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LogisticsApp.Server.Models.WaybillEntry", b =>
|
||||
{
|
||||
b.HasOne("LogisticsApp.Server.Models.Order", "Order")
|
||||
.WithMany()
|
||||
.HasForeignKey("OrderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("LogisticsApp.Server.Models.Vehicle", "Vehicle")
|
||||
.WithMany()
|
||||
.HasForeignKey("VehicleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Order");
|
||||
|
||||
b.Navigation("Vehicle");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Models/Orders.cs
Normal file
35
Models/Orders.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace LogisticsApp.Server.Models
|
||||
{
|
||||
public class Order
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(200)]
|
||||
public string ClientName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "decimal(18,2)")]
|
||||
public decimal OrderCost { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime OrderDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[Required]
|
||||
[StringLength(20)]
|
||||
public string Status { get; set; } = "pending"; // pending, in_progress, completed, cancelled
|
||||
|
||||
[Required]
|
||||
[StringLength(500)]
|
||||
public string Address { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
22
Models/User.cs
Normal file
22
Models/User.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LogisticsApp.Server.Models
|
||||
{
|
||||
public class User
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(50)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string PasswordHash { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string Role { get; set; } = "admin"; // admin, user
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
24
Models/Vehicle.cs
Normal file
24
Models/Vehicle.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LogisticsApp.Server.Models
|
||||
{
|
||||
public class Vehicle
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string DriverName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(50)]
|
||||
public string VehicleType { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(20)]
|
||||
public string LicensePlate { get; set; } = string.Empty;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
32
Models/WaybillEntry.cs
Normal file
32
Models/WaybillEntry.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace LogisticsApp.Server.Models
|
||||
{
|
||||
public class WaybillEntry
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[ForeignKey("Vehicle")]
|
||||
public int VehicleId { get; set; }
|
||||
|
||||
[Required]
|
||||
[ForeignKey("Order")]
|
||||
public int OrderId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(5)]
|
||||
public string StartTime { get; set; } = string.Empty; // Format: "HH:mm"
|
||||
|
||||
[Required]
|
||||
[StringLength(5)]
|
||||
public string EndTime { get; set; } = string.Empty; // Format: "HH:mm"
|
||||
|
||||
[Required]
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
121
Program.cs
Normal file
121
Program.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Text;
|
||||
using LogisticsApp.Server.Data;
|
||||
using LogisticsApp.Server.Services;
|
||||
using LogisticsApp.Server.Middleware;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
||||
// Configure Swagger
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "Logistics API",
|
||||
Version = "v1",
|
||||
Description = "API for Logistics Management System"
|
||||
});
|
||||
|
||||
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
Scheme = "Bearer"
|
||||
});
|
||||
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[] {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Database Configuration
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
|
||||
|
||||
// JWT Authentication
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
||||
ValidAudience = builder.Configuration["Jwt:Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key not configured")))
|
||||
};
|
||||
});
|
||||
|
||||
// Services
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
|
||||
// CORS for React client
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("ReactClient", policy =>
|
||||
{
|
||||
policy.WithOrigins("http://localhost:3000")
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials()
|
||||
.WithExposedHeaders("Set-Cookie");
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseCors("ReactClient");
|
||||
app.UseMiddleware<JwtMiddleware>();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
// Initialize database with sample data
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
try
|
||||
{
|
||||
var context = services.GetRequiredService<ApplicationDbContext>();
|
||||
context.Database.EnsureCreated();
|
||||
await DbInitializer.Initialize(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = services.GetRequiredService<ILogger<Program>>();
|
||||
logger.LogError(ex, "An error occurred while seeding the database.");
|
||||
}
|
||||
}
|
||||
|
||||
app.Run();
|
||||
41
Properties/launchSettings.json
Normal file
41
Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:53244",
|
||||
"sslPort": 44332
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5155",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7071;http://localhost:5155",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Services/AuthService.cs
Normal file
80
Services/AuthService.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using LogisticsApp.Server.Data;
|
||||
using LogisticsApp.Server.DTOs;
|
||||
using LogisticsApp.Server.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace LogisticsApp.Server.Services
|
||||
{
|
||||
public interface IAuthService
|
||||
{
|
||||
Task<LoginResponse?> LoginAsync(LoginRequest request);
|
||||
string GenerateJwtToken(User user);
|
||||
}
|
||||
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public AuthService(ApplicationDbContext context, IConfiguration configuration)
|
||||
{
|
||||
_context = context;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task<LoginResponse?> LoginAsync(LoginRequest request)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Username == request.Username);
|
||||
|
||||
if (user == null || !VerifyPassword(request.Password, user.PasswordHash))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = GenerateJwtToken(user);
|
||||
|
||||
return new LoginResponse
|
||||
{
|
||||
Token = token,
|
||||
Username = user.Username,
|
||||
Role = user.Role,
|
||||
Expires = DateTime.UtcNow.AddHours(24)
|
||||
};
|
||||
}
|
||||
|
||||
public string GenerateJwtToken(User user)
|
||||
{
|
||||
var key = new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key not configured")));
|
||||
|
||||
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new Claim(ClaimTypes.Name, user.Username),
|
||||
new Claim(ClaimTypes.Role, user.Role)
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: _configuration["Jwt:Issuer"],
|
||||
audience: _configuration["Jwt:Audience"],
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddHours(24),
|
||||
signingCredentials: credentials
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
private static bool VerifyPassword(string password, string passwordHash)
|
||||
{
|
||||
return BCrypt.Net.BCrypt.Verify(password, passwordHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
appsettings.Development.json
Normal file
8
appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
appsettings.json
Normal file
17
appsettings.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Database=logisticsdb;Username=postgres;Password=postgres"
|
||||
},
|
||||
"Jwt": {
|
||||
"Key": "your-super-secret-key-at-least-32-characters-long",
|
||||
"Issuer": "LogisticsApp",
|
||||
"Audience": "LogisticsAppClient"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in New Issue
Block a user