Поиск, права админ клиент, почта, пароль от 8 символов
This commit is contained in:
parent
f09fa1ecc9
commit
33a8506f9b
|
|
@ -18,6 +18,22 @@ public class AuthController : ControllerBase
|
|||
_context = context;
|
||||
}
|
||||
|
||||
// простая проверка почты: есть @ и точка после нее
|
||||
private static bool IsEmailOk(string email)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
return false; // пустая не годится
|
||||
|
||||
int at = email.IndexOf('@'); // ищу собаку
|
||||
|
||||
if (at < 1)
|
||||
return false; // @ нет или стоит первой
|
||||
|
||||
int dot = email.IndexOf('.', at); // ищу точку после @
|
||||
|
||||
return dot > at + 1 && dot < email.Length - 1; // точка не сразу и не в конце
|
||||
}
|
||||
|
||||
// войти, адрес POST api/auth/login
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login(User user) // приходят логин и пароль
|
||||
|
|
@ -38,21 +54,43 @@ public class AuthController : ControllerBase
|
|||
{
|
||||
existingUser.Id,
|
||||
existingUser.Login,
|
||||
existingUser.Email,
|
||||
Role = existingUser.Role?.Name
|
||||
});
|
||||
}
|
||||
|
||||
// регистрация, адрес POST api/auth/register
|
||||
[HttpPost("register")]
|
||||
public async Task<IActionResult> Register(User user) // приходят логин и пароль
|
||||
public async Task<IActionResult> Register(User user) // приходят логин пароль и почта
|
||||
{
|
||||
// чищу пробелы
|
||||
user.Login = (user.Login ?? "").Trim();
|
||||
user.Email = (user.Email ?? "").Trim();
|
||||
|
||||
// проверки по порядку, чтоб человек понял что не так
|
||||
if (user.Login.Length < 2)
|
||||
return BadRequest("Логин слишком короткий, надо минимум 2 буквы.");
|
||||
|
||||
if (user.Password == null || user.Password.Length < 8)
|
||||
return BadRequest("Пароль слишком короткий, надо минимум 8 символов.");
|
||||
|
||||
if (!IsEmailOk(user.Email))
|
||||
return BadRequest("Почта неправильная, проверьте ее.");
|
||||
|
||||
// проверяю нет ли уже такого логина
|
||||
var exists = await _context.Users
|
||||
var loginExists = await _context.Users
|
||||
.AnyAsync(u => u.Login == user.Login);
|
||||
|
||||
if (exists)
|
||||
if (loginExists)
|
||||
return BadRequest("Такой логин уже занят.");
|
||||
|
||||
// проверяю не занята ли почта
|
||||
var emailExists = await _context.Users
|
||||
.AnyAsync(u => u.Email == user.Email);
|
||||
|
||||
if (emailExists)
|
||||
return BadRequest("Такая почта уже зарегистрирована.");
|
||||
|
||||
// ищу роль клиент чтоб дать ее новичку
|
||||
var clientRole = await _context.Roles
|
||||
.FirstOrDefaultAsync(r => r.Name == "Client");
|
||||
|
|
@ -70,6 +108,7 @@ public class AuthController : ControllerBase
|
|||
{
|
||||
user.Id,
|
||||
user.Login,
|
||||
user.Email,
|
||||
Role = clientRole.Name
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ public class CategoriesController : ControllerBase
|
|||
[HttpPost]
|
||||
public async Task<IActionResult> CreateCategory(Category category) // приходит название
|
||||
{
|
||||
category.Name = (category.Name ?? "").Trim(); // убираю пробелы
|
||||
|
||||
if (category.Name.Length < 2)
|
||||
return BadRequest("Название слишком короткое, надо минимум 2 буквы.");
|
||||
|
||||
_context.Categories.Add(category); // добавляю
|
||||
await _context.SaveChangesAsync(); // сохраняю
|
||||
return Ok(category);
|
||||
|
|
@ -44,6 +49,11 @@ public class CategoriesController : ControllerBase
|
|||
if (existingCategory == null)
|
||||
return NotFound(); // нет такой
|
||||
|
||||
category.Name = (category.Name ?? "").Trim();
|
||||
|
||||
if (category.Name.Length < 2)
|
||||
return BadRequest("Название слишком короткое, надо минимум 2 буквы.");
|
||||
|
||||
existingCategory.Name = category.Name; // меняю название
|
||||
await _context.SaveChangesAsync(); // сохраняю
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ public class OrderItemsController : ControllerBase
|
|||
[HttpPost]
|
||||
public async Task<IActionResult> CreateOrderItem(OrderItem orderItem) // приходит заказ товар колво цена
|
||||
{
|
||||
if (orderItem.Quantity <= 0)
|
||||
return BadRequest("Количество должно быть больше нуля.");
|
||||
|
||||
if (orderItem.Price <= 0)
|
||||
return BadRequest("Цена должна быть больше нуля.");
|
||||
|
||||
_context.OrderItems.Add(orderItem); // добавляю
|
||||
await _context.SaveChangesAsync(); // сохраняю
|
||||
return Ok(orderItem);
|
||||
|
|
@ -49,6 +55,12 @@ public class OrderItemsController : ControllerBase
|
|||
if (existingItem == null)
|
||||
return NotFound();
|
||||
|
||||
if (orderItem.Quantity <= 0)
|
||||
return BadRequest("Количество должно быть больше нуля.");
|
||||
|
||||
if (orderItem.Price <= 0)
|
||||
return BadRequest("Цена должна быть больше нуля.");
|
||||
|
||||
// меняю поля
|
||||
existingItem.OrderId = orderItem.OrderId;
|
||||
existingItem.ProductId = orderItem.ProductId;
|
||||
|
|
|
|||
|
|
@ -30,10 +30,16 @@ public class OrdersController : ControllerBase
|
|||
return Ok(orders);
|
||||
}
|
||||
|
||||
// какие статусы можно ставить
|
||||
private static readonly string[] OkStatuses = ["New", "Processing", "Completed", "Cancelled"];
|
||||
|
||||
// добавить заказ
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateOrder(Order order) // приходит человек дата статус
|
||||
{
|
||||
if (!OkStatuses.Contains(order.Status))
|
||||
return BadRequest("Такого статуса нет.");
|
||||
|
||||
_context.Orders.Add(order); // добавляю
|
||||
await _context.SaveChangesAsync(); // сохраняю
|
||||
return Ok(order);
|
||||
|
|
@ -48,6 +54,9 @@ public class OrdersController : ControllerBase
|
|||
if (existingOrder == null)
|
||||
return NotFound();
|
||||
|
||||
if (!OkStatuses.Contains(order.Status))
|
||||
return BadRequest("Такого статуса нет.");
|
||||
|
||||
// меняю поля
|
||||
existingOrder.UserId = order.UserId;
|
||||
existingOrder.Date = order.Date;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,14 @@ public class ProductsController : ControllerBase
|
|||
[HttpPost]
|
||||
public async Task<IActionResult> CreateProduct(Product product) // приходит товар
|
||||
{
|
||||
product.Name = (product.Name ?? "").Trim(); // убираю пробелы
|
||||
|
||||
if (product.Name.Length < 2)
|
||||
return BadRequest("Название слишком короткое, надо минимум 2 буквы.");
|
||||
|
||||
if (product.Price <= 0)
|
||||
return BadRequest("Цена должна быть больше нуля.");
|
||||
|
||||
// проверяю есть ли такая категория
|
||||
var categoryExists = await _context.Categories
|
||||
.AnyAsync(c => c.Id == product.CategoryId);
|
||||
|
|
@ -57,6 +65,14 @@ public class ProductsController : ControllerBase
|
|||
if (existingProduct == null)
|
||||
return NotFound(); // нет такого
|
||||
|
||||
product.Name = (product.Name ?? "").Trim();
|
||||
|
||||
if (product.Name.Length < 2)
|
||||
return BadRequest("Название слишком короткое, надо минимум 2 буквы.");
|
||||
|
||||
if (product.Price <= 0)
|
||||
return BadRequest("Цена должна быть больше нуля.");
|
||||
|
||||
// меняю поля
|
||||
existingProduct.Name = product.Name;
|
||||
existingProduct.Price = product.Price;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ public class StocksController : ControllerBase
|
|||
[HttpPost]
|
||||
public async Task<IActionResult> CreateStock(Stock stock) // приходит товар склад и количество
|
||||
{
|
||||
if (stock.Quantity < 0)
|
||||
return BadRequest("Количество не может быть меньше нуля.");
|
||||
|
||||
_context.Stocks.Add(stock); // добавляю
|
||||
await _context.SaveChangesAsync(); // сохраняю
|
||||
return Ok(stock);
|
||||
|
|
@ -49,6 +52,9 @@ public class StocksController : ControllerBase
|
|||
if (existingStock == null)
|
||||
return NotFound();
|
||||
|
||||
if (stock.Quantity < 0)
|
||||
return BadRequest("Количество не может быть меньше нуля.");
|
||||
|
||||
// меняю поля
|
||||
existingStock.ProductId = stock.ProductId;
|
||||
existingStock.WarehouseId = stock.WarehouseId;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,54 @@ public class UsersController : ControllerBase
|
|||
_context = context;
|
||||
}
|
||||
|
||||
// простая проверка почты: есть @ и точка после нее
|
||||
private static bool IsEmailOk(string email)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
return false; // пустая не годится
|
||||
|
||||
int at = email.IndexOf('@'); // ищу собаку
|
||||
|
||||
if (at < 1)
|
||||
return false; // @ нет или стоит первой
|
||||
|
||||
int dot = email.IndexOf('.', at); // ищу точку после @
|
||||
|
||||
return dot > at + 1 && dot < email.Length - 1; // точка не сразу и не в конце
|
||||
}
|
||||
|
||||
// проверяю человека перед сохранением, возвращаю текст ошибки или пусто
|
||||
private async Task<string> CheckUser(User user, int skipId)
|
||||
{
|
||||
user.Login = (user.Login ?? "").Trim(); // убираю пробелы
|
||||
user.Email = (user.Email ?? "").Trim();
|
||||
|
||||
if (user.Login.Length < 2)
|
||||
return "Логин слишком короткий, надо минимум 2 буквы.";
|
||||
|
||||
if (user.Password == null || user.Password.Length < 8)
|
||||
return "Пароль слишком короткий, надо минимум 8 символов.";
|
||||
|
||||
if (!IsEmailOk(user.Email))
|
||||
return "Почта неправильная, проверьте ее.";
|
||||
|
||||
// логин должен быть только у одного (себя не считаю)
|
||||
bool loginBusy = await _context.Users
|
||||
.AnyAsync(u => u.Login == user.Login && u.Id != skipId);
|
||||
|
||||
if (loginBusy)
|
||||
return "Такой логин уже занят.";
|
||||
|
||||
// почта тоже только у одного
|
||||
bool emailBusy = await _context.Users
|
||||
.AnyAsync(u => u.Email == user.Email && u.Id != skipId);
|
||||
|
||||
if (emailBusy)
|
||||
return "Такая почта уже зарегистрирована.";
|
||||
|
||||
return ""; // все хорошо
|
||||
}
|
||||
|
||||
// отдать всех людей
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetUsers()
|
||||
|
|
@ -32,8 +80,13 @@ public class UsersController : ControllerBase
|
|||
|
||||
// добавить человека
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateUser(User user) // приходит логин пароль роль
|
||||
public async Task<IActionResult> CreateUser(User user) // приходит логин пароль почта роль
|
||||
{
|
||||
string error = await CheckUser(user, 0); // проверяю, 0 значит нового
|
||||
|
||||
if (error != "")
|
||||
return BadRequest(error); // что-то не так, говорю что
|
||||
|
||||
_context.Users.Add(user); // добавляю
|
||||
await _context.SaveChangesAsync(); // сохраняю
|
||||
return Ok(user);
|
||||
|
|
@ -48,9 +101,15 @@ public class UsersController : ControllerBase
|
|||
if (existingUser == null)
|
||||
return NotFound();
|
||||
|
||||
string error = await CheckUser(user, id); // проверяю, себя пропускаю
|
||||
|
||||
if (error != "")
|
||||
return BadRequest(error);
|
||||
|
||||
// меняю поля
|
||||
existingUser.Login = user.Login;
|
||||
existingUser.Password = user.Password;
|
||||
existingUser.Email = user.Email;
|
||||
existingUser.RoleId = user.RoleId;
|
||||
|
||||
await _context.SaveChangesAsync(); // сохраняю
|
||||
|
|
|
|||
|
|
@ -30,6 +30,15 @@ public class WarehousesController : ControllerBase
|
|||
[HttpPost]
|
||||
public async Task<IActionResult> CreateWarehouse(Warehouse warehouse) // приходит название и адрес
|
||||
{
|
||||
warehouse.Name = (warehouse.Name ?? "").Trim();
|
||||
warehouse.Address = (warehouse.Address ?? "").Trim();
|
||||
|
||||
if (warehouse.Name.Length < 2)
|
||||
return BadRequest("Название склада слишком короткое.");
|
||||
|
||||
if (warehouse.Address.Length < 3)
|
||||
return BadRequest("Адрес слишком короткий.");
|
||||
|
||||
_context.Warehouses.Add(warehouse); // добавляю
|
||||
await _context.SaveChangesAsync(); // сохраняю
|
||||
return Ok(warehouse);
|
||||
|
|
@ -44,6 +53,15 @@ public class WarehousesController : ControllerBase
|
|||
if (existingWarehouse == null)
|
||||
return NotFound();
|
||||
|
||||
warehouse.Name = (warehouse.Name ?? "").Trim();
|
||||
warehouse.Address = (warehouse.Address ?? "").Trim();
|
||||
|
||||
if (warehouse.Name.Length < 2)
|
||||
return BadRequest("Название склада слишком короткое.");
|
||||
|
||||
if (warehouse.Address.Length < 3)
|
||||
return BadRequest("Адрес слишком короткий.");
|
||||
|
||||
// меняю поля
|
||||
existingWarehouse.Name = warehouse.Name;
|
||||
existingWarehouse.Address = warehouse.Address;
|
||||
|
|
|
|||
BIN
DEMO/DemoDB.db-shm
Normal file
BIN
DEMO/DemoDB.db-shm
Normal file
Binary file not shown.
BIN
DEMO/DemoDB.db-wal
Normal file
BIN
DEMO/DemoDB.db-wal
Normal file
Binary file not shown.
270
DEMO/Migrations/20260909001244_AddUserEmail.Designer.cs
generated
Normal file
270
DEMO/Migrations/20260909001244_AddUserEmail.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using DEMO.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DEMO.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20260909001244_AddUserEmail")]
|
||||
partial class AddUserEmail
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Orders");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.OrderItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("OrderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ProductId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrderId");
|
||||
|
||||
b.HasIndex("ProductId");
|
||||
|
||||
b.ToTable("OrderItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Products");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Roles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ProductId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("WarehouseId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ProductId");
|
||||
|
||||
b.HasIndex("WarehouseId");
|
||||
|
||||
b.ToTable("Stocks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Login")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Warehouse", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Warehouses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||
{
|
||||
b.HasOne("DEMO.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.OrderItem", b =>
|
||||
{
|
||||
b.HasOne("DEMO.Models.Order", "Order")
|
||||
.WithMany()
|
||||
.HasForeignKey("OrderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DEMO.Models.Product", "Product")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProductId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Order");
|
||||
|
||||
b.Navigation("Product");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||
{
|
||||
b.HasOne("DEMO.Models.Category", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||
{
|
||||
b.HasOne("DEMO.Models.Product", "Product")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProductId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DEMO.Models.Warehouse", "Warehouse")
|
||||
.WithMany()
|
||||
.HasForeignKey("WarehouseId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Product");
|
||||
|
||||
b.Navigation("Warehouse");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||
{
|
||||
b.HasOne("DEMO.Models.Role", "Role")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
29
DEMO/Migrations/20260909001244_AddUserEmail.cs
Normal file
29
DEMO/Migrations/20260909001244_AddUserEmail.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DEMO.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddUserEmail : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Email",
|
||||
table: "Users",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Email",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -150,6 +150,10 @@ namespace DEMO.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Login")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ public class User
|
|||
|
||||
public string Password { get; set; } = string.Empty; // пароль
|
||||
|
||||
public string Email { get; set; } = string.Empty; // почта
|
||||
|
||||
public int RoleId { get; set; } // какая роль (номер)
|
||||
|
||||
public Role? Role { get; set; } // сама роль (подтянется)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,11 @@
|
|||
<input id="registerPasswordRepeat" type="password" placeholder="Еще раз пароль" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Почта
|
||||
<input id="registerEmail" type="email" placeholder="Ваша почта" required>
|
||||
</label>
|
||||
|
||||
<!-- сюда пишу ошибку регистрации -->
|
||||
<div id="registerError" class="auth-error"></div>
|
||||
|
||||
|
|
@ -128,6 +133,9 @@
|
|||
<p id="pageDescription">Управление товарами магазина</p>
|
||||
</div>
|
||||
|
||||
<!-- поиск по странице -->
|
||||
<input id="searchInput" class="search-input" type="text" placeholder="Поиск...">
|
||||
|
||||
<!-- кнопка добавить новое -->
|
||||
<button id="addButton" class="primary-button">+ Добавить</button>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ const pageTitle = document.getElementById("pageTitle");
|
|||
const pageDescription = document.getElementById("pageDescription");
|
||||
const pageContent = document.getElementById("pageContent");
|
||||
const addButton = document.getElementById("addButton");
|
||||
const searchInput = document.getElementById("searchInput"); // поле поиска
|
||||
|
||||
// всплывающее окно
|
||||
const modal = document.getElementById("modal");
|
||||
|
|
@ -55,6 +56,29 @@ let currentPage = "products";
|
|||
let editingId = null;
|
||||
// кто вошел в систему
|
||||
let currentAuthUser = null;
|
||||
// я админ или нет (у админа больше кнопок)
|
||||
let isAdmin = false;
|
||||
// данные текущей страницы, чтоб искать по ним
|
||||
let currentData = [];
|
||||
|
||||
// проверяю админ ли я, если нет то ругаюсь и не пускаю
|
||||
function needAdmin() {
|
||||
if (isAdmin) {
|
||||
return true;
|
||||
}
|
||||
showToast("Только админ может это делать.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// проверяю почту: есть @ и точка после нее
|
||||
function isEmailOk(email) {
|
||||
const at = email.indexOf("@"); // ищу собаку
|
||||
if (at < 1) {
|
||||
return false;
|
||||
}
|
||||
const dot = email.indexOf(".", at); // ищу точку после @
|
||||
return dot > at + 1 && dot < email.length - 1;
|
||||
}
|
||||
|
||||
// показываю сообщение снизу на 2 секунды
|
||||
function showToast(message) {
|
||||
|
|
@ -145,10 +169,13 @@ function showRegisterForm() {
|
|||
// захожу в программу после входа
|
||||
function enterApplication(user) {
|
||||
currentAuthUser = user; // запоминаю кто вошел
|
||||
isAdmin = user.role === "Admin"; // проверяю админ ли я
|
||||
sessionStorage.setItem("demoUser", JSON.stringify(user)); // сохраняю чтоб не выкидывало
|
||||
currentUser.textContent = user.login; // пишу логин
|
||||
currentRole.textContent = user.role || "Client"; // пишу роль
|
||||
userAvatar.textContent = user.login ? user.login.charAt(0).toUpperCase() : "U"; // первая буква в кружок
|
||||
// кнопку Пользователи показываю только админу
|
||||
document.querySelector('.nav-button[data-page="users"]').classList.toggle("hidden", !isAdmin);
|
||||
authScreen.classList.add("hidden"); // прячу вход
|
||||
app.classList.remove("hidden"); // показываю программу
|
||||
showPage("products"); // открываю товары
|
||||
|
|
@ -194,8 +221,9 @@ registerForm.addEventListener("submit", async (event) => {
|
|||
const login = document.getElementById("registerLogin").value.trim();
|
||||
const password = document.getElementById("registerPassword").value;
|
||||
const passwordRepeat = document.getElementById("registerPasswordRepeat").value;
|
||||
const email = document.getElementById("registerEmail").value.trim();
|
||||
// простые проверки
|
||||
if (!login || !password || !passwordRepeat) {
|
||||
if (!login || !password || !passwordRepeat || !email) {
|
||||
registerError.textContent = "Заполните все поля.";
|
||||
return;
|
||||
}
|
||||
|
|
@ -203,8 +231,12 @@ registerForm.addEventListener("submit", async (event) => {
|
|||
registerError.textContent = "Логин слишком короткий.";
|
||||
return;
|
||||
}
|
||||
if (password.length < 4) {
|
||||
registerError.textContent = "Пароль слишком короткий.";
|
||||
if (password.length < 8) {
|
||||
registerError.textContent = "Пароль должен быть минимум 8 символов.";
|
||||
return;
|
||||
}
|
||||
if (!isEmailOk(email)) {
|
||||
registerError.textContent = "Почта неправильная, проверьте ее.";
|
||||
return;
|
||||
}
|
||||
if (password !== passwordRepeat) {
|
||||
|
|
@ -214,7 +246,7 @@ registerForm.addEventListener("submit", async (event) => {
|
|||
try {
|
||||
await api("/api/auth/register", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ login, password, roleId: 2 })
|
||||
body: JSON.stringify({ login, password, email, roleId: 2 })
|
||||
});
|
||||
registerForm.reset();
|
||||
showLoginForm(); // иду на вход
|
||||
|
|
@ -242,6 +274,13 @@ document.querySelectorAll(".nav-button").forEach((button) => {
|
|||
|
||||
// открываю страницу (товары, заказы и тд)
|
||||
async function showPage(page) {
|
||||
// в пользователи пускаю только админа
|
||||
if (page === "users" && !isAdmin) {
|
||||
pageTitle.textContent = "Пользователи";
|
||||
pageDescription.textContent = "Все люди в системе";
|
||||
pageContent.innerHTML = "<div class='empty-page'><strong>Нет прав</strong><span>Только админ видит эту страницу</span></div>";
|
||||
return;
|
||||
}
|
||||
currentPage = page; // запоминаю где я
|
||||
const config = pages[page];
|
||||
if (!config) {
|
||||
|
|
@ -249,15 +288,35 @@ async function showPage(page) {
|
|||
}
|
||||
pageTitle.textContent = config.title; // пишу название
|
||||
pageDescription.textContent = config.description;
|
||||
// кнопку добавить показываю админу везде, а обычному только в заказах
|
||||
addButton.classList.toggle("hidden", !(isAdmin || page === "orders"));
|
||||
searchInput.value = ""; // чищу поиск
|
||||
pageContent.innerHTML = "<div class='empty-page'><strong>Загрузка...</strong></div>"; // пока грузится
|
||||
try {
|
||||
const data = await api(config.endpoint); // беру данные с сервера
|
||||
currentData = data; // запоминаю чтоб искать по ним
|
||||
renderPage(page, data); // рисую
|
||||
} catch (error) {
|
||||
pageContent.innerHTML = "<div class='error-page'>Не получилось: " + escapeHtml(error.message) + "</div>";
|
||||
}
|
||||
}
|
||||
|
||||
// ищу по странице когда печатаю в поиск
|
||||
searchInput.addEventListener("input", () => {
|
||||
const q = searchInput.value.trim().toLowerCase(); // что ищу
|
||||
if (!q) {
|
||||
renderPage(currentPage, currentData); // пусто показываю все
|
||||
return;
|
||||
}
|
||||
// ищу совпадение где угодно (название, логин, адрес и тд)
|
||||
const found = currentData.filter((item) => JSON.stringify(item).toLowerCase().includes(q));
|
||||
if (found.length === 0) {
|
||||
pageContent.innerHTML = "<div class='empty-page'><strong>Ничего не нашлось</strong></div>";
|
||||
return;
|
||||
}
|
||||
renderPage(currentPage, found); // рисую найденное
|
||||
});
|
||||
|
||||
// выбираю что рисовать
|
||||
function renderPage(page, data) {
|
||||
// если пусто то так и пишу
|
||||
|
|
@ -294,10 +353,14 @@ function renderProducts(products) {
|
|||
html += "<h3>" + escapeHtml(p.name) + "</h3>";
|
||||
html += "<div class=\"card-price\">" + formatMoney(p.price) + "</div>";
|
||||
html += "<div class=\"card-info\">Категория: <strong>" + escapeHtml(p.category ? p.category.name : "—") + "</strong></div>";
|
||||
// кнопки показываю только админу
|
||||
if (isAdmin) {
|
||||
html += "<div class=\"card-actions\">";
|
||||
html += "<button class=\"action-button\" onclick=\"editProduct(" + p.id + ")\">Изменить</button>";
|
||||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('products'," + p.id + ")\">Удалить</button>";
|
||||
html += "</div></div>";
|
||||
html += "</div>";
|
||||
}
|
||||
html += "</div>";
|
||||
}
|
||||
html += "</div>";
|
||||
pageContent.innerHTML = html; // показываю все
|
||||
|
|
@ -308,8 +371,11 @@ function renderCategories(categories) {
|
|||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Действия</th></tr></thead><tbody>";
|
||||
for (const c of categories) {
|
||||
html += "<tr><td>#" + c.id + "</td><td>" + escapeHtml(c.name) + "</td><td><div class=\"table-actions\">";
|
||||
// кнопки только для админа
|
||||
if (isAdmin) {
|
||||
html += "<button class=\"action-button\" onclick=\"editCategory(" + c.id + ")\">Изменить</button>";
|
||||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('categories'," + c.id + ")\">Удалить</button>";
|
||||
}
|
||||
html += "</div></td></tr>";
|
||||
}
|
||||
html += "</tbody></table></div>";
|
||||
|
|
@ -321,8 +387,11 @@ function renderWarehouses(warehouses) {
|
|||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Адрес</th><th>Действия</th></tr></thead><tbody>";
|
||||
for (const w of warehouses) {
|
||||
html += "<tr><td>#" + w.id + "</td><td>" + escapeHtml(w.name) + "</td><td>" + escapeHtml(w.address) + "</td><td><div class=\"table-actions\">";
|
||||
// кнопки только для админа
|
||||
if (isAdmin) {
|
||||
html += "<button class=\"action-button\" onclick=\"editWarehouse(" + w.id + ")\">Изменить</button>";
|
||||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('warehouses'," + w.id + ")\">Удалить</button>";
|
||||
}
|
||||
html += "</div></td></tr>";
|
||||
}
|
||||
html += "</tbody></table></div>";
|
||||
|
|
@ -336,8 +405,11 @@ function renderStock(stocks) {
|
|||
const pname = s.product ? s.product.name : "—";
|
||||
const wname = s.warehouse ? s.warehouse.name : "—";
|
||||
html += "<tr><td>#" + s.id + "</td><td>" + escapeHtml(pname) + "</td><td>" + escapeHtml(wname) + "</td><td>" + s.quantity + "</td><td><div class=\"table-actions\">";
|
||||
// кнопки только для админа
|
||||
if (isAdmin) {
|
||||
html += "<button class=\"action-button\" onclick=\"editStock(" + s.id + ")\">Изменить</button>";
|
||||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('stock'," + s.id + ")\">Удалить</button>";
|
||||
}
|
||||
html += "</div></td></tr>";
|
||||
}
|
||||
html += "</tbody></table></div>";
|
||||
|
|
@ -346,10 +418,15 @@ function renderStock(stocks) {
|
|||
|
||||
// рисую пользователей
|
||||
function renderUsers(users) {
|
||||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Логин</th><th>Роль</th><th>Действия</th></tr></thead><tbody>";
|
||||
// сюда пускаю только админа (на всякий случай)
|
||||
if (!isAdmin) {
|
||||
pageContent.innerHTML = "<div class='empty-page'><strong>Нет прав</strong></div>";
|
||||
return;
|
||||
}
|
||||
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Логин</th><th>Почта</th><th>Роль</th><th>Действия</th></tr></thead><tbody>";
|
||||
for (const u of users) {
|
||||
const rname = u.role ? u.role.name : "—";
|
||||
html += "<tr><td>#" + u.id + "</td><td>" + escapeHtml(u.login) + "</td><td><span class=\"status\">" + escapeHtml(rname) + "</span></td><td><div class=\"table-actions\">";
|
||||
html += "<tr><td>#" + u.id + "</td><td>" + escapeHtml(u.login) + "</td><td>" + escapeHtml(u.email || "—") + "</td><td><span class=\"status\">" + escapeHtml(rname) + "</span></td><td><div class=\"table-actions\">";
|
||||
html += "<button class=\"action-button\" onclick=\"editUser(" + u.id + ")\">Изменить</button>";
|
||||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('users'," + u.id + ")\">Удалить</button>";
|
||||
html += "</div></td></tr>";
|
||||
|
|
@ -365,8 +442,11 @@ function renderOrders(orders) {
|
|||
const uname = o.user ? o.user.login : "—";
|
||||
html += "<tr><td>#" + o.id + "</td><td>" + escapeHtml(uname) + "</td><td>" + formatDate(o.date) + "</td><td><span class=\"status\">" + escapeHtml(o.status) + "</span></td><td><div class=\"table-actions\">";
|
||||
html += "<button class=\"action-button\" onclick=\"showOrderItems(" + o.id + ")\">Товары</button>";
|
||||
// менять и удалять заказы может только админ, смотреть товары могут все
|
||||
if (isAdmin) {
|
||||
html += "<button class=\"action-button\" onclick=\"editOrder(" + o.id + ")\">Изменить</button>";
|
||||
html += "<button class=\"action-button delete\" onclick=\"deleteItem('orders'," + o.id + ")\">Удалить</button>";
|
||||
}
|
||||
html += "</div></td></tr>";
|
||||
}
|
||||
html += "</tbody></table></div>";
|
||||
|
|
@ -376,6 +456,10 @@ function renderOrders(orders) {
|
|||
// кнопка Добавить сверху
|
||||
addButton.addEventListener("click", () => {
|
||||
editingId = null; // значит добавляю а не изменяю
|
||||
// заказы создавать можно всем, остальное только админу
|
||||
if (currentPage !== "orders" && !needAdmin()) {
|
||||
return;
|
||||
}
|
||||
if (currentPage === "products") {
|
||||
openProductForm();
|
||||
}
|
||||
|
|
@ -466,6 +550,9 @@ async function saveProduct() {
|
|||
|
||||
// жму изменить на товаре
|
||||
async function editProduct(id) {
|
||||
if (!needAdmin()) { // только админ
|
||||
return;
|
||||
}
|
||||
const products = await api("/api/products"); // беру все
|
||||
const product = products.find((x) => x.id === id); // ищу нужный
|
||||
if (!product) {
|
||||
|
|
@ -511,6 +598,9 @@ async function saveCategory() {
|
|||
|
||||
// жму изменить на категории
|
||||
async function editCategory(id) {
|
||||
if (!needAdmin()) { // только админ
|
||||
return;
|
||||
}
|
||||
const categories = await api("/api/categories");
|
||||
const category = categories.find((x) => x.id === id);
|
||||
if (!category) {
|
||||
|
|
@ -560,6 +650,9 @@ async function saveWarehouse() {
|
|||
|
||||
// жму изменить на складе
|
||||
async function editWarehouse(id) {
|
||||
if (!needAdmin()) { // только админ
|
||||
return;
|
||||
}
|
||||
const warehouses = await api("/api/warehouses");
|
||||
const warehouse = warehouses.find((x) => x.id === id);
|
||||
if (!warehouse) {
|
||||
|
|
@ -622,6 +715,9 @@ async function saveStock() {
|
|||
|
||||
// жму изменить на остатке
|
||||
async function editStock(id) {
|
||||
if (!needAdmin()) { // только админ
|
||||
return;
|
||||
}
|
||||
const stocks = await api("/api/stocks");
|
||||
const stock = stocks.find((x) => x.id === id);
|
||||
if (!stock) {
|
||||
|
|
@ -642,10 +738,12 @@ async function openUserForm(user = null) {
|
|||
const title = user ? "Изменить пользователя" : "Добавить пользователя";
|
||||
const login = user ? escapeHtml(user.login) : "";
|
||||
const password = user ? escapeHtml(user.password) : "";
|
||||
const email = user ? escapeHtml(user.email || "") : "";
|
||||
openModal(title,
|
||||
"<div class=\"form\">" +
|
||||
"<label>Логин<input id=\"userLogin\" type=\"text\" value=\"" + login + "\"></label>" +
|
||||
"<label>Пароль<input id=\"userPassword\" type=\"text\" value=\"" + password + "\"></label>" +
|
||||
"<label>Почта<input id=\"userEmail\" type=\"email\" value=\"" + email + "\"></label>" +
|
||||
"<label>Роль<select id=\"userRole\">" + options + "</select></label>" +
|
||||
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||||
"<button type=\"button\" class=\"primary-button\" onclick=\"saveUser()\">Сохранить</button></div></div>"
|
||||
|
|
@ -656,13 +754,22 @@ async function openUserForm(user = null) {
|
|||
async function saveUser() {
|
||||
const login = document.getElementById("userLogin").value.trim();
|
||||
const password = document.getElementById("userPassword").value;
|
||||
const email = document.getElementById("userEmail").value.trim();
|
||||
const roleId = Number(document.getElementById("userRole").value);
|
||||
if (!login || !password) {
|
||||
if (!login || !password || !email) {
|
||||
showToast("Заполните все поля.");
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
showToast("Пароль должен быть минимум 8 символов.");
|
||||
return;
|
||||
}
|
||||
if (!isEmailOk(email)) {
|
||||
showToast("Почта неправильная, проверьте ее.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = { login, password, roleId };
|
||||
const data = { login, password, email, roleId };
|
||||
if (editingId) {
|
||||
await api("/api/users/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||||
showToast("Пользователь изменен.");
|
||||
|
|
@ -679,6 +786,9 @@ async function saveUser() {
|
|||
|
||||
// жму изменить на пользователе
|
||||
async function editUser(id) {
|
||||
if (!needAdmin()) { // только админ
|
||||
return;
|
||||
}
|
||||
const users = await api("/api/users");
|
||||
const user = users.find((x) => x.id === id);
|
||||
if (!user) {
|
||||
|
|
@ -743,6 +853,9 @@ async function saveOrder() {
|
|||
|
||||
// жму изменить на заказе
|
||||
async function editOrder(id) {
|
||||
if (!needAdmin()) { // только админ
|
||||
return;
|
||||
}
|
||||
const orders = await api("/api/orders");
|
||||
const order = orders.find((x) => x.id === id);
|
||||
if (!order) {
|
||||
|
|
@ -820,6 +933,9 @@ async function saveOrderItem(orderId) {
|
|||
|
||||
// удаляю что угодно (товар, заказ и тд)
|
||||
async function deleteItem(type, id) {
|
||||
if (!needAdmin()) { // удалять может только админ
|
||||
return;
|
||||
}
|
||||
// красивые названия для вопроса
|
||||
const names = {
|
||||
products: "товар",
|
||||
|
|
|
|||
|
|
@ -235,6 +235,15 @@ button {
|
|||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* поле поиска */
|
||||
.search-input {
|
||||
height: 44px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
/* синяя кнопка добавить */
|
||||
.primary-button {
|
||||
padding: 10px 16px;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user