send test email
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using JobTrackerApi.Data;
|
using JobTrackerApi.Data;
|
||||||
using JobTrackerApi.Models;
|
using JobTrackerApi.Models;
|
||||||
@@ -1015,5 +1015,12 @@ namespace JobTrackerApi.Controllers
|
|||||||
|
|
||||||
return Ok(outList);
|
return Ok(outList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("summarizer-metrics")]
|
||||||
|
public async Task<ActionResult<SummarizerMetrics>> GetSummarizerMetrics(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var metrics = await _summarizer.GetMetricsAsync(cancellationToken);
|
||||||
|
return Ok(metrics);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using JobTrackerApi.Models;
|
using JobTrackerApi.Models;
|
||||||
using JobTrackerApi.Services;
|
using JobTrackerApi.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
namespace JobTrackerApi.Controllers;
|
namespace JobTrackerApi.Controllers;
|
||||||
|
|
||||||
@@ -113,6 +114,7 @@ public sealed class UsersController : ControllerBase
|
|||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id}/send-password-reset")]
|
[HttpPost("{id}/send-password-reset")]
|
||||||
public async Task<IActionResult> SendPasswordReset([FromRoute] string id, CancellationToken cancellationToken)
|
public async Task<IActionResult> SendPasswordReset([FromRoute] string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -139,5 +141,30 @@ public sealed class UsersController : ControllerBase
|
|||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public sealed record SendTestEmailRequest(string? ToEmail, string? Subject, string? Message);
|
||||||
|
|
||||||
|
[HttpPost("send-test-email")]
|
||||||
|
public async Task<IActionResult> SendTestEmail([FromBody] SendTestEmailRequest? request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var currentUserId = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub");
|
||||||
|
var currentUser = currentUserId is null ? null : await _users.FindByIdAsync(currentUserId);
|
||||||
|
|
||||||
|
var toEmail = (request?.ToEmail ?? currentUser?.Email ?? "").Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(toEmail)) return BadRequest("Recipient email is required.");
|
||||||
|
|
||||||
|
var subject = string.IsNullOrWhiteSpace(request?.Subject) ? "Job Tracker test email" : request!.Subject!.Trim();
|
||||||
|
var message = string.IsNullOrWhiteSpace(request?.Message)
|
||||||
|
? "This is a test email from the Job Tracker admin panel.\n\nIf you received this, the SMTP configuration is working."
|
||||||
|
: request!.Message!.Trim();
|
||||||
|
|
||||||
|
await _email.SendAsync(
|
||||||
|
toEmail,
|
||||||
|
subject,
|
||||||
|
$"{message}\n\nSent at: {DateTimeOffset.UtcNow:u}",
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ export default function AdminUsersPage() {
|
|||||||
const [newPassword, setNewPassword] = useState("");
|
const [newPassword, setNewPassword] = useState("");
|
||||||
const [newIsAdmin, setNewIsAdmin] = useState(false);
|
const [newIsAdmin, setNewIsAdmin] = useState(false);
|
||||||
|
|
||||||
|
const [testEmailTo, setTestEmailTo] = useState("");
|
||||||
|
const [testEmailSubject, setTestEmailSubject] = useState("Job Tracker SMTP test");
|
||||||
|
const [testEmailMessage, setTestEmailMessage] = useState("This is a test email from the Job Tracker admin panel.");
|
||||||
|
const [sendingTestEmail, setSendingTestEmail] = useState(false);
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -50,7 +55,6 @@ export default function AdminUsersPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void load();
|
void load();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const canCreate = useMemo(() => newEmail.trim().length > 3 && newPassword.length >= 6, [newEmail, newPassword]);
|
const canCreate = useMemo(() => newEmail.trim().length > 3 && newPassword.length >= 6, [newEmail, newPassword]);
|
||||||
@@ -76,6 +80,23 @@ export default function AdminUsersPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendTestEmail = async () => {
|
||||||
|
setSendingTestEmail(true);
|
||||||
|
try {
|
||||||
|
await api.post("/users/send-test-email", {
|
||||||
|
toEmail: testEmailTo.trim() || null,
|
||||||
|
subject: testEmailSubject.trim() || null,
|
||||||
|
message: testEmailMessage.trim() || null,
|
||||||
|
});
|
||||||
|
toast("Test email sent.", "success");
|
||||||
|
} catch (e: any) {
|
||||||
|
const msg = e?.response?.data || e?.message || "Failed to send test email.";
|
||||||
|
toast(String(msg), "error");
|
||||||
|
} finally {
|
||||||
|
setSendingTestEmail(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const remove = async (u: UserDto) => {
|
const remove = async (u: UserDto) => {
|
||||||
if (!window.confirm(`Delete user ${u.email || u.userName || u.id}?`)) return;
|
if (!window.confirm(`Delete user ${u.email || u.userName || u.id}?`)) return;
|
||||||
try {
|
try {
|
||||||
@@ -94,6 +115,35 @@ export default function AdminUsersPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Typography sx={{ color: "text.secondary", mb: 2 }}>Admin-only user management.</Typography>
|
<Typography sx={{ color: "text.secondary", mb: 2 }}>Admin-only user management.</Typography>
|
||||||
|
|
||||||
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
|
<Typography sx={{ fontWeight: 900, mb: 1 }}>SMTP test email</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: "text.secondary", mb: 1.5 }}>
|
||||||
|
Send a quick delivery check using the configured SMTP settings. Leave the recipient blank to use your admin email.
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 1.5 }}>
|
||||||
|
<TextField
|
||||||
|
label="Recipient email"
|
||||||
|
value={testEmailTo}
|
||||||
|
onChange={(e) => setTestEmailTo(e.target.value)}
|
||||||
|
placeholder="Uses your admin email if left blank"
|
||||||
|
/>
|
||||||
|
<TextField label="Subject" value={testEmailSubject} onChange={(e) => setTestEmailSubject(e.target.value)} />
|
||||||
|
<TextField
|
||||||
|
label="Message"
|
||||||
|
multiline
|
||||||
|
minRows={3}
|
||||||
|
value={testEmailMessage}
|
||||||
|
onChange={(e) => setTestEmailMessage(e.target.value)}
|
||||||
|
sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 1.5 }}>
|
||||||
|
<Button variant="contained" disabled={sendingTestEmail} onClick={() => void sendTestEmail()}>
|
||||||
|
{sendingTestEmail ? "Sending..." : "Send test email"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography sx={{ fontWeight: 900, mb: 1 }}>Create user</Typography>
|
<Typography sx={{ fontWeight: 900, mb: 1 }}>Create user</Typography>
|
||||||
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 1.5 }}>
|
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 1.5 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user