@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.
- package/README.md +149 -11
- package/backend/Controllers/AccountController.cs +100 -0
- package/backend/Controllers/ActivityController.cs +44 -0
- package/backend/Controllers/AuthController.cs +127 -0
- package/backend/Controllers/LookupController.cs +46 -0
- package/backend/Controllers/TasksController.cs +652 -0
- package/backend/Controllers/UsersController.cs +181 -0
- package/backend/Data/AppDbContext.cs +93 -0
- package/backend/Data/DbSeeder.cs +122 -0
- package/backend/DevTasks.Api.csproj +13 -0
- package/backend/Dtos/ActivityDtos.cs +12 -0
- package/backend/Dtos/AuthDtos.cs +37 -0
- package/backend/Dtos/TaskDtos.cs +104 -0
- package/backend/Dtos/UserDtos.cs +29 -0
- package/backend/Enums/EditRequestStatus.cs +8 -0
- package/backend/Enums/TaskPriority.cs +8 -0
- package/backend/Enums/TaskState.cs +9 -0
- package/backend/Enums/TaskVisibility.cs +7 -0
- package/backend/Enums/UserRole.cs +7 -0
- package/backend/Extensions/ClaimsPrincipalExtensions.cs +23 -0
- package/backend/Models/ActivityLog.cs +12 -0
- package/backend/Models/AppUser.cs +17 -0
- package/backend/Models/TaskEditRequest.cs +31 -0
- package/backend/Models/TaskItem.cs +25 -0
- package/backend/Program.cs +138 -0
- package/backend/Properties/launchSettings.json +13 -0
- package/backend/Services/ActivityService.cs +28 -0
- package/backend/Services/PasswordHasher.cs +58 -0
- package/backend/Services/TokenService.cs +49 -0
- package/backend/appsettings.Development.json +10 -0
- package/backend/appsettings.json +24 -0
- package/frontend/index.html +12 -0
- package/frontend/package-lock.json +1769 -0
- package/frontend/package.json +23 -0
- package/frontend/src/App.tsx +40 -0
- package/frontend/src/api/http.ts +75 -0
- package/frontend/src/auth/AuthContext.tsx +101 -0
- package/frontend/src/components/EditRequestModal.tsx +139 -0
- package/frontend/src/components/EditRequestsPanel.tsx +94 -0
- package/frontend/src/components/Layout.tsx +76 -0
- package/frontend/src/components/PageHeader.tsx +21 -0
- package/frontend/src/components/ProtectedRoute.tsx +14 -0
- package/frontend/src/components/StatCard.tsx +15 -0
- package/frontend/src/components/TaskCard.tsx +83 -0
- package/frontend/src/components/TaskDetailsModal.tsx +45 -0
- package/frontend/src/components/TaskFilters.tsx +67 -0
- package/frontend/src/components/TaskModal.tsx +159 -0
- package/frontend/src/components/TaskTable.tsx +68 -0
- package/frontend/src/components/UserModal.tsx +124 -0
- package/frontend/src/main.tsx +19 -0
- package/frontend/src/pages/ActivityPage.tsx +37 -0
- package/frontend/src/pages/BoardPage.tsx +75 -0
- package/frontend/src/pages/CalendarPage.tsx +101 -0
- package/frontend/src/pages/DashboardPage.tsx +131 -0
- package/frontend/src/pages/LoginPage.tsx +69 -0
- package/frontend/src/pages/ProfilePage.tsx +111 -0
- package/frontend/src/pages/PublicTasksPage.tsx +99 -0
- package/frontend/src/pages/RegisterPage.tsx +80 -0
- package/frontend/src/pages/TasksPage.tsx +135 -0
- package/frontend/src/pages/UsersPage.tsx +86 -0
- package/frontend/src/styles.css +596 -0
- package/frontend/src/theme.tsx +49 -0
- package/frontend/src/types.ts +78 -0
- package/frontend/src/utils/date.ts +30 -0
- package/frontend/src/utils/labels.ts +3 -0
- package/frontend/src/vite-env.d.ts +1 -0
- package/frontend/tsconfig.json +21 -0
- package/frontend/vite.config.ts +15 -0
- package/package.json +22 -9
- package/index.js +0 -7
package/README.md
CHANGED
|
@@ -1,21 +1,159 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DevTasks — Full Stack Task Management Website
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A complete task management system with authentication, authorization,
|
|
4
|
+
public/private tasks, users, board, calendar, dashboard, activity log,
|
|
5
|
+
request limiting, edit requests, SQLite/PostgreSQL support, and light/dark UI.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
The UI uses a custom warm workspace theme with plum, charcoal, cream, and
|
|
8
|
+
terracotta accents. It does not use the default blue SaaS theme.
|
|
9
|
+
|
|
10
|
+
## Tech stack
|
|
11
|
+
|
|
12
|
+
- Backend: ASP.NET Core 8 Web API
|
|
13
|
+
- Frontend: React + TypeScript + Vite
|
|
14
|
+
- Database: SQLite by default, PostgreSQL optional
|
|
15
|
+
- Auth: JWT + role-based authorization
|
|
16
|
+
- Rate limit: 100 requests per minute per IP
|
|
17
|
+
|
|
18
|
+
## Default seeded accounts
|
|
19
|
+
|
|
20
|
+
The database is created automatically on the first backend run.
|
|
21
|
+
|
|
22
|
+
| Role | Email | Password |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Admin | `admin@devtasks.local` | `Admin123!` |
|
|
25
|
+
| User | `programmer@devtasks.local` | `User123!` |
|
|
26
|
+
|
|
27
|
+
New registered users are inactive by default. The admin must activate them
|
|
28
|
+
from the Users page before they can login.
|
|
29
|
+
|
|
30
|
+
## Run the backend
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd backend
|
|
34
|
+
dotnet restore
|
|
35
|
+
dotnet run
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Backend URL:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
http://localhost:5058
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The SQLite database will be created here:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
backend/data/devtasks.db
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Run the frontend
|
|
51
|
+
|
|
52
|
+
Open another terminal:
|
|
6
53
|
|
|
7
54
|
```bash
|
|
8
|
-
|
|
55
|
+
cd frontend
|
|
56
|
+
npm install
|
|
57
|
+
npm run dev
|
|
9
58
|
```
|
|
10
59
|
|
|
11
|
-
|
|
60
|
+
Frontend URL:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
http://localhost:5173
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The frontend proxies `/api` to `http://localhost:5058`.
|
|
67
|
+
|
|
68
|
+
## PostgreSQL support
|
|
69
|
+
|
|
70
|
+
SQLite is used by default in `backend/appsettings.json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
"Database": {
|
|
74
|
+
"Provider": "Sqlite"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
To use PostgreSQL, change it to:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
"Database": {
|
|
82
|
+
"Provider": "Postgres"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Then update this connection string:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
"PostgresConnection": "Host=localhost;Port=5432;Database=devtasks;Username=postgres;Password=postgres"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The backend uses `EnsureCreated()` for this demo project, so create an empty
|
|
93
|
+
PostgreSQL database first, then run the backend.
|
|
94
|
+
|
|
95
|
+
## Important note when replacing an older copy
|
|
96
|
+
|
|
97
|
+
Task IDs were changed from GUID values to numeric IDs starting from `1`.
|
|
98
|
+
If you already ran the old SQLite version, delete the old database file before
|
|
99
|
+
running this upgraded version:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
backend/data/devtasks.db
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Main features
|
|
106
|
+
|
|
107
|
+
- Register / login / logout
|
|
108
|
+
- New accounts require admin activation
|
|
109
|
+
- JWT authentication
|
|
110
|
+
- Admin and normal user roles
|
|
111
|
+
- Admin user management
|
|
112
|
+
- User profile editing
|
|
113
|
+
- Add/edit/delete tasks
|
|
114
|
+
- Numeric task IDs starting from `1`
|
|
115
|
+
- Private/public task visibility
|
|
116
|
+
- Public task edit requests
|
|
117
|
+
- Owner/admin accept or reject edit requests
|
|
118
|
+
- Multiple edit requests to the same task are handled
|
|
119
|
+
- Assigned tasks
|
|
120
|
+
- Statuses: Todo, In Progress, Done, Cancelled
|
|
121
|
+
- Priorities: Low, Medium, High
|
|
122
|
+
- Dashboard statistics with viewable task cards
|
|
123
|
+
- My Tasks table/card view
|
|
124
|
+
- Public Tasks table/card view
|
|
125
|
+
- Board View with drag and drop
|
|
126
|
+
- Calendar View; admin sees all users' tasks
|
|
127
|
+
- Activity log
|
|
128
|
+
- Search and filters
|
|
129
|
+
- Light/dark theme toggle
|
|
130
|
+
- Per-IP request limiting
|
|
131
|
+
- Info logs disabled by default
|
|
132
|
+
|
|
133
|
+
## Important files
|
|
12
134
|
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
135
|
+
```text
|
|
136
|
+
backend/Program.cs
|
|
137
|
+
backend/Data/AppDbContext.cs
|
|
138
|
+
backend/Data/DbSeeder.cs
|
|
139
|
+
backend/Controllers/AuthController.cs
|
|
140
|
+
backend/Controllers/UsersController.cs
|
|
141
|
+
backend/Controllers/AccountController.cs
|
|
142
|
+
backend/Controllers/TasksController.cs
|
|
143
|
+
backend/Controllers/ActivityController.cs
|
|
144
|
+
frontend/src/App.tsx
|
|
145
|
+
frontend/src/auth/AuthContext.tsx
|
|
146
|
+
frontend/src/api/http.ts
|
|
147
|
+
frontend/src/pages
|
|
148
|
+
frontend/src/components
|
|
149
|
+
frontend/src/styles.css
|
|
17
150
|
```
|
|
18
151
|
|
|
19
|
-
##
|
|
152
|
+
## Notes
|
|
20
153
|
|
|
21
|
-
|
|
154
|
+
- `DELETE /api/users/{id}` deactivates users instead of physically deleting them.
|
|
155
|
+
- `DELETE /api/tasks/{id}` physically deletes a task and writes to the activity log.
|
|
156
|
+
- Normal users can edit/delete their own tasks only.
|
|
157
|
+
- Other users can request edits to public tasks.
|
|
158
|
+
- Accepting one pending edit request rejects other pending edit requests for the same task.
|
|
159
|
+
- Admin can manage all users and all tasks.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
using DevTasks.Api.Data;
|
|
2
|
+
using DevTasks.Api.Dtos;
|
|
3
|
+
using DevTasks.Api.Extensions;
|
|
4
|
+
using DevTasks.Api.Models;
|
|
5
|
+
using DevTasks.Api.Services;
|
|
6
|
+
using Microsoft.AspNetCore.Authorization;
|
|
7
|
+
using Microsoft.AspNetCore.Mvc;
|
|
8
|
+
using Microsoft.EntityFrameworkCore;
|
|
9
|
+
|
|
10
|
+
namespace DevTasks.Api.Controllers;
|
|
11
|
+
|
|
12
|
+
[ApiController]
|
|
13
|
+
[Route("api/[controller]")]
|
|
14
|
+
[Authorize]
|
|
15
|
+
public sealed class AccountController : ControllerBase
|
|
16
|
+
{
|
|
17
|
+
private readonly AppDbContext _db;
|
|
18
|
+
private readonly PasswordHasher _passwordHasher;
|
|
19
|
+
private readonly ActivityService _activity;
|
|
20
|
+
|
|
21
|
+
public AccountController(
|
|
22
|
+
AppDbContext db,
|
|
23
|
+
PasswordHasher passwordHasher,
|
|
24
|
+
ActivityService activity)
|
|
25
|
+
{
|
|
26
|
+
_db = db;
|
|
27
|
+
_passwordHasher = passwordHasher;
|
|
28
|
+
_activity = activity;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[HttpGet("me")]
|
|
32
|
+
public async Task<ActionResult<UserResponse>> GetMe()
|
|
33
|
+
{
|
|
34
|
+
var id = User.GetUserId();
|
|
35
|
+
var user = await _db.Users.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
|
|
36
|
+
return user is null ? NotFound(new { message = "User was not found." }) : Ok(ToResponse(user));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
[HttpPut("me")]
|
|
40
|
+
public async Task<ActionResult<UserResponse>> UpdateMe(UpdateProfileRequest request)
|
|
41
|
+
{
|
|
42
|
+
var id = User.GetUserId();
|
|
43
|
+
var user = await _db.Users.FirstOrDefaultAsync(x => x.Id == id);
|
|
44
|
+
if (user is null)
|
|
45
|
+
{
|
|
46
|
+
return NotFound(new { message = "User was not found." });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!string.IsNullOrWhiteSpace(request.FullName))
|
|
50
|
+
{
|
|
51
|
+
user.FullName = request.FullName.Trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!string.IsNullOrWhiteSpace(request.Email))
|
|
55
|
+
{
|
|
56
|
+
var email = request.Email.Trim().ToLowerInvariant();
|
|
57
|
+
var used = await _db.Users.AnyAsync(x => x.Id != id && x.Email == email);
|
|
58
|
+
if (used)
|
|
59
|
+
{
|
|
60
|
+
return Conflict(new { message = "Email is already used." });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
user.Email = email;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!string.IsNullOrWhiteSpace(request.NewPassword))
|
|
67
|
+
{
|
|
68
|
+
if (string.IsNullOrWhiteSpace(request.CurrentPassword) ||
|
|
69
|
+
!_passwordHasher.Verify(request.CurrentPassword, user.PasswordHash))
|
|
70
|
+
{
|
|
71
|
+
return BadRequest(new { message = "Current password is incorrect." });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (request.NewPassword.Length < 6)
|
|
75
|
+
{
|
|
76
|
+
return BadRequest(new { message = "New password must be at least 6 characters." });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
user.PasswordHash = _passwordHasher.Hash(request.NewPassword);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await _db.SaveChangesAsync();
|
|
83
|
+
await _activity.AddAsync("Profile updated", "User", user.Id.ToString(), user.Id);
|
|
84
|
+
|
|
85
|
+
return Ok(ToResponse(user));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private static UserResponse ToResponse(AppUser user)
|
|
89
|
+
{
|
|
90
|
+
return new UserResponse
|
|
91
|
+
{
|
|
92
|
+
Id = user.Id,
|
|
93
|
+
FullName = user.FullName,
|
|
94
|
+
Email = user.Email,
|
|
95
|
+
Role = user.Role,
|
|
96
|
+
IsActive = user.IsActive,
|
|
97
|
+
CreatedAt = user.CreatedAt
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
using DevTasks.Api.Data;
|
|
2
|
+
using DevTasks.Api.Dtos;
|
|
3
|
+
using DevTasks.Api.Enums;
|
|
4
|
+
using Microsoft.AspNetCore.Authorization;
|
|
5
|
+
using Microsoft.AspNetCore.Mvc;
|
|
6
|
+
using Microsoft.EntityFrameworkCore;
|
|
7
|
+
|
|
8
|
+
namespace DevTasks.Api.Controllers;
|
|
9
|
+
|
|
10
|
+
[ApiController]
|
|
11
|
+
[Route("api/[controller]")]
|
|
12
|
+
[Authorize(Roles = nameof(UserRole.Admin))]
|
|
13
|
+
public sealed class ActivityController : ControllerBase
|
|
14
|
+
{
|
|
15
|
+
private readonly AppDbContext _db;
|
|
16
|
+
|
|
17
|
+
public ActivityController(AppDbContext db)
|
|
18
|
+
{
|
|
19
|
+
_db = db;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
[HttpGet]
|
|
23
|
+
public async Task<ActionResult<IReadOnlyList<ActivityLogResponse>>> GetActivity()
|
|
24
|
+
{
|
|
25
|
+
var logs = await _db.ActivityLogs
|
|
26
|
+
.AsNoTracking()
|
|
27
|
+
.Include(x => x.User)
|
|
28
|
+
.OrderByDescending(x => x.CreatedAt)
|
|
29
|
+
.Take(200)
|
|
30
|
+
.Select(x => new ActivityLogResponse
|
|
31
|
+
{
|
|
32
|
+
Id = x.Id,
|
|
33
|
+
Action = x.Action,
|
|
34
|
+
EntityName = x.EntityName,
|
|
35
|
+
EntityId = x.EntityId,
|
|
36
|
+
UserId = x.UserId,
|
|
37
|
+
UserFullName = x.User == null ? "Unknown" : x.User.FullName,
|
|
38
|
+
CreatedAt = x.CreatedAt
|
|
39
|
+
})
|
|
40
|
+
.ToListAsync();
|
|
41
|
+
|
|
42
|
+
return Ok(logs);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
using DevTasks.Api.Data;
|
|
2
|
+
using DevTasks.Api.Dtos;
|
|
3
|
+
using DevTasks.Api.Enums;
|
|
4
|
+
using DevTasks.Api.Models;
|
|
5
|
+
using DevTasks.Api.Services;
|
|
6
|
+
using Microsoft.AspNetCore.Authorization;
|
|
7
|
+
using Microsoft.AspNetCore.Mvc;
|
|
8
|
+
using Microsoft.EntityFrameworkCore;
|
|
9
|
+
|
|
10
|
+
namespace DevTasks.Api.Controllers;
|
|
11
|
+
|
|
12
|
+
[ApiController]
|
|
13
|
+
[Route("api/[controller]")]
|
|
14
|
+
public sealed class AuthController : ControllerBase
|
|
15
|
+
{
|
|
16
|
+
private readonly AppDbContext _db;
|
|
17
|
+
private readonly PasswordHasher _passwordHasher;
|
|
18
|
+
private readonly TokenService _tokenService;
|
|
19
|
+
|
|
20
|
+
public AuthController(
|
|
21
|
+
AppDbContext db,
|
|
22
|
+
PasswordHasher passwordHasher,
|
|
23
|
+
TokenService tokenService)
|
|
24
|
+
{
|
|
25
|
+
_db = db;
|
|
26
|
+
_passwordHasher = passwordHasher;
|
|
27
|
+
_tokenService = tokenService;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
[HttpPost("register")]
|
|
31
|
+
[AllowAnonymous]
|
|
32
|
+
public async Task<ActionResult<RegisterResponse>> Register(RegisterRequest request)
|
|
33
|
+
{
|
|
34
|
+
var validation = ValidateRegister(request);
|
|
35
|
+
if (validation is not null)
|
|
36
|
+
{
|
|
37
|
+
return BadRequest(new { message = validation });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var email = request.Email.Trim().ToLowerInvariant();
|
|
41
|
+
var exists = await _db.Users.AnyAsync(x => x.Email == email);
|
|
42
|
+
if (exists)
|
|
43
|
+
{
|
|
44
|
+
return Conflict(new { message = "Email is already registered." });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var user = new AppUser
|
|
48
|
+
{
|
|
49
|
+
FullName = request.FullName.Trim(),
|
|
50
|
+
Email = email,
|
|
51
|
+
PasswordHash = _passwordHasher.Hash(request.Password),
|
|
52
|
+
Role = UserRole.User,
|
|
53
|
+
IsActive = false,
|
|
54
|
+
CreatedAt = DateTime.UtcNow
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
_db.Users.Add(user);
|
|
58
|
+
await _db.SaveChangesAsync();
|
|
59
|
+
|
|
60
|
+
return Ok(new RegisterResponse
|
|
61
|
+
{
|
|
62
|
+
Message = "Account created. An admin must activate it before you can login."
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[HttpPost("login")]
|
|
67
|
+
[AllowAnonymous]
|
|
68
|
+
public async Task<ActionResult<AuthResponse>> Login(LoginRequest request)
|
|
69
|
+
{
|
|
70
|
+
var email = request.Email.Trim().ToLowerInvariant();
|
|
71
|
+
var user = await _db.Users.FirstOrDefaultAsync(x => x.Email == email);
|
|
72
|
+
|
|
73
|
+
if (user is null || !_passwordHasher.Verify(request.Password, user.PasswordHash))
|
|
74
|
+
{
|
|
75
|
+
return Unauthorized(new { message = "Invalid email or password." });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!user.IsActive)
|
|
79
|
+
{
|
|
80
|
+
return Unauthorized(new { message = "Your account is waiting for admin activation." });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return Ok(CreateAuthResponse(user));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private AuthResponse CreateAuthResponse(AppUser user)
|
|
87
|
+
{
|
|
88
|
+
return new AuthResponse
|
|
89
|
+
{
|
|
90
|
+
Token = _tokenService.CreateToken(user),
|
|
91
|
+
User = ToUserResponse(user)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private static UserResponse ToUserResponse(AppUser user)
|
|
96
|
+
{
|
|
97
|
+
return new UserResponse
|
|
98
|
+
{
|
|
99
|
+
Id = user.Id,
|
|
100
|
+
FullName = user.FullName,
|
|
101
|
+
Email = user.Email,
|
|
102
|
+
Role = user.Role,
|
|
103
|
+
IsActive = user.IsActive,
|
|
104
|
+
CreatedAt = user.CreatedAt
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private static string? ValidateRegister(RegisterRequest request)
|
|
109
|
+
{
|
|
110
|
+
if (string.IsNullOrWhiteSpace(request.FullName))
|
|
111
|
+
{
|
|
112
|
+
return "Full name is required.";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (string.IsNullOrWhiteSpace(request.Email) || !request.Email.Contains('@'))
|
|
116
|
+
{
|
|
117
|
+
return "Valid email is required.";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (string.IsNullOrWhiteSpace(request.Password) || request.Password.Length < 6)
|
|
121
|
+
{
|
|
122
|
+
return "Password must be at least 6 characters.";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
using DevTasks.Api.Data;
|
|
2
|
+
using DevTasks.Api.Dtos;
|
|
3
|
+
using DevTasks.Api.Models;
|
|
4
|
+
using Microsoft.AspNetCore.Authorization;
|
|
5
|
+
using Microsoft.AspNetCore.Mvc;
|
|
6
|
+
using Microsoft.EntityFrameworkCore;
|
|
7
|
+
|
|
8
|
+
namespace DevTasks.Api.Controllers;
|
|
9
|
+
|
|
10
|
+
[ApiController]
|
|
11
|
+
[Route("api/[controller]")]
|
|
12
|
+
[Authorize]
|
|
13
|
+
public sealed class LookupController : ControllerBase
|
|
14
|
+
{
|
|
15
|
+
private readonly AppDbContext _db;
|
|
16
|
+
|
|
17
|
+
public LookupController(AppDbContext db)
|
|
18
|
+
{
|
|
19
|
+
_db = db;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
[HttpGet("users")]
|
|
23
|
+
public async Task<ActionResult<IReadOnlyList<UserResponse>>> GetActiveUsers()
|
|
24
|
+
{
|
|
25
|
+
var users = await _db.Users
|
|
26
|
+
.AsNoTracking()
|
|
27
|
+
.Where(x => x.IsActive)
|
|
28
|
+
.OrderBy(x => x.FullName)
|
|
29
|
+
.ToListAsync();
|
|
30
|
+
|
|
31
|
+
return Ok(users.Select(ToResponse).ToList());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private static UserResponse ToResponse(AppUser user)
|
|
35
|
+
{
|
|
36
|
+
return new UserResponse
|
|
37
|
+
{
|
|
38
|
+
Id = user.Id,
|
|
39
|
+
FullName = user.FullName,
|
|
40
|
+
Email = user.Email,
|
|
41
|
+
Role = user.Role,
|
|
42
|
+
IsActive = user.IsActive,
|
|
43
|
+
CreatedAt = user.CreatedAt
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|