@onahhas/hello-dev 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +149 -11
  2. package/backend/Controllers/AccountController.cs +100 -0
  3. package/backend/Controllers/ActivityController.cs +44 -0
  4. package/backend/Controllers/AuthController.cs +127 -0
  5. package/backend/Controllers/LookupController.cs +46 -0
  6. package/backend/Controllers/TasksController.cs +652 -0
  7. package/backend/Controllers/UsersController.cs +181 -0
  8. package/backend/Data/AppDbContext.cs +93 -0
  9. package/backend/Data/DbSeeder.cs +122 -0
  10. package/backend/DevTasks.Api.csproj +13 -0
  11. package/backend/Dtos/ActivityDtos.cs +12 -0
  12. package/backend/Dtos/AuthDtos.cs +37 -0
  13. package/backend/Dtos/TaskDtos.cs +104 -0
  14. package/backend/Dtos/UserDtos.cs +29 -0
  15. package/backend/Enums/EditRequestStatus.cs +8 -0
  16. package/backend/Enums/TaskPriority.cs +8 -0
  17. package/backend/Enums/TaskState.cs +9 -0
  18. package/backend/Enums/TaskVisibility.cs +7 -0
  19. package/backend/Enums/UserRole.cs +7 -0
  20. package/backend/Extensions/ClaimsPrincipalExtensions.cs +23 -0
  21. package/backend/Models/ActivityLog.cs +12 -0
  22. package/backend/Models/AppUser.cs +17 -0
  23. package/backend/Models/TaskEditRequest.cs +31 -0
  24. package/backend/Models/TaskItem.cs +25 -0
  25. package/backend/Program.cs +138 -0
  26. package/backend/Properties/launchSettings.json +13 -0
  27. package/backend/Services/ActivityService.cs +28 -0
  28. package/backend/Services/PasswordHasher.cs +58 -0
  29. package/backend/Services/TokenService.cs +49 -0
  30. package/backend/appsettings.Development.json +10 -0
  31. package/backend/appsettings.json +24 -0
  32. package/frontend/index.html +12 -0
  33. package/frontend/package-lock.json +1769 -0
  34. package/frontend/package.json +23 -0
  35. package/frontend/src/App.tsx +40 -0
  36. package/frontend/src/api/http.ts +75 -0
  37. package/frontend/src/auth/AuthContext.tsx +101 -0
  38. package/frontend/src/components/EditRequestModal.tsx +139 -0
  39. package/frontend/src/components/EditRequestsPanel.tsx +94 -0
  40. package/frontend/src/components/Layout.tsx +76 -0
  41. package/frontend/src/components/PageHeader.tsx +21 -0
  42. package/frontend/src/components/ProtectedRoute.tsx +14 -0
  43. package/frontend/src/components/StatCard.tsx +15 -0
  44. package/frontend/src/components/TaskCard.tsx +83 -0
  45. package/frontend/src/components/TaskDetailsModal.tsx +45 -0
  46. package/frontend/src/components/TaskFilters.tsx +67 -0
  47. package/frontend/src/components/TaskModal.tsx +159 -0
  48. package/frontend/src/components/TaskTable.tsx +68 -0
  49. package/frontend/src/components/UserModal.tsx +124 -0
  50. package/frontend/src/main.tsx +19 -0
  51. package/frontend/src/pages/ActivityPage.tsx +37 -0
  52. package/frontend/src/pages/BoardPage.tsx +75 -0
  53. package/frontend/src/pages/CalendarPage.tsx +101 -0
  54. package/frontend/src/pages/DashboardPage.tsx +131 -0
  55. package/frontend/src/pages/LoginPage.tsx +69 -0
  56. package/frontend/src/pages/ProfilePage.tsx +111 -0
  57. package/frontend/src/pages/PublicTasksPage.tsx +99 -0
  58. package/frontend/src/pages/RegisterPage.tsx +80 -0
  59. package/frontend/src/pages/TasksPage.tsx +135 -0
  60. package/frontend/src/pages/UsersPage.tsx +86 -0
  61. package/frontend/src/styles.css +596 -0
  62. package/frontend/src/theme.tsx +49 -0
  63. package/frontend/src/types.ts +78 -0
  64. package/frontend/src/utils/date.ts +30 -0
  65. package/frontend/src/utils/labels.ts +3 -0
  66. package/frontend/src/vite-env.d.ts +1 -0
  67. package/frontend/tsconfig.json +21 -0
  68. package/frontend/vite.config.ts +15 -0
  69. package/package.json +22 -9
  70. package/index.js +0 -7
@@ -0,0 +1,181 @@
1
+ using DevTasks.Api.Data;
2
+ using DevTasks.Api.Dtos;
3
+ using DevTasks.Api.Enums;
4
+ using DevTasks.Api.Extensions;
5
+ using DevTasks.Api.Models;
6
+ using DevTasks.Api.Services;
7
+ using Microsoft.AspNetCore.Authorization;
8
+ using Microsoft.AspNetCore.Mvc;
9
+ using Microsoft.EntityFrameworkCore;
10
+
11
+ namespace DevTasks.Api.Controllers;
12
+
13
+ [ApiController]
14
+ [Route("api/[controller]")]
15
+ [Authorize(Roles = nameof(UserRole.Admin))]
16
+ public sealed class UsersController : ControllerBase
17
+ {
18
+ private readonly AppDbContext _db;
19
+ private readonly PasswordHasher _passwordHasher;
20
+ private readonly ActivityService _activity;
21
+
22
+ public UsersController(
23
+ AppDbContext db,
24
+ PasswordHasher passwordHasher,
25
+ ActivityService activity)
26
+ {
27
+ _db = db;
28
+ _passwordHasher = passwordHasher;
29
+ _activity = activity;
30
+ }
31
+
32
+ [HttpGet]
33
+ public async Task<ActionResult<IReadOnlyList<UserResponse>>> GetUsers()
34
+ {
35
+ var users = await _db.Users
36
+ .AsNoTracking()
37
+ .OrderByDescending(x => x.CreatedAt)
38
+ .ToListAsync();
39
+
40
+ return Ok(users.Select(ToResponse).ToList());
41
+ }
42
+
43
+ [HttpPost]
44
+ public async Task<ActionResult<UserResponse>> CreateUser(CreateUserRequest request)
45
+ {
46
+ var validation = ValidateCreate(request);
47
+ if (validation is not null)
48
+ {
49
+ return BadRequest(new { message = validation });
50
+ }
51
+
52
+ var email = request.Email.Trim().ToLowerInvariant();
53
+ if (await _db.Users.AnyAsync(x => x.Email == email))
54
+ {
55
+ return Conflict(new { message = "Email is already used." });
56
+ }
57
+
58
+ var user = new AppUser
59
+ {
60
+ FullName = request.FullName.Trim(),
61
+ Email = email,
62
+ PasswordHash = _passwordHasher.Hash(request.Password),
63
+ Role = request.Role,
64
+ IsActive = request.IsActive,
65
+ CreatedAt = DateTime.UtcNow
66
+ };
67
+
68
+ _db.Users.Add(user);
69
+ await _db.SaveChangesAsync();
70
+ await _activity.AddAsync("User added", "User", user.Id.ToString(), User.GetUserId());
71
+
72
+ return CreatedAtAction(nameof(GetUsers), new { id = user.Id }, ToResponse(user));
73
+ }
74
+
75
+ [HttpPut("{id:guid}")]
76
+ public async Task<ActionResult<UserResponse>> UpdateUser(Guid id, UpdateUserRequest request)
77
+ {
78
+ var user = await _db.Users.FindAsync(id);
79
+ if (user is null)
80
+ {
81
+ return NotFound(new { message = "User was not found." });
82
+ }
83
+
84
+ if (!string.IsNullOrWhiteSpace(request.Email))
85
+ {
86
+ var email = request.Email.Trim().ToLowerInvariant();
87
+ var used = await _db.Users.AnyAsync(x => x.Id != id && x.Email == email);
88
+ if (used)
89
+ {
90
+ return Conflict(new { message = "Email is already used." });
91
+ }
92
+
93
+ user.Email = email;
94
+ }
95
+
96
+ if (!string.IsNullOrWhiteSpace(request.FullName))
97
+ {
98
+ user.FullName = request.FullName.Trim();
99
+ }
100
+
101
+ if (!string.IsNullOrWhiteSpace(request.Password))
102
+ {
103
+ if (request.Password.Length < 6)
104
+ {
105
+ return BadRequest(new { message = "Password must be at least 6 characters." });
106
+ }
107
+
108
+ user.PasswordHash = _passwordHasher.Hash(request.Password);
109
+ }
110
+
111
+ if (request.Role.HasValue)
112
+ {
113
+ user.Role = request.Role.Value;
114
+ }
115
+
116
+ if (request.IsActive.HasValue)
117
+ {
118
+ user.IsActive = request.IsActive.Value;
119
+ }
120
+
121
+ await _db.SaveChangesAsync();
122
+ await _activity.AddAsync("User edited", "User", user.Id.ToString(), User.GetUserId());
123
+
124
+ return Ok(ToResponse(user));
125
+ }
126
+
127
+ [HttpDelete("{id:guid}")]
128
+ public async Task<IActionResult> DeleteUser(Guid id)
129
+ {
130
+ var currentUserId = User.GetUserId();
131
+ if (id == currentUserId)
132
+ {
133
+ return BadRequest(new { message = "You cannot deactivate your own user." });
134
+ }
135
+
136
+ var user = await _db.Users.FindAsync(id);
137
+ if (user is null)
138
+ {
139
+ return NotFound(new { message = "User was not found." });
140
+ }
141
+
142
+ user.IsActive = false;
143
+ await _db.SaveChangesAsync();
144
+ await _activity.AddAsync("User deactivated", "User", user.Id.ToString(), currentUserId);
145
+
146
+ return NoContent();
147
+ }
148
+
149
+ private static UserResponse ToResponse(AppUser user)
150
+ {
151
+ return new UserResponse
152
+ {
153
+ Id = user.Id,
154
+ FullName = user.FullName,
155
+ Email = user.Email,
156
+ Role = user.Role,
157
+ IsActive = user.IsActive,
158
+ CreatedAt = user.CreatedAt
159
+ };
160
+ }
161
+
162
+ private static string? ValidateCreate(CreateUserRequest request)
163
+ {
164
+ if (string.IsNullOrWhiteSpace(request.FullName))
165
+ {
166
+ return "Full name is required.";
167
+ }
168
+
169
+ if (string.IsNullOrWhiteSpace(request.Email) || !request.Email.Contains('@'))
170
+ {
171
+ return "Valid email is required.";
172
+ }
173
+
174
+ if (string.IsNullOrWhiteSpace(request.Password) || request.Password.Length < 6)
175
+ {
176
+ return "Password must be at least 6 characters.";
177
+ }
178
+
179
+ return null;
180
+ }
181
+ }
@@ -0,0 +1,93 @@
1
+ using DevTasks.Api.Enums;
2
+ using DevTasks.Api.Models;
3
+ using Microsoft.EntityFrameworkCore;
4
+
5
+ namespace DevTasks.Api.Data;
6
+
7
+ public sealed class AppDbContext : DbContext
8
+ {
9
+ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
10
+ {
11
+ }
12
+
13
+ public DbSet<AppUser> Users => Set<AppUser>();
14
+ public DbSet<TaskItem> TaskItems => Set<TaskItem>();
15
+ public DbSet<TaskEditRequest> TaskEditRequests => Set<TaskEditRequest>();
16
+ public DbSet<ActivityLog> ActivityLogs => Set<ActivityLog>();
17
+
18
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
19
+ {
20
+ base.OnModelCreating(modelBuilder);
21
+
22
+ modelBuilder.Entity<AppUser>(entity =>
23
+ {
24
+ entity.HasKey(x => x.Id);
25
+ entity.Property(x => x.FullName).HasMaxLength(120).IsRequired();
26
+ entity.Property(x => x.Email).HasMaxLength(180).IsRequired();
27
+ entity.HasIndex(x => x.Email).IsUnique();
28
+ entity.Property(x => x.PasswordHash).IsRequired();
29
+ entity.Property(x => x.Role).HasConversion<string>().HasMaxLength(20);
30
+ });
31
+
32
+ modelBuilder.Entity<TaskItem>(entity =>
33
+ {
34
+ entity.HasKey(x => x.Id);
35
+ entity.Property(x => x.Id).ValueGeneratedOnAdd();
36
+ entity.Property(x => x.Title).HasMaxLength(160).IsRequired();
37
+ entity.Property(x => x.Description).HasMaxLength(4000);
38
+ entity.Property(x => x.Status).HasConversion<string>().HasMaxLength(30);
39
+ entity.Property(x => x.Priority).HasConversion<string>().HasMaxLength(20);
40
+ entity.Property(x => x.Visibility).HasConversion<string>().HasMaxLength(20);
41
+
42
+ entity.HasOne(x => x.CreatedByUser)
43
+ .WithMany(x => x.CreatedTasks)
44
+ .HasForeignKey(x => x.CreatedByUserId)
45
+ .OnDelete(DeleteBehavior.Restrict);
46
+
47
+ entity.HasOne(x => x.AssignedToUser)
48
+ .WithMany(x => x.AssignedTasks)
49
+ .HasForeignKey(x => x.AssignedToUserId)
50
+ .OnDelete(DeleteBehavior.SetNull);
51
+ });
52
+
53
+ modelBuilder.Entity<TaskEditRequest>(entity =>
54
+ {
55
+ entity.HasKey(x => x.Id);
56
+ entity.Property(x => x.Title).HasMaxLength(160);
57
+ entity.Property(x => x.Description).HasMaxLength(4000);
58
+ entity.Property(x => x.Status).HasConversion<string>().HasMaxLength(30);
59
+ entity.Property(x => x.Priority).HasConversion<string>().HasMaxLength(20);
60
+ entity.Property(x => x.Visibility).HasConversion<string>().HasMaxLength(20);
61
+ entity.Property(x => x.StatusOfRequest).HasConversion<string>().HasMaxLength(20);
62
+ entity.Property(x => x.OwnerNote).HasMaxLength(800);
63
+
64
+ entity.HasOne(x => x.TaskItem)
65
+ .WithMany(x => x.EditRequests)
66
+ .HasForeignKey(x => x.TaskItemId)
67
+ .OnDelete(DeleteBehavior.Cascade);
68
+
69
+ entity.HasOne(x => x.RequestedByUser)
70
+ .WithMany()
71
+ .HasForeignKey(x => x.RequestedByUserId)
72
+ .OnDelete(DeleteBehavior.Restrict);
73
+
74
+ entity.HasOne(x => x.AssignedToUser)
75
+ .WithMany()
76
+ .HasForeignKey(x => x.AssignedToUserId)
77
+ .OnDelete(DeleteBehavior.SetNull);
78
+ });
79
+
80
+ modelBuilder.Entity<ActivityLog>(entity =>
81
+ {
82
+ entity.HasKey(x => x.Id);
83
+ entity.Property(x => x.Action).HasMaxLength(250).IsRequired();
84
+ entity.Property(x => x.EntityName).HasMaxLength(80).IsRequired();
85
+ entity.Property(x => x.EntityId).HasMaxLength(80).IsRequired();
86
+
87
+ entity.HasOne(x => x.User)
88
+ .WithMany()
89
+ .HasForeignKey(x => x.UserId)
90
+ .OnDelete(DeleteBehavior.Restrict);
91
+ });
92
+ }
93
+ }
@@ -0,0 +1,122 @@
1
+ using DevTasks.Api.Enums;
2
+ using DevTasks.Api.Models;
3
+ using DevTasks.Api.Services;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace DevTasks.Api.Data;
7
+
8
+ public static class DbSeeder
9
+ {
10
+ public static async Task SeedAsync(AppDbContext db, PasswordHasher hasher)
11
+ {
12
+ if (await db.Users.AnyAsync())
13
+ {
14
+ return;
15
+ }
16
+
17
+ var admin = new AppUser
18
+ {
19
+ Id = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
20
+ FullName = "System Admin",
21
+ Email = "admin@devtasks.local",
22
+ PasswordHash = hasher.Hash("Admin123!"),
23
+ Role = UserRole.Admin,
24
+ IsActive = true,
25
+ CreatedAt = DateTime.UtcNow
26
+ };
27
+
28
+ var programmer = new AppUser
29
+ {
30
+ Id = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"),
31
+ FullName = "Demo Programmer",
32
+ Email = "programmer@devtasks.local",
33
+ PasswordHash = hasher.Hash("User123!"),
34
+ Role = UserRole.User,
35
+ IsActive = true,
36
+ CreatedAt = DateTime.UtcNow
37
+ };
38
+
39
+ db.Users.AddRange(admin, programmer);
40
+
41
+ var now = DateTime.UtcNow;
42
+ var tasks = new List<TaskItem>
43
+ {
44
+ new()
45
+ {
46
+ Title = "Build login screen",
47
+ Description = "Create the minimal auth page and connect it with JWT login.",
48
+ Status = TaskState.Done,
49
+ Priority = TaskPriority.High,
50
+ Visibility = TaskVisibility.Public,
51
+ CreatedByUserId = admin.Id,
52
+ AssignedToUserId = programmer.Id,
53
+ DueDate = now.AddDays(-1),
54
+ CreatedAt = now.AddDays(-6),
55
+ UpdatedAt = now.AddDays(-2)
56
+ },
57
+ new()
58
+ {
59
+ Title = "Create task board",
60
+ Description = "Add drag and drop between Todo, In Progress, Done, and Cancelled.",
61
+ Status = TaskState.InProgress,
62
+ Priority = TaskPriority.High,
63
+ Visibility = TaskVisibility.Public,
64
+ CreatedByUserId = programmer.Id,
65
+ AssignedToUserId = programmer.Id,
66
+ DueDate = now.AddDays(3),
67
+ CreatedAt = now.AddDays(-4),
68
+ UpdatedAt = now.AddDays(-1)
69
+ },
70
+ new()
71
+ {
72
+ Title = "Write API validation notes",
73
+ Description = "Document required fields and possible validation errors.",
74
+ Status = TaskState.Todo,
75
+ Priority = TaskPriority.Medium,
76
+ Visibility = TaskVisibility.Private,
77
+ CreatedByUserId = programmer.Id,
78
+ AssignedToUserId = programmer.Id,
79
+ DueDate = now.AddDays(5),
80
+ CreatedAt = now.AddDays(-2),
81
+ UpdatedAt = now.AddDays(-2)
82
+ },
83
+ new()
84
+ {
85
+ Title = "Clean activity log UI",
86
+ Description = "Make the activity page readable and useful for admins.",
87
+ Status = TaskState.Todo,
88
+ Priority = TaskPriority.Low,
89
+ Visibility = TaskVisibility.Public,
90
+ CreatedByUserId = admin.Id,
91
+ AssignedToUserId = admin.Id,
92
+ DueDate = now.AddDays(8),
93
+ CreatedAt = now.AddDays(-1),
94
+ UpdatedAt = now.AddDays(-1)
95
+ }
96
+ };
97
+
98
+ db.TaskItems.AddRange(tasks);
99
+ await db.SaveChangesAsync();
100
+
101
+ db.ActivityLogs.AddRange(
102
+ new ActivityLog
103
+ {
104
+ Action = "Seeded the first admin user",
105
+ EntityName = "User",
106
+ EntityId = admin.Id.ToString(),
107
+ UserId = admin.Id,
108
+ CreatedAt = now.AddDays(-7)
109
+ },
110
+ new ActivityLog
111
+ {
112
+ Action = "Seeded demo tasks",
113
+ EntityName = "Task",
114
+ EntityId = tasks[0].Id.ToString(),
115
+ UserId = admin.Id,
116
+ CreatedAt = now.AddDays(-6)
117
+ }
118
+ );
119
+
120
+ await db.SaveChangesAsync();
121
+ }
122
+ }
@@ -0,0 +1,13 @@
1
+ <Project Sdk="Microsoft.NET.Sdk.Web">
2
+ <PropertyGroup>
3
+ <TargetFramework>net8.0</TargetFramework>
4
+ <Nullable>enable</Nullable>
5
+ <ImplicitUsings>enable</ImplicitUsings>
6
+ </PropertyGroup>
7
+
8
+ <ItemGroup>
9
+ <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.*" />
10
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.*" />
11
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.*" />
12
+ </ItemGroup>
13
+ </Project>
@@ -0,0 +1,12 @@
1
+ namespace DevTasks.Api.Dtos;
2
+
3
+ public sealed class ActivityLogResponse
4
+ {
5
+ public Guid Id { get; set; }
6
+ public string Action { get; set; } = string.Empty;
7
+ public string EntityName { get; set; } = string.Empty;
8
+ public string EntityId { get; set; } = string.Empty;
9
+ public Guid UserId { get; set; }
10
+ public string UserFullName { get; set; } = string.Empty;
11
+ public DateTime CreatedAt { get; set; }
12
+ }
@@ -0,0 +1,37 @@
1
+ using DevTasks.Api.Enums;
2
+
3
+ namespace DevTasks.Api.Dtos;
4
+
5
+ public sealed class RegisterRequest
6
+ {
7
+ public string FullName { get; set; } = string.Empty;
8
+ public string Email { get; set; } = string.Empty;
9
+ public string Password { get; set; } = string.Empty;
10
+ }
11
+
12
+ public sealed class RegisterResponse
13
+ {
14
+ public string Message { get; set; } = string.Empty;
15
+ }
16
+
17
+ public sealed class LoginRequest
18
+ {
19
+ public string Email { get; set; } = string.Empty;
20
+ public string Password { get; set; } = string.Empty;
21
+ }
22
+
23
+ public sealed class AuthResponse
24
+ {
25
+ public string Token { get; set; } = string.Empty;
26
+ public UserResponse User { get; set; } = new();
27
+ }
28
+
29
+ public sealed class UserResponse
30
+ {
31
+ public Guid Id { get; set; }
32
+ public string FullName { get; set; } = string.Empty;
33
+ public string Email { get; set; } = string.Empty;
34
+ public UserRole Role { get; set; }
35
+ public bool IsActive { get; set; }
36
+ public DateTime CreatedAt { get; set; }
37
+ }
@@ -0,0 +1,104 @@
1
+ using DevTasks.Api.Enums;
2
+
3
+ namespace DevTasks.Api.Dtos;
4
+
5
+ public sealed class TaskResponse
6
+ {
7
+ public int Id { get; set; }
8
+ public string Title { get; set; } = string.Empty;
9
+ public string Description { get; set; } = string.Empty;
10
+ public TaskState Status { get; set; }
11
+ public TaskPriority Priority { get; set; }
12
+ public TaskVisibility Visibility { get; set; }
13
+ public DateTime? DueDate { get; set; }
14
+ public Guid CreatedByUserId { get; set; }
15
+ public string CreatedByFullName { get; set; } = string.Empty;
16
+ public Guid? AssignedToUserId { get; set; }
17
+ public string? AssignedToFullName { get; set; }
18
+ public DateTime CreatedAt { get; set; }
19
+ public DateTime UpdatedAt { get; set; }
20
+ public bool IsOverdue { get; set; }
21
+ public int PendingEditRequestCount { get; set; }
22
+ }
23
+
24
+ public sealed class CreateTaskRequest
25
+ {
26
+ public string Title { get; set; } = string.Empty;
27
+ public string Description { get; set; } = string.Empty;
28
+ public TaskState Status { get; set; } = TaskState.Todo;
29
+ public TaskPriority Priority { get; set; } = TaskPriority.Medium;
30
+ public TaskVisibility Visibility { get; set; } = TaskVisibility.Private;
31
+ public DateTime? DueDate { get; set; }
32
+ public Guid? AssignedToUserId { get; set; }
33
+ }
34
+
35
+ public sealed class UpdateTaskRequest
36
+ {
37
+ public string? Title { get; set; }
38
+ public string? Description { get; set; }
39
+ public TaskState? Status { get; set; }
40
+ public TaskPriority? Priority { get; set; }
41
+ public TaskVisibility? Visibility { get; set; }
42
+ public DateTime? DueDate { get; set; }
43
+ public bool ClearDueDate { get; set; }
44
+ public Guid? AssignedToUserId { get; set; }
45
+ public bool ClearAssignedUser { get; set; }
46
+ }
47
+
48
+ public sealed class UpdateTaskStatusRequest
49
+ {
50
+ public TaskState Status { get; set; }
51
+ }
52
+
53
+ public sealed class TaskQuery
54
+ {
55
+ public string? Q { get; set; }
56
+ public TaskState? Status { get; set; }
57
+ public TaskPriority? Priority { get; set; }
58
+ public TaskVisibility? Visibility { get; set; }
59
+ public Guid? AssignedToUserId { get; set; }
60
+ public DateTime? DueFrom { get; set; }
61
+ public DateTime? DueTo { get; set; }
62
+ }
63
+
64
+ public sealed class CreateTaskEditRequest
65
+ {
66
+ public string? Title { get; set; }
67
+ public string? Description { get; set; }
68
+ public TaskState? Status { get; set; }
69
+ public TaskPriority? Priority { get; set; }
70
+ public TaskVisibility? Visibility { get; set; }
71
+ public DateTime? DueDate { get; set; }
72
+ public bool ClearDueDate { get; set; }
73
+ public Guid? AssignedToUserId { get; set; }
74
+ public bool ClearAssignedUser { get; set; }
75
+ }
76
+
77
+ public sealed class ResolveTaskEditRequest
78
+ {
79
+ public string? OwnerNote { get; set; }
80
+ }
81
+
82
+ public sealed class TaskEditRequestResponse
83
+ {
84
+ public Guid Id { get; set; }
85
+ public int TaskId { get; set; }
86
+ public string TaskTitle { get; set; } = string.Empty;
87
+ public Guid RequestedByUserId { get; set; }
88
+ public string RequestedByFullName { get; set; } = string.Empty;
89
+ public string RequestedByEmail { get; set; } = string.Empty;
90
+ public string? Title { get; set; }
91
+ public string? Description { get; set; }
92
+ public TaskState? Status { get; set; }
93
+ public TaskPriority? Priority { get; set; }
94
+ public TaskVisibility? Visibility { get; set; }
95
+ public DateTime? DueDate { get; set; }
96
+ public bool ClearDueDate { get; set; }
97
+ public Guid? AssignedToUserId { get; set; }
98
+ public string? AssignedToFullName { get; set; }
99
+ public bool ClearAssignedUser { get; set; }
100
+ public EditRequestStatus StatusOfRequest { get; set; }
101
+ public string? OwnerNote { get; set; }
102
+ public DateTime CreatedAt { get; set; }
103
+ public DateTime? ResolvedAt { get; set; }
104
+ }
@@ -0,0 +1,29 @@
1
+ using DevTasks.Api.Enums;
2
+
3
+ namespace DevTasks.Api.Dtos;
4
+
5
+ public sealed class CreateUserRequest
6
+ {
7
+ public string FullName { get; set; } = string.Empty;
8
+ public string Email { get; set; } = string.Empty;
9
+ public string Password { get; set; } = string.Empty;
10
+ public UserRole Role { get; set; } = UserRole.User;
11
+ public bool IsActive { get; set; } = true;
12
+ }
13
+
14
+ public sealed class UpdateUserRequest
15
+ {
16
+ public string? FullName { get; set; }
17
+ public string? Email { get; set; }
18
+ public string? Password { get; set; }
19
+ public UserRole? Role { get; set; }
20
+ public bool? IsActive { get; set; }
21
+ }
22
+
23
+ public sealed class UpdateProfileRequest
24
+ {
25
+ public string? FullName { get; set; }
26
+ public string? Email { get; set; }
27
+ public string? CurrentPassword { get; set; }
28
+ public string? NewPassword { get; set; }
29
+ }
@@ -0,0 +1,8 @@
1
+ namespace DevTasks.Api.Enums;
2
+
3
+ public enum EditRequestStatus
4
+ {
5
+ Pending = 0,
6
+ Accepted = 1,
7
+ Rejected = 2
8
+ }
@@ -0,0 +1,8 @@
1
+ namespace DevTasks.Api.Enums;
2
+
3
+ public enum TaskPriority
4
+ {
5
+ Low = 0,
6
+ Medium = 1,
7
+ High = 2
8
+ }
@@ -0,0 +1,9 @@
1
+ namespace DevTasks.Api.Enums;
2
+
3
+ public enum TaskState
4
+ {
5
+ Todo = 0,
6
+ InProgress = 1,
7
+ Done = 2,
8
+ Cancelled = 3
9
+ }
@@ -0,0 +1,7 @@
1
+ namespace DevTasks.Api.Enums;
2
+
3
+ public enum TaskVisibility
4
+ {
5
+ Private = 0,
6
+ Public = 1
7
+ }
@@ -0,0 +1,7 @@
1
+ namespace DevTasks.Api.Enums;
2
+
3
+ public enum UserRole
4
+ {
5
+ User = 0,
6
+ Admin = 1
7
+ }
@@ -0,0 +1,23 @@
1
+ using System.Security.Claims;
2
+ using DevTasks.Api.Enums;
3
+
4
+ namespace DevTasks.Api.Extensions;
5
+
6
+ public static class ClaimsPrincipalExtensions
7
+ {
8
+ public static Guid GetUserId(this ClaimsPrincipal user)
9
+ {
10
+ var value = user.FindFirstValue(ClaimTypes.NameIdentifier);
11
+ if (string.IsNullOrWhiteSpace(value))
12
+ {
13
+ throw new UnauthorizedAccessException("Missing user id claim.");
14
+ }
15
+
16
+ return Guid.Parse(value);
17
+ }
18
+
19
+ public static bool IsAdmin(this ClaimsPrincipal user)
20
+ {
21
+ return user.IsInRole(UserRole.Admin.ToString());
22
+ }
23
+ }
@@ -0,0 +1,12 @@
1
+ namespace DevTasks.Api.Models;
2
+
3
+ public sealed class ActivityLog
4
+ {
5
+ public Guid Id { get; set; } = Guid.NewGuid();
6
+ public string Action { get; set; } = string.Empty;
7
+ public string EntityName { get; set; } = string.Empty;
8
+ public string EntityId { get; set; } = string.Empty;
9
+ public Guid UserId { get; set; }
10
+ public AppUser? User { get; set; }
11
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
12
+ }