Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Store.API/Controllers/EmployeesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
return Ok(ApiResponse<EmployeeDto>.Ok(employee));
}

[HttpGet("{id:guid}/360")]
public async Task<IActionResult> Get360ById(Guid id, CancellationToken ct)
{
var employee360 = await _employeeService.Get360ByIdAsync(id, ct);
if (employee360 is null) return NotFound(ApiResponse<object>.Fail("Employee not found."));
return Ok(ApiResponse<Employee360Dto>.Ok(employee360));
}

[HttpPost]
[Authorize(Roles = "Admin,Manager")]
public async Task<IActionResult> Create([FromBody] CreateEmployeeRequest request, CancellationToken ct)
Expand Down
25 changes: 25 additions & 0 deletions Store.API/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
return Ok(ApiResponse<UserDto>.Ok(user));
}

[HttpGet("{id:guid}/360")]
public async Task<IActionResult> Get360ById(Guid id, CancellationToken ct)
{
var user360 = await _userService.Get360ByIdAsync(id, ct);
if (user360 is null)
{
return NotFound(ApiErrorResponse.From("not_found", "User not found.", traceId: HttpContext.TraceIdentifier));
}

return Ok(ApiResponse<User360Dto>.Ok(user360));
}

[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> Create([FromBody] CreateUserRequest request, CancellationToken ct)
Expand Down Expand Up @@ -225,6 +237,19 @@ public async Task<IActionResult> RevokeAllSessions(CancellationToken ct)
return Ok(ApiResponse<object>.Ok(null!, "All sessions revoked successfully."));
}

[HttpPost("{id}/sessions/revoke")]
[Authorize(Roles = "Admin,Manager")]
public async Task<IActionResult> RevokeUserSessions(Guid id, CancellationToken ct)
{
var success = await _dispatcher.SendAsync(new RevokeAllSessionsCommand(id), ct);
if (!success)
{
return BadRequest(ApiErrorResponse.From("revoke_failed", "Failed to revoke sessions.", traceId: HttpContext.TraceIdentifier));
}

return Ok(ApiResponse<object>.Ok(null!, "All sessions revoked successfully."));
}

// --- Contact Change Endpoints ---
[HttpPost("profile/contact-change")]
public async Task<IActionResult> RequestContactChange([FromBody] CreateContactChangeDto request, CancellationToken ct)
Expand Down
1 change: 1 addition & 0 deletions Store.API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
builder.Services.AddScoped<Store.API.Infrastructure.Processing.IImageProcessorService, Store.API.Infrastructure.Processing.ImageProcessorService>();
builder.Services.AddScoped<Store.Models.Interfaces.Services.IWebAuthnService, Store.API.Services.WebAuthnService>();
builder.Services.AddMemoryCache();
builder.Services.AddHttpContextAccessor();

builder.Services.AddFido2(options =>
{
Expand Down
11 changes: 7 additions & 4 deletions Store.DbServices/Configurations/UserConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public void Configure(EntityTypeBuilder<User> builder)
.HasForeignKey<UserPassword>(p => p.UserId)
.OnDelete(DeleteBehavior.Cascade);

// One-to-one: User → Token
builder.HasOne(u => u.UserToken)
// One-to-many: User → Tokens
builder.HasMany(u => u.UserTokens)
.WithOne(t => t.User)
.HasForeignKey<UserToken>(t => t.UserId)
.HasForeignKey(t => t.UserId)
.OnDelete(DeleteBehavior.Cascade);

// Many-to-one: User → Role
Expand Down Expand Up @@ -66,11 +66,14 @@ public class UserTokenConfiguration : IEntityTypeConfiguration<UserToken>
public void Configure(EntityTypeBuilder<UserToken> builder)
{
builder.HasKey(t => t.UserTokenId);
builder.HasIndex(t => t.UserId).IsUnique();
builder.HasIndex(t => t.UserId);

builder.Property(t => t.Token).IsRequired().HasMaxLength(2000);
builder.Property(t => t.RefreshTokenHash).IsRequired().HasMaxLength(256);
builder.Property(t => t.ExpiryDate).IsRequired();
builder.Property(t => t.RefreshTokenExpiryDate).IsRequired();
builder.Property(t => t.IpAddress).HasMaxLength(45);
builder.Property(t => t.UserAgent).HasMaxLength(500);
builder.Property(t => t.DeviceName).HasMaxLength(100);
}
}
Loading
Loading