1b3a8b493e
deploy / deploy (push) Failing after 1m21s
Full rewrite of the portfolio site from Next.js 14 to .NET 10: - ASP.NET Core 10 Razor Pages, no Node.js dependency - EF Core 10 + SQLite (same schema as before — data survives upgrade) - Cookie authentication (same single-password model) - Resend contact form via HttpClient - Bilingual FA/EN via locale cookie + BasePageModel - All UI ported to Razor Pages with Tailwind CDN + custom CSS - Vanilla JS: particles, typewriter, cursor, animations, portfolio modal - Dockerfile: SDK 10.0-alpine → aspnet 10.0-alpine (no npm/Node needed) - CI/CD: dropped NPM_TOKEN, ADMIN_SESSION_SECRET — pure dotnet publish Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
3.0 KiB
C#
70 lines
3.0 KiB
C#
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
|
|
namespace SoroushAsadi.Services;
|
|
|
|
public class EmailService(HttpClient http, IConfiguration config, ILogger<EmailService> logger)
|
|
{
|
|
private string? ApiKey => config["RESEND_API_KEY"] ?? Environment.GetEnvironmentVariable("RESEND_API_KEY");
|
|
private string? Inbox => config["CONTACT_INBOX"] ?? Environment.GetEnvironmentVariable("CONTACT_INBOX");
|
|
private string? From => config["CONTACT_FROM"] ?? Environment.GetEnvironmentVariable("CONTACT_FROM");
|
|
|
|
public record ContactForm(
|
|
string Name, string Company, string Service,
|
|
string Budget, string Message, string Locale);
|
|
|
|
/// <returns>null on success, error string on failure.</returns>
|
|
public async Task<string?> SendContactAsync(ContactForm form)
|
|
{
|
|
if (ApiKey is null || Inbox is null || From is null)
|
|
{
|
|
logger.LogInformation("[contact] received (no Resend key — logging only): {Name}", form.Name);
|
|
return null; // dev no-op
|
|
}
|
|
|
|
var html = $"""
|
|
<div style="font-family:ui-sans-serif,system-ui,sans-serif;line-height:1.55">
|
|
<h2 style="margin:0 0 12px">New consultation request</h2>
|
|
<table style="border-collapse:collapse">
|
|
<tr><td style="padding:4px 12px 4px 0;color:#475569">Name</td><td>{Esc(form.Name)}</td></tr>
|
|
<tr><td style="padding:4px 12px 4px 0;color:#475569">Company</td><td>{Esc(form.Company)}</td></tr>
|
|
<tr><td style="padding:4px 12px 4px 0;color:#475569">Service</td><td>{Esc(form.Service)}</td></tr>
|
|
<tr><td style="padding:4px 12px 4px 0;color:#475569">Budget</td><td>{Esc(form.Budget)}</td></tr>
|
|
<tr><td style="padding:4px 12px 4px 0;color:#475569">Locale</td><td>{Esc(form.Locale)}</td></tr>
|
|
</table>
|
|
<h3 style="margin:20px 0 6px">Message</h3>
|
|
<p style="white-space:pre-wrap;background:#f8fafc;padding:12px;border-radius:8px">{Esc(form.Message)}</p>
|
|
</div>
|
|
""";
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.resend.com/emails");
|
|
req.Headers.Add("Authorization", $"Bearer {ApiKey}");
|
|
req.Content = JsonContent.Create(new
|
|
{
|
|
from = From,
|
|
to = new[] { Inbox },
|
|
subject = $"New consultation request — {form.Name}",
|
|
html
|
|
});
|
|
|
|
try
|
|
{
|
|
var res = await http.SendAsync(req);
|
|
if (!res.IsSuccessStatusCode)
|
|
{
|
|
var body = await res.Content.ReadAsStringAsync();
|
|
logger.LogError("[contact] Resend error {Status}: {Body}", res.StatusCode, body);
|
|
return "Email service rejected the request.";
|
|
}
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "[contact] Send failed");
|
|
return "Email service unreachable.";
|
|
}
|
|
}
|
|
|
|
private static string Esc(string? s) => System.Web.HttpUtility.HtmlEncode(s ?? "");
|
|
}
|