Стать исполнителем
This commit is contained in:
@@ -1,4 +1,20 @@
|
||||
services:
|
||||
migrations:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: nashel-migrations
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database=nashel;Username=postgres;Password=postgres
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
command: >
|
||||
sh -c "cd /app/src/Host && dotnet ef database update --project ../Modules/Identity/Infrastructure --startup-project . --context IdentityDbContext"
|
||||
networks:
|
||||
- nashel-network
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
@@ -14,6 +30,8 @@ services:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
migrations:
|
||||
condition: service_completed_successfully
|
||||
networks:
|
||||
- nashel-network
|
||||
|
||||
|
||||
@@ -104,6 +104,31 @@ builder.Services.AddCors(options =>
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Middleware для обработки исключений
|
||||
app.UseExceptionHandler(errorApp =>
|
||||
{
|
||||
errorApp.Run(async context =>
|
||||
{
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.ContentType = "application/json";
|
||||
|
||||
var exception = context.Features.Get<Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature>();
|
||||
if (exception != null)
|
||||
{
|
||||
var error = new
|
||||
{
|
||||
Message = exception.Error.Message,
|
||||
StackTrace = exception.Error.StackTrace
|
||||
};
|
||||
|
||||
Console.WriteLine($"[ERROR] {exception.Error.Message}");
|
||||
Console.WriteLine(exception.Error.StackTrace);
|
||||
|
||||
await context.Response.WriteAsJsonAsync(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Настройка конвейера HTTP-запросов.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5232",
|
||||
"applicationUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,80 +9,87 @@ public record BecomePerformerCommand : IRequest<Unit>
|
||||
{
|
||||
public string Description { get; init; } = default!;
|
||||
public List<string> CompetencyNames { get; init; } = default!;
|
||||
public bool Is24_7 { get; init; }
|
||||
public string? Location { get; init; }
|
||||
public string? CurrentLocation { get; init; }
|
||||
public bool IsAlwaysReady { get; init; }
|
||||
public List<string>? WorkingDays { get; init; }
|
||||
public string? WorkingHoursStart { get; init; }
|
||||
public string? WorkingHoursEnd { get; init; }
|
||||
public string? WorkingDays { get; init; }
|
||||
}
|
||||
|
||||
public class BecomePerformerCommandHandler : IRequestHandler<BecomePerformerCommand, Unit>
|
||||
{
|
||||
private readonly IAccountRepository _accountRepository;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly ICompetencyRepository _competencyRepository;
|
||||
|
||||
public BecomePerformerCommandHandler(IAccountRepository accountRepository, ICurrentUserService currentUserService)
|
||||
public BecomePerformerCommandHandler(
|
||||
IAccountRepository accountRepository,
|
||||
ICurrentUserService currentUserService,
|
||||
ICompetencyRepository competencyRepository)
|
||||
{
|
||||
_accountRepository = accountRepository;
|
||||
_currentUserService = currentUserService;
|
||||
_competencyRepository = competencyRepository;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(BecomePerformerCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine($"[BecomePerformerCommand] Starting to process request for user");
|
||||
|
||||
var userId = _currentUserService.UserId;
|
||||
if (userId == null)
|
||||
{
|
||||
Console.WriteLine("[BecomePerformerCommand] User is not authorized");
|
||||
throw new Exception("Неавторизован");
|
||||
}
|
||||
|
||||
Console.WriteLine($"[BecomePerformerCommand] UserId: {userId.Value}");
|
||||
|
||||
var account = await _accountRepository.GetByIdAsync(userId.Value, cancellationToken);
|
||||
if (account == null)
|
||||
{
|
||||
Console.WriteLine($"[BecomePerformerCommand] Account not found for userId: {userId.Value}");
|
||||
throw new Exception("Аккаунт не найден");
|
||||
}
|
||||
|
||||
// Создаем компетенции
|
||||
var competencies = request.CompetencyNames
|
||||
.Select(name => Competency.Create(name))
|
||||
.ToList();
|
||||
Console.WriteLine($"[BecomePerformerCommand] Account found: {account.Id}");
|
||||
|
||||
// Создаем график работы
|
||||
WorkSchedule? workSchedule = null;
|
||||
if (request.Is24_7 || request.IsAlwaysReady || (request.WorkingDays != null && request.WorkingDays.Any()))
|
||||
// Получаем или создаем компетенции
|
||||
var competencies = new List<Competency>();
|
||||
if (request.CompetencyNames != null)
|
||||
{
|
||||
string? workingDaysJson = null;
|
||||
string? workingHoursJson = null;
|
||||
|
||||
if (request.WorkingDays != null && request.WorkingDays.Any())
|
||||
Console.WriteLine($"[BecomePerformerCommand] Processing {request.CompetencyNames.Count} competencies");
|
||||
foreach (var name in request.CompetencyNames)
|
||||
{
|
||||
workingDaysJson = System.Text.Json.JsonSerializer.Serialize(request.WorkingDays);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.WorkingHoursStart) && !string.IsNullOrEmpty(request.WorkingHoursEnd))
|
||||
{
|
||||
workingHoursJson = System.Text.Json.JsonSerializer.Serialize(new
|
||||
var competency = await _competencyRepository.GetByNameAsync(name, cancellationToken);
|
||||
if (competency == null)
|
||||
{
|
||||
start = request.WorkingHoursStart,
|
||||
end = request.WorkingHoursEnd
|
||||
});
|
||||
Console.WriteLine($"[BecomePerformerCommand] Creating new competency: {name}");
|
||||
competency = Competency.Create(name);
|
||||
await _competencyRepository.AddAsync(competency, cancellationToken);
|
||||
}
|
||||
competencies.Add(competency);
|
||||
}
|
||||
}
|
||||
|
||||
WorkSchedule? workSchedule = null;
|
||||
if (!request.IsAlwaysReady && !string.IsNullOrEmpty(request.WorkingDays))
|
||||
{
|
||||
Console.WriteLine($"[BecomePerformerCommand] Creating work schedule");
|
||||
workSchedule = WorkSchedule.Create(
|
||||
request.Is24_7,
|
||||
request.IsAlwaysReady,
|
||||
workingDaysJson,
|
||||
workingHoursJson
|
||||
request.WorkingDays
|
||||
);
|
||||
}
|
||||
|
||||
// Обновляем данные исполнителя
|
||||
account.UpdatePerformerData(request.Description, competencies, workSchedule);
|
||||
|
||||
// Добавляем роль исполнителя
|
||||
Console.WriteLine($"[BecomePerformerCommand] Updating performer data");
|
||||
account.UpdatePerformerData(request.Description, competencies, request.Location, request.CurrentLocation, workSchedule);
|
||||
account.BecomePerformer();
|
||||
|
||||
Console.WriteLine($"[BecomePerformerCommand] Saving to database");
|
||||
await _accountRepository.UpdateAsync(account, cancellationToken);
|
||||
|
||||
Console.WriteLine($"[BecomePerformerCommand] Successfully completed");
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,12 +79,12 @@ public class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand, G
|
||||
var passwordHash = _passwordHasher.HashPassword(request.Password);
|
||||
var account = Account.Create(request.Phone, passwordHash);
|
||||
|
||||
// Добавляем роль, если она отличается от User
|
||||
if (request.Role != Role.User && request.Role != Role.Admin)
|
||||
// Добавляем роль только для Company. Newbie не добавляем - она добавляется через BecomePerformerCommand
|
||||
if (request.Role == Role.Company)
|
||||
{
|
||||
// Здесь можно добавить логику проверки прав, но для MVP разрешим
|
||||
account.Roles.Add(request.Role);
|
||||
}
|
||||
// Для Newbie роль не добавляем - пользователь остаётся User до заполнения формы становления исполнителем
|
||||
|
||||
var profile = UserProfile.Create(
|
||||
account.Id,
|
||||
|
||||
@@ -12,6 +12,8 @@ public record UpdateProfileCommand : IRequest
|
||||
public string? CompanyName { get; init; }
|
||||
public string? Inn { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public string? Location { get; init; }
|
||||
public string? CurrentLocation { get; init; }
|
||||
}
|
||||
|
||||
public class UpdateProfileCommandHandler : IRequestHandler<UpdateProfileCommand>
|
||||
@@ -50,7 +52,9 @@ public class UpdateProfileCommandHandler : IRequestHandler<UpdateProfileCommand>
|
||||
request.Patronymic,
|
||||
request.CompanyName,
|
||||
request.Inn,
|
||||
request.Description);
|
||||
request.Description,
|
||||
request.Location,
|
||||
request.CurrentLocation);
|
||||
|
||||
await _accountRepository.UpdateAsync(account, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -7,13 +7,12 @@ namespace Nashel.Modules.Identity.Application.Queries.GetProfile;
|
||||
public record GetProfileQuery : IRequest<ProfileResponse>;
|
||||
|
||||
public record WorkScheduleResponse(
|
||||
bool Is24_7,
|
||||
bool IsAlwaysReady,
|
||||
List<string>? WorkingDays,
|
||||
string? WorkingHoursStart,
|
||||
string? WorkingHoursEnd
|
||||
Dictionary<string, List<TimePeriod>>? SchedulePerDay
|
||||
);
|
||||
|
||||
public record TimePeriod(string Start, string End);
|
||||
|
||||
public record ProfileResponse(
|
||||
Guid Id,
|
||||
string Phone,
|
||||
@@ -24,6 +23,8 @@ public record ProfileResponse(
|
||||
string? CompanyName,
|
||||
string? Inn,
|
||||
string? Description,
|
||||
string? Location,
|
||||
string? CurrentLocation,
|
||||
string FullName,
|
||||
string? AvatarUrl,
|
||||
List<string> Competencies,
|
||||
@@ -64,31 +65,15 @@ public class GetProfileQueryHandler : IRequestHandler<GetProfileQuery, ProfileRe
|
||||
WorkScheduleResponse? workScheduleResponse = null;
|
||||
if (account.Profile.WorkSchedule != null)
|
||||
{
|
||||
var workingDays = account.Profile.WorkSchedule.WorkingDays != null
|
||||
? System.Text.Json.JsonSerializer.Deserialize<List<string>>(account.Profile.WorkSchedule.WorkingDays)
|
||||
: null;
|
||||
|
||||
string? workingHoursStart = null;
|
||||
string? workingHoursEnd = null;
|
||||
if (account.Profile.WorkSchedule.WorkingHours != null)
|
||||
Dictionary<string, List<TimePeriod>>? schedulePerDay = null;
|
||||
if (account.Profile.WorkSchedule.WorkingDays != null)
|
||||
{
|
||||
var workingHours = System.Text.Json.JsonSerializer.Deserialize<System.Text.Json.JsonElement>(account.Profile.WorkSchedule.WorkingHours);
|
||||
if (workingHours.TryGetProperty("start", out var start))
|
||||
{
|
||||
workingHoursStart = start.GetString();
|
||||
}
|
||||
if (workingHours.TryGetProperty("end", out var end))
|
||||
{
|
||||
workingHoursEnd = end.GetString();
|
||||
}
|
||||
schedulePerDay = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, List<TimePeriod>>>(account.Profile.WorkSchedule.WorkingDays);
|
||||
}
|
||||
|
||||
workScheduleResponse = new WorkScheduleResponse(
|
||||
account.Profile.WorkSchedule.Is24_7,
|
||||
account.Profile.WorkSchedule.IsAlwaysReady,
|
||||
workingDays,
|
||||
workingHoursStart,
|
||||
workingHoursEnd
|
||||
schedulePerDay
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,6 +87,8 @@ public class GetProfileQueryHandler : IRequestHandler<GetProfileQuery, ProfileRe
|
||||
account.Profile.CompanyName,
|
||||
account.Profile.Inn,
|
||||
account.Profile.Description,
|
||||
account.Profile.Location,
|
||||
account.Profile.CurrentLocation,
|
||||
fullName,
|
||||
account.Profile.AvatarUrl,
|
||||
account.Profile.Competencies.Select(c => c.Name).ToList(),
|
||||
|
||||
@@ -60,9 +60,9 @@ public class Account : AggregateRoot<Guid>
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePerformerData(string description, IEnumerable<Competency> competencies, WorkSchedule? workSchedule)
|
||||
public void UpdatePerformerData(string description, IEnumerable<Competency> competencies, string? location, string? currentLocation, WorkSchedule? workSchedule)
|
||||
{
|
||||
Profile.UpdatePerformerData(description, competencies, workSchedule);
|
||||
Profile.UpdatePerformerData(description, competencies, location, currentLocation, workSchedule);
|
||||
}
|
||||
|
||||
public void UpdateWorkSchedule(WorkSchedule workSchedule)
|
||||
|
||||
@@ -16,6 +16,8 @@ public class UserProfile : Entity<Guid>
|
||||
public string? Inn { get; private set; }
|
||||
public string? Description { get; private set; }
|
||||
public string? AvatarUrl { get; private set; }
|
||||
public string? Location { get; private set; }
|
||||
public string? CurrentLocation { get; private set; }
|
||||
public IReadOnlyCollection<Competency> Competencies => _competencies.AsReadOnly();
|
||||
public WorkSchedule? WorkSchedule { get; private set; }
|
||||
|
||||
@@ -41,7 +43,7 @@ public class UserProfile : Entity<Guid>
|
||||
return new UserProfile(id, firstName, lastName, patronymic, companyName, inn, description);
|
||||
}
|
||||
|
||||
public void Update(string firstName, string lastName, string? patronymic, string? companyName, string? inn, string? description)
|
||||
public void Update(string firstName, string lastName, string? patronymic, string? companyName, string? inn, string? description, string? location = null, string? currentLocation = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(description) && description.Length > MaxDescriptionLength)
|
||||
throw new ArgumentException($"Описание не может превышать {MaxDescriptionLength} символов", nameof(description));
|
||||
@@ -52,6 +54,8 @@ public class UserProfile : Entity<Guid>
|
||||
CompanyName = companyName;
|
||||
Inn = inn;
|
||||
Description = description;
|
||||
Location = location;
|
||||
CurrentLocation = currentLocation;
|
||||
}
|
||||
|
||||
public void UpdateAvatar(string? avatarUrl)
|
||||
@@ -70,7 +74,7 @@ public class UserProfile : Entity<Guid>
|
||||
_competencies.AddRange(competencyList);
|
||||
}
|
||||
|
||||
public void UpdatePerformerData(string description, IEnumerable<Competency> competencies, WorkSchedule? workSchedule)
|
||||
public void UpdatePerformerData(string description, IEnumerable<Competency> competencies, string? location, string? currentLocation, WorkSchedule? workSchedule)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(description) || description.Length < 50)
|
||||
throw new ArgumentException($"Описание должно содержать минимум 50 символов", nameof(description));
|
||||
@@ -79,6 +83,8 @@ public class UserProfile : Entity<Guid>
|
||||
throw new ArgumentException($"Описание не может превышать {MaxDescriptionLength} символов", nameof(description));
|
||||
|
||||
Description = description;
|
||||
Location = location;
|
||||
CurrentLocation = currentLocation;
|
||||
|
||||
var competencyList = competencies.ToList();
|
||||
if (competencyList.Count > MaxCompetencies)
|
||||
@@ -90,6 +96,11 @@ public class UserProfile : Entity<Guid>
|
||||
WorkSchedule = workSchedule;
|
||||
}
|
||||
|
||||
public void UpdateCurrentLocation(string? currentLocation)
|
||||
{
|
||||
CurrentLocation = currentLocation;
|
||||
}
|
||||
|
||||
public void SetWorkSchedule(WorkSchedule workSchedule)
|
||||
{
|
||||
WorkSchedule = workSchedule;
|
||||
|
||||
@@ -3,47 +3,39 @@ namespace Nashel.Modules.Identity.Domain.Entities;
|
||||
public class WorkSchedule
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
public bool Is24_7 { get; private set; }
|
||||
public bool IsAlwaysReady { get; private set; }
|
||||
public string? WorkingDays { get; private set; } // JSON-массив дней недели: ["Mon", "Tue", ...]
|
||||
public string? WorkingHours { get; private set; } // JSON: {"start": "09:00", "end": "18:00"}
|
||||
public string? WorkingDays { get; private set; } // JSON: {"Понедельник": [{"start": "09:00", "end": "18:00"}], ...}
|
||||
|
||||
// EF Core constructor
|
||||
private WorkSchedule() { }
|
||||
|
||||
private WorkSchedule(bool is24_7, bool isAlwaysReady, string? workingDays, string? workingHours)
|
||||
private WorkSchedule(bool isAlwaysReady, string? workingDays)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Is24_7 = is24_7;
|
||||
IsAlwaysReady = isAlwaysReady;
|
||||
WorkingDays = workingDays;
|
||||
WorkingHours = workingHours;
|
||||
}
|
||||
|
||||
public static WorkSchedule Create(bool is24_7, bool isAlwaysReady, string? workingDays, string? workingHours)
|
||||
public static WorkSchedule Create(bool isAlwaysReady, string? workingDays)
|
||||
{
|
||||
// Если 24/7 или Always Ready, то дни и часы не нужны
|
||||
if (is24_7 || isAlwaysReady)
|
||||
// Если Always Ready, то расписание не нужно
|
||||
if (isAlwaysReady)
|
||||
{
|
||||
workingDays = null;
|
||||
workingHours = null;
|
||||
}
|
||||
|
||||
return new WorkSchedule(is24_7, isAlwaysReady, workingDays, workingHours);
|
||||
return new WorkSchedule(isAlwaysReady, workingDays);
|
||||
}
|
||||
|
||||
public void Update(bool is24_7, bool isAlwaysReady, string? workingDays, string? workingHours)
|
||||
public void Update(bool isAlwaysReady, string? workingDays)
|
||||
{
|
||||
// Если 24/7 или Always Ready, то дни и часы не нужны
|
||||
if (is24_7 || isAlwaysReady)
|
||||
// Если Always Ready, то расписание не нужно
|
||||
if (isAlwaysReady)
|
||||
{
|
||||
workingDays = null;
|
||||
workingHours = null;
|
||||
}
|
||||
|
||||
Is24_7 = is24_7;
|
||||
IsAlwaysReady = isAlwaysReady;
|
||||
WorkingDays = workingDays;
|
||||
WorkingHours = workingHours;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ public static class DependencyInjection
|
||||
public static IServiceCollection AddIdentityModule(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddScoped<IAccountRepository, AccountRepository>();
|
||||
services.AddScoped<ICompetencyRepository, CompetencyRepository>();
|
||||
services.AddScoped<IJwtTokenGenerator, JwtTokenGenerator>();
|
||||
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||
services.AddScoped<IPasswordHasher, PasswordHasher>();
|
||||
|
||||
@@ -12,16 +12,10 @@ public class WorkScheduleConfiguration : IEntityTypeConfiguration<WorkSchedule>
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Is24_7)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.IsAlwaysReady)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.WorkingDays)
|
||||
.HasMaxLength(100); // JSON-массив дней недели
|
||||
|
||||
builder.Property(x => x.WorkingHours)
|
||||
.HasMaxLength(100); // JSON с часами работы
|
||||
.HasMaxLength(2000); // JSON с расписанием для каждого дня
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Nashel.Modules.Identity.Infrastructure.Persistence;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nashel.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
[DbContext(typeof(IdentityDbContext))]
|
||||
[Migration("20260216120252_AddCurrentLocationAndRemoveIs24_7")]
|
||||
partial class AddCurrentLocationAndRemoveIs24_7
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Nashel.Modules.Identity.Domain.Aggregates.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Phone")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<string>("Roles")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Phone")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Accounts", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nashel.Modules.Identity.Domain.Aggregates.UserProfile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AvatarUrl")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<string>("CompanyName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("CurrentLocation")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Inn")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Patronymic")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("UserProfiles", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nashel.Modules.Identity.Domain.Entities.Competency", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_Competencies_Name");
|
||||
|
||||
b.ToTable("Competencies", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nashel.Modules.Identity.Domain.Entities.WorkSchedule", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsAlwaysReady")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("WorkingDays")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("WorkSchedules", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UserProfileCompetencies", b =>
|
||||
{
|
||||
b.Property<Guid>("UserProfileId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("CompetencyId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("UserProfileId", "CompetencyId");
|
||||
|
||||
b.HasIndex("CompetencyId");
|
||||
|
||||
b.ToTable("UserProfileCompetencies", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nashel.Modules.Identity.Domain.Aggregates.UserProfile", b =>
|
||||
{
|
||||
b.HasOne("Nashel.Modules.Identity.Domain.Aggregates.Account", null)
|
||||
.WithOne("Profile")
|
||||
.HasForeignKey("Nashel.Modules.Identity.Domain.Aggregates.UserProfile", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nashel.Modules.Identity.Domain.Entities.WorkSchedule", b =>
|
||||
{
|
||||
b.HasOne("Nashel.Modules.Identity.Domain.Aggregates.UserProfile", null)
|
||||
.WithOne("WorkSchedule")
|
||||
.HasForeignKey("Nashel.Modules.Identity.Domain.Entities.WorkSchedule", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UserProfileCompetencies", b =>
|
||||
{
|
||||
b.HasOne("Nashel.Modules.Identity.Domain.Entities.Competency", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("CompetencyId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Nashel.Modules.Identity.Domain.Aggregates.UserProfile", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserProfileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nashel.Modules.Identity.Domain.Aggregates.Account", b =>
|
||||
{
|
||||
b.Navigation("Profile")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nashel.Modules.Identity.Domain.Aggregates.UserProfile", b =>
|
||||
{
|
||||
b.Navigation("WorkSchedule");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nashel.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCurrentLocationAndRemoveIs24_7 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Is24_7",
|
||||
schema: "identity",
|
||||
table: "WorkSchedules");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WorkingHours",
|
||||
schema: "identity",
|
||||
table: "WorkSchedules");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "WorkingDays",
|
||||
schema: "identity",
|
||||
table: "WorkSchedules",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(100)",
|
||||
oldMaxLength: 100,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CurrentLocation",
|
||||
schema: "identity",
|
||||
table: "UserProfiles",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Location",
|
||||
schema: "identity",
|
||||
table: "UserProfiles",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CurrentLocation",
|
||||
schema: "identity",
|
||||
table: "UserProfiles");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Location",
|
||||
schema: "identity",
|
||||
table: "UserProfiles");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "WorkingDays",
|
||||
schema: "identity",
|
||||
table: "WorkSchedules",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Is24_7",
|
||||
schema: "identity",
|
||||
table: "WorkSchedules",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WorkingHours",
|
||||
schema: "identity",
|
||||
table: "WorkSchedules",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,9 @@ namespace Nashel.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("CurrentLocation")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
@@ -80,6 +83,9 @@ namespace Nashel.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Patronymic")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
@@ -114,19 +120,12 @@ namespace Nashel.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("Is24_7")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsAlwaysReady")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("WorkingDays")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("WorkingHours")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Nashel.Modules.Identity.Domain.Aggregates;
|
||||
using Nashel.Modules.Identity.Domain.Entities;
|
||||
using Nashel.Modules.Identity.Domain.Repositories;
|
||||
using Nashel.Modules.Identity.Infrastructure.Persistence;
|
||||
|
||||
@@ -20,6 +21,12 @@ public class AccountRepository : IAccountRepository
|
||||
if (account.Profile != null)
|
||||
{
|
||||
await _context.UserProfiles.AddAsync(account.Profile, cancellationToken);
|
||||
|
||||
// Добавляем WorkSchedule если есть
|
||||
if (account.Profile.WorkSchedule != null)
|
||||
{
|
||||
await _context.WorkSchedules.AddAsync(account.Profile.WorkSchedule, cancellationToken);
|
||||
}
|
||||
}
|
||||
await _context.Accounts.AddAsync(account, cancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
@@ -30,6 +37,8 @@ public class AccountRepository : IAccountRepository
|
||||
return await _context.Accounts
|
||||
.Include(a => a.Profile)
|
||||
.ThenInclude(p => p.Competencies)
|
||||
.Include(a => a.Profile)
|
||||
.ThenInclude(p => p.WorkSchedule)
|
||||
.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -42,12 +51,88 @@ public class AccountRepository : IAccountRepository
|
||||
|
||||
public async Task UpdateAsync(Account account, CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine($"[AccountRepository.UpdateAsync] Starting update for account: {account.Id}");
|
||||
|
||||
// Явно обновляем профиль, чтобы EF Core сохранил изменения в таблицу UserProfiles
|
||||
if (account.Profile != null)
|
||||
{
|
||||
_context.UserProfiles.Update(account.Profile);
|
||||
Console.WriteLine($"[AccountRepository.UpdateAsync] Profile found: {account.Profile.Id}");
|
||||
|
||||
// Загружаем текущий профиль с компетенциями
|
||||
var existingProfile = await _context.UserProfiles
|
||||
.Include(p => p.Competencies)
|
||||
.Include(p => p.WorkSchedule)
|
||||
.FirstOrDefaultAsync(p => p.Id == account.Profile.Id, cancellationToken);
|
||||
|
||||
if (existingProfile != null)
|
||||
{
|
||||
Console.WriteLine($"[AccountRepository.UpdateAsync] Existing profile loaded");
|
||||
|
||||
// Обновляем свойства профиля
|
||||
existingProfile.Update(
|
||||
account.Profile.FirstName,
|
||||
account.Profile.LastName,
|
||||
account.Profile.Patronymic,
|
||||
account.Profile.CompanyName,
|
||||
account.Profile.Inn,
|
||||
account.Profile.Description,
|
||||
account.Profile.Location,
|
||||
account.Profile.CurrentLocation
|
||||
);
|
||||
|
||||
// Обновляем компетенции через доменный метод
|
||||
// Поскольку компетенции уже сохранены через CompetencyRepository,
|
||||
// просто загружаем их из БД по Id для правильной работы EF Core
|
||||
var competencies = new List<Competency>();
|
||||
if (account.Profile.Competencies != null && account.Profile.Competencies.Any())
|
||||
{
|
||||
var competencyIds = account.Profile.Competencies
|
||||
.Select(c => c.Id)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
Console.WriteLine($"[AccountRepository.UpdateAsync] Loading {competencyIds.Count} competencies from DB");
|
||||
competencies = await _context.Competencies
|
||||
.Where(c => competencyIds.Contains(c.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
existingProfile.UpdateCompetencies(competencies);
|
||||
|
||||
// Обновляем WorkSchedule
|
||||
if (account.Profile.WorkSchedule != null)
|
||||
{
|
||||
if (existingProfile.WorkSchedule != null)
|
||||
{
|
||||
// Обновляем существующий WorkSchedule
|
||||
existingProfile.WorkSchedule.Update(
|
||||
account.Profile.WorkSchedule.IsAlwaysReady,
|
||||
account.Profile.WorkSchedule.WorkingDays
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Создаём новый WorkSchedule
|
||||
var newSchedule = WorkSchedule.Create(
|
||||
account.Profile.WorkSchedule.IsAlwaysReady,
|
||||
account.Profile.WorkSchedule.WorkingDays
|
||||
);
|
||||
// EF Core сам установит правильную связь через Id профиля
|
||||
existingProfile.SetWorkSchedule(newSchedule);
|
||||
_context.WorkSchedules.Add(newSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
_context.UserProfiles.Update(existingProfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[AccountRepository.UpdateAsync] Existing profile not found, using account profile");
|
||||
_context.UserProfiles.Update(account.Profile);
|
||||
}
|
||||
}
|
||||
_context.Accounts.Update(account);
|
||||
Console.WriteLine($"[AccountRepository.UpdateAsync] Saving changes to database");
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
Console.WriteLine($"[AccountRepository.UpdateAsync] Successfully completed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Nashel.Modules.Identity.Domain.Entities;
|
||||
using Nashel.Modules.Identity.Domain.Repositories;
|
||||
using Nashel.Modules.Identity.Infrastructure.Persistence;
|
||||
|
||||
namespace Nashel.Modules.Identity.Infrastructure.Repositories;
|
||||
|
||||
public class CompetencyRepository : ICompetencyRepository
|
||||
{
|
||||
private readonly IdentityDbContext _context;
|
||||
|
||||
public CompetencyRepository(IdentityDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Competency?> GetByNameAsync(string name, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Competencies
|
||||
.FirstOrDefaultAsync(c => c.Name == name, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task AddAsync(Competency competency, CancellationToken cancellationToken)
|
||||
{
|
||||
await _context.Competencies.AddAsync(competency, cancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Competency>> GetAllAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Competencies
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -58,16 +58,23 @@ public static class IdentityEndpoints
|
||||
|
||||
profileGroup.MapPost("/become-performer", async (BecomePerformerRequest request, ISender sender) =>
|
||||
{
|
||||
Console.WriteLine($"[BecomePerformerEndpoint] Request received");
|
||||
Console.WriteLine($"[BecomePerformerEndpoint] Description: {request.Description?.Substring(0, Math.Min(50, request.Description.Length))}...");
|
||||
Console.WriteLine($"[BecomePerformerEndpoint] Competencies count: {request.Competencies?.Count ?? 0}");
|
||||
Console.WriteLine($"[BecomePerformerEndpoint] Location: {request.Location}");
|
||||
Console.WriteLine($"[BecomePerformerEndpoint] IsAlwaysReady: {request.IsAlwaysReady}");
|
||||
|
||||
await sender.Send(new BecomePerformerCommand
|
||||
{
|
||||
Description = request.Description,
|
||||
CompetencyNames = request.Competencies,
|
||||
Is24_7 = request.Is24_7,
|
||||
Location = request.Location,
|
||||
CurrentLocation = request.CurrentLocation,
|
||||
IsAlwaysReady = request.IsAlwaysReady,
|
||||
WorkingDays = request.WorkingDays,
|
||||
WorkingHoursStart = request.WorkingHoursStart,
|
||||
WorkingHoursEnd = request.WorkingHoursEnd
|
||||
WorkingDays = request.WorkingDays
|
||||
});
|
||||
|
||||
Console.WriteLine($"[BecomePerformerEndpoint] Successfully completed");
|
||||
return Results.Ok();
|
||||
})
|
||||
.WithName("BecomePerformer")
|
||||
@@ -107,9 +114,8 @@ public record UpdateCompetenciesRequest(List<string> Competencies);
|
||||
public record BecomePerformerRequest(
|
||||
string Description,
|
||||
List<string> Competencies,
|
||||
bool Is24_7,
|
||||
string? Location,
|
||||
string? CurrentLocation,
|
||||
bool IsAlwaysReady,
|
||||
List<string>? WorkingDays,
|
||||
string? WorkingHoursStart,
|
||||
string? WorkingHoursEnd
|
||||
string? WorkingDays
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user