Compare commits

...

3 Commits

Author SHA1 Message Date
Francesco Lorenzo D'Amico ee43dc60d8 Create EF-Migration for NotificationSent-field 2026-08-18 16:46:57 +02:00
Francesco Lorenzo D'Amico c956c5b3b2 Backend-scheudler for telegram notifications by new contact requests 2026-08-18 16:46:07 +02:00
Francesco Lorenzo D'Amico ee9e8de354 Contactform saves input into db 2026-08-18 16:44:42 +02:00
8 changed files with 339 additions and 5 deletions
@@ -0,0 +1,128 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RazorPagesMovie.Data;
#nullable disable
namespace RazorPagesMovie.Migrations
{
[DbContext(typeof(RazorPagesMovieContext))]
[Migration("20260818134749_AddNotificationSentToContactMessage")]
partial class AddNotificationSentToContactMessage
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.28")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("RazorPagesMovie.Models.AdminUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.ToTable("AdminUser");
});
modelBuilder.Entity("RazorPagesMovie.Models.ContactMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(5000)
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("NotificationSent")
.HasColumnType("bit");
b.Property<string>("Phone")
.HasMaxLength(30)
.HasColumnType("nvarchar(30)");
b.Property<DateTime>("SentAt")
.HasColumnType("datetime2");
b.Property<string>("Subject")
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.HasKey("Id");
b.ToTable("ContactMessage");
});
modelBuilder.Entity("RazorPagesMovie.Models.Movie", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Genre")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("nvarchar(30)");
b.Property<decimal>("Price")
.HasColumnType("decimal(18, 2)");
b.Property<string>("Rating")
.IsRequired()
.HasMaxLength(5)
.HasColumnType("nvarchar(5)");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("datetime2");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(60)
.HasColumnType("nvarchar(60)");
b.HasKey("Id");
b.ToTable("Movie");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RazorPagesMovie.Migrations
{
/// <inheritdoc />
public partial class AddNotificationSentToContactMessage : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Message",
table: "ContactMessage",
type: "nvarchar(max)",
maxLength: 5000,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(4000)",
oldMaxLength: 4000);
migrationBuilder.AddColumn<bool>(
name: "NotificationSent",
table: "ContactMessage",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "NotificationSent",
table: "ContactMessage");
migrationBuilder.AlterColumn<string>(
name: "Message",
table: "ContactMessage",
type: "nvarchar(4000)",
maxLength: 4000,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldMaxLength: 5000);
}
}
}
@@ -59,14 +59,17 @@ namespace RazorPagesMovie.Migrations
b.Property<string>("Message") b.Property<string>("Message")
.IsRequired() .IsRequired()
.HasMaxLength(4000) .HasMaxLength(5000)
.HasColumnType("nvarchar(4000)"); .HasColumnType("nvarchar(max)");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(100) .HasMaxLength(100)
.HasColumnType("nvarchar(100)"); .HasColumnType("nvarchar(100)");
b.Property<bool>("NotificationSent")
.HasColumnType("bit");
b.Property<string>("Phone") b.Property<string>("Phone")
.HasMaxLength(30) .HasMaxLength(30)
.HasColumnType("nvarchar(30)"); .HasColumnType("nvarchar(30)");
+2
View File
@@ -33,4 +33,6 @@ public class ContactMessage
[Display(Name = "Gesendet am")] [Display(Name = "Gesendet am")]
[DataType(DataType.DateTime)] [DataType(DataType.DateTime)]
public DateTime SentAt { get; set; } = DateTime.Now; public DateTime SentAt { get; set; } = DateTime.Now;
public bool NotificationSent { get; set; } = false;
} }
+6 -3
View File
@@ -939,9 +939,12 @@
<div class="contact-form-box style-two"> <div class="contact-form-box style-two">
<form action="https://formspree.io/f/myyleorq" @if (Model.ContactFormSubmitted)
method="POST" {
id="dreamit-form"> <div class="alert alert-success">Danke für deine Nachricht! Ich melde mich so schnell wie möglich.</div>
}
<form method="post" id="dreamit-form">
<h4> <h4>
Schreib mir eine Nachricht Schreib mir eine Nachricht
+50
View File
@@ -1,12 +1,62 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages namespace RazorPagesMovie.Pages
{ {
public class BewerbungModel : PageModel public class BewerbungModel : PageModel
{ {
private readonly RazorPagesMovieContext _context;
public BewerbungModel(RazorPagesMovieContext context)
{
_context = context;
}
[BindProperty]
public string Name { get; set; } = string.Empty;
[BindProperty]
public string Email { get; set; } = string.Empty;
[BindProperty]
public string? Phone { get; set; }
[BindProperty]
public string? Subject { get; set; }
[BindProperty]
public string Message { get; set; } = string.Empty;
public bool ContactFormSubmitted { get; set; }
public void OnGet() public void OnGet()
{ {
} }
public async Task<IActionResult> OnPostAsync()
{
var contactMessage = new ContactMessage
{
Name = Name,
Email = Email,
Phone = Phone,
Subject = Subject,
Message = Message
};
if (!TryValidateModel(contactMessage, nameof(ContactMessage)))
{
return Page();
}
_context.ContactMessage.Add(contactMessage);
await _context.SaveChangesAsync();
ContactFormSubmitted = true;
return Page();
}
} }
} }
+4
View File
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data; using RazorPagesMovie.Data;
using RazorPagesMovie.Models; using RazorPagesMovie.Models;
using RazorPagesMovie.Services;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true); builder.Configuration.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true);
@@ -21,6 +22,9 @@ builder.Services.AddRazorPages(options =>
options.Conventions.AuthorizeFolder("/Admin"); options.Conventions.AuthorizeFolder("/Admin");
}); });
builder.Services.AddHttpClient();
builder.Services.AddHostedService<ContactMessageNotificationService>();
var app = builder.Build(); var app = builder.Build();
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
@@ -0,0 +1,95 @@
using System.Net.Http.Json;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
namespace RazorPagesMovie.Services;
public class ContactMessageNotificationService : BackgroundService
{
private static readonly TimeSpan CheckInterval = TimeSpan.FromMinutes(5);
private readonly IServiceScopeFactory _scopeFactory;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly ILogger<ContactMessageNotificationService> _logger;
public ContactMessageNotificationService(
IServiceScopeFactory scopeFactory,
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
ILogger<ContactMessageNotificationService> logger)
{
_scopeFactory = scopeFactory;
_httpClientFactory = httpClientFactory;
_configuration = configuration;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await CheckForNewContactMessagesAsync(stoppingToken);
await Task.Delay(CheckInterval, stoppingToken);
}
}
private async Task CheckForNewContactMessagesAsync(CancellationToken stoppingToken)
{
var botToken = _configuration["Telegram:BotToken"];
var chatId = _configuration["Telegram:ChatId"];
if (string.IsNullOrWhiteSpace(botToken) || string.IsNullOrWhiteSpace(chatId))
{
return;
}
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<RazorPagesMovieContext>();
var neueEintraege = await context.ContactMessage
.Where(c => !c.NotificationSent)
.OrderBy(c => c.SentAt)
.ToListAsync(stoppingToken);
if (neueEintraege.Count == 0)
{
return;
}
var httpClient = _httpClientFactory.CreateClient();
var sendMessageUrl = $"https://api.telegram.org/bot{botToken}/sendMessage";
foreach (var eintrag in neueEintraege)
{
var text = $"Neue Kontaktanfrage von {eintrag.Name}\n"
+ $"E-Mail: {eintrag.Email}\n"
+ (string.IsNullOrWhiteSpace(eintrag.Phone) ? "" : $"Telefon: {eintrag.Phone}\n")
+ (string.IsNullOrWhiteSpace(eintrag.Subject) ? "" : $"Betreff: {eintrag.Subject}\n")
+ $"\n{eintrag.Message}";
try
{
var response = await httpClient.PostAsJsonAsync(
sendMessageUrl,
new { chat_id = chatId, text },
stoppingToken);
if (response.IsSuccessStatusCode)
{
eintrag.NotificationSent = true;
}
else
{
_logger.LogWarning("Telegram-Versand fehlgeschlagen für ContactMessage {Id}: {StatusCode}", eintrag.Id, response.StatusCode);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Telegram-Versand fehlgeschlagen für ContactMessage {Id}", eintrag.Id);
}
}
await context.SaveChangesAsync(stoppingToken);
}
}