From 74cb7f3456f171d9edd36d396a3d4fe42ce1e947 Mon Sep 17 00:00:00 2001 From: Francesco Caldarelli <148480520+iamShallo@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:51:06 +0100 Subject: [PATCH 1/2] Final fixes and code cleanup --- TourismApp/Controllers/AccountController.cs | 28 +-------- TourismApp/Controllers/ChatController.cs | 2 +- TourismApp/Controllers/ExploreController.cs | 11 +--- TourismApp/Controllers/HomeController.cs | 13 +---- .../Controllers/PreferencesController.cs | 3 - TourismApp/Data/AppDbContext.cs | 16 +---- TourismApp/Models/Dtos/PoiCardDto.cs | 6 +- TourismApp/Models/Event.cs | 3 +- TourismApp/Models/Municipality.cs | 3 +- TourismApp/Models/News.cs | 2 +- TourismApp/Models/PendingUser.cs | 4 +- TourismApp/Models/UserPreference.cs | 5 -- TourismApp/Services/AiService.cs | 6 +- TourismApp/Services/ExploreService.cs | 12 ---- TourismApp/Services/IEmailService.cs | 9 +-- TourismApp/Services/RankingServices.cs | 9 +-- TourismApp/Services/TourismDataService.cs | 16 ----- .../ViewModels/QuestionnaireViewModel.cs | 5 -- TourismApp/ViewModels/RegisterViewModel.cs | 11 +--- TourismApp/Views/Account/Register.cshtml | 1 - TourismApp/Views/Explore/Index.cshtml | 29 +++------- TourismApp/Views/Home/Index.cshtml | 3 - TourismApp/Views/Preferences/Index.cshtml | 12 +--- TourismApp/Views/Shared/_AccountLayout.cshtml | 2 - TourismApp/Views/Shared/_Layout.cshtml | 5 +- TourismApp/wwwroot/css/site.css | 58 ++++++------------- 26 files changed, 53 insertions(+), 221 deletions(-) diff --git a/TourismApp/Controllers/AccountController.cs b/TourismApp/Controllers/AccountController.cs index 639b77b..400088a 100644 --- a/TourismApp/Controllers/AccountController.cs +++ b/TourismApp/Controllers/AccountController.cs @@ -57,15 +57,13 @@ public async Task Login(LoginViewModel model, string returnUrl = if (result.Succeeded) { - // Primo accesso: se l'utente non ha ancora preferenze salvate, porta alla pagina Preferenze. var hasPreferences = await _dbContext.UserPreferences .AsNoTracking() .AnyAsync(up => up.UserId == user.Id); if (!hasPreferences) { - // Mostra la pagina delle preferenze come prima pagina. - // Ignoriamo il returnUrl SOLO al primo accesso. + return RedirectToAction("Index", "Preferences"); } @@ -87,10 +85,6 @@ public async Task Logout() return RedirectToAction("Index", "Home"); } - // ========================== - // REGISTRAZIONE - // ========================== - [HttpGet] public IActionResult Register() { @@ -180,7 +174,7 @@ public async Task ConfirmPendingUser(string token, string email) Email = pendingUser.Email, UserName = pendingUser.Email, EmailConfirmed = true, - PasswordHash = pendingUser.PasswordHash // Hash recuperato + PasswordHash = pendingUser.PasswordHash }; var result = await _userManager.CreateAsync(user); @@ -195,18 +189,12 @@ public async Task ConfirmPendingUser(string token, string email) return View("ConfirmEmailSuccess"); } - // ========================== - // PASSWORD RECOVERY (Parte Fixata) - // ========================== - - // 1. GET: Pagina inserimento mail [HttpGet] public IActionResult VerifyEmail() { return View(); } - // 2. POST: Invio link [HttpPost] [ValidateAntiForgeryToken] public async Task VerifyEmail(VerifyEmailViewModel model) @@ -214,17 +202,13 @@ public async Task VerifyEmail(VerifyEmailViewModel model) if (!ModelState.IsValid) return View(model); var user = await _userManager.FindByEmailAsync(model.Email); - // Non riveliamo se l'utente non esiste per sicurezza if (user == null || !user.EmailConfirmed) { return RedirectToAction("VerifyEmailSent"); } - // Genera il token PULITO var token = await _userManager.GeneratePasswordResetTokenAsync(user); - // NOTA: Qui NON usiamo UrlEncode manuale, lo fa Url.Action da solo! - var resetLink = Url.Action("ChangePassword", "Account", new { email = user.Email, token = token }, Request.Scheme); string message = $"

Click here to reset your password: Reset Password

"; @@ -233,14 +217,12 @@ public async Task VerifyEmail(VerifyEmailViewModel model) return RedirectToAction("VerifyEmailSent"); } - // 3. GET: Conferma "Mail Inviata" [HttpGet] public IActionResult VerifyEmailSent() { return View(); } - // 4. GET: Pagina inserimento nuova password (dal link mail) [HttpGet] public IActionResult ChangePassword(string token, string email) { @@ -251,7 +233,6 @@ public IActionResult ChangePassword(string token, string email) return View(new ChangePasswordViewModel { Token = token, Email = email }); } - // 5. POST: Salvataggio nuova password [HttpPost] [ValidateAntiForgeryToken] public async Task ChangePassword(ChangePasswordViewModel model) @@ -279,17 +260,12 @@ public async Task ChangePassword(ChangePasswordViewModel model) return View(model); } - // 6. GET: Conferma finale (Usa la tua vista esistente) [HttpGet] public IActionResult ResetSuccessful() { return View(); } - // ========================== - // HELPERS - // ========================== - private async Task SendConfirmationEmailAsync(string name, string email, string token) { var link = Url.Action("ConfirmPendingUser", "Account", new { token = token, email = email }, Request.Scheme); diff --git a/TourismApp/Controllers/ChatController.cs b/TourismApp/Controllers/ChatController.cs index 2c59f21..5585960 100644 --- a/TourismApp/Controllers/ChatController.cs +++ b/TourismApp/Controllers/ChatController.cs @@ -3,7 +3,7 @@ namespace TourismApp.Controllers { - [Route("api/[controller]")] // Questo crea l'indirizzo: /api/chat + [Route("api/[controller]")] [ApiController] public class ChatController : ControllerBase { diff --git a/TourismApp/Controllers/ExploreController.cs b/TourismApp/Controllers/ExploreController.cs index 44181b5..a7f7426 100644 --- a/TourismApp/Controllers/ExploreController.cs +++ b/TourismApp/Controllers/ExploreController.cs @@ -24,7 +24,6 @@ public ExploreController(RankingService rankingService, _exploreService = exploreService; } - // Ho aggiunto 'sort' ai parametri per ordinamento lato server (name asc/desc). public async Task Index(string section = "poi", string category = null, string q = null, string eventCategory = null, string date = null, string sort = null) { var userId = _userManager.GetUserId(User); @@ -36,13 +35,10 @@ public async Task Index(string section = "poi", string category = Query = q }; - // LOGICA: Sezione POI (Usa il Ranking Intelligente) if (section == "poi") { - // 1. Ottieni lista ordinata per gusti (RankingService) var pois = await _rankingService.GetPersonalizedFeedAsync(userId); - // 2. Applica i filtri manuali (Barra di ricerca e Categoria POI) if (!string.IsNullOrEmpty(category)) { pois = pois.Where(p => p.Category == category).ToList(); @@ -53,9 +49,7 @@ public async Task Index(string section = "poi", string category = pois = pois.Where(p => p.Name.Contains(q, System.StringComparison.OrdinalIgnoreCase)).ToList(); } - // 3. Applica sorting lato server per nome - // sort: "name-asc" | "name-desc" | "distance-nearest" | "distance-farthest" - // Le opzioni distance saranno gestite lato client via JS. + if (!string.IsNullOrWhiteSpace(sort)) { switch (sort) @@ -67,14 +61,13 @@ public async Task Index(string section = "poi", string category = pois = pois.OrderByDescending(p => p.Name).ToList(); break; default: - // Per distance lo lasciamo all'ordine di ranking; il client riordina. break; } } viewModel.PointsOfInterest = pois; } - // LOGICA: Altre Sezioni (Usa ExploreService passando i parametri richiesti) + else { if (section == "news") diff --git a/TourismApp/Controllers/HomeController.cs b/TourismApp/Controllers/HomeController.cs index eab1eb5..484ace8 100644 --- a/TourismApp/Controllers/HomeController.cs +++ b/TourismApp/Controllers/HomeController.cs @@ -1,11 +1,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; // Serve per le query async +using Microsoft.EntityFrameworkCore; using System.Diagnostics; using TourismApp.Data; using TourismApp.Models; -using TourismApp.Services; // Se usi il RankingService qui -using TourismApp.ViewModels; // Se usi ViewModels +using TourismApp.Services; +using TourismApp.ViewModels; namespace TourismApp.Controllers { @@ -14,9 +14,7 @@ public class HomeController : Controller private readonly ILogger _logger; private readonly UserManager _userManager; private readonly AppDbContext _context; - // private readonly RankingService _rankingService; // Scommenta quando userai il ranking - // Iniettiamo UserManager e DbContext public HomeController(ILogger logger, UserManager userManager, AppDbContext context) @@ -26,18 +24,13 @@ public HomeController(ILogger logger, _context = context; } - // Nota: Ho tolto "async Task" perché non facciamo più chiamate asincrone al DB qui dentro. - // Se preferisci lasciarlo async, non è un errore, ma ti darà un avviso verde (warning). public IActionResult Index() { - // CONTROLLO DIRETTO: L'utente è loggato? if (User.Identity != null && User.Identity.IsAuthenticated) { - // SÌ -> Salta la Dashboard e vai dritto ai POI (Explore) return RedirectToAction("Index", "Explore"); } - // NO -> Mostra la pagina "Discover our city" (Firenze) return View(); } diff --git a/TourismApp/Controllers/PreferencesController.cs b/TourismApp/Controllers/PreferencesController.cs index fd57368..1043bb8 100644 --- a/TourismApp/Controllers/PreferencesController.cs +++ b/TourismApp/Controllers/PreferencesController.cs @@ -42,7 +42,6 @@ public async Task Index() InterestShop = existingCategories.Contains("Shop"), InterestFun = existingCategories.Contains("Fun"), - // RECUPERA IL TIPO DI VIAGGIO TravelType = existingCategories.FirstOrDefault(c => c == "Solo" || c == "Couple" || c == "Family" || c == "Friends") ?? "Solo" }; @@ -59,7 +58,6 @@ public async Task Index(QuestionnaireViewModel model) var user = await _userManager.GetUserAsync(User); if (user == null) return RedirectToAction("Login", "Account"); - // Rimuovi vecchie preferenze var oldPrefs = _context.UserPreferences.Where(up => up.UserId == user.Id); _context.UserPreferences.RemoveRange(oldPrefs); await _context.SaveChangesAsync(); @@ -72,7 +70,6 @@ public async Task Index(QuestionnaireViewModel model) if (model.InterestShop) AddPreference(newPrefs, user.Id, "Shop", 1.0); if (model.InterestFun) AddPreference(newPrefs, user.Id, "Fun", 1.0); - // SALVA IL TIPO DI VIAGGIO if (!string.IsNullOrEmpty(model.TravelType)) { AddPreference(newPrefs, user.Id, model.TravelType, 1.0); diff --git a/TourismApp/Data/AppDbContext.cs b/TourismApp/Data/AppDbContext.cs index 29f7831..626e2ea 100644 --- a/TourismApp/Data/AppDbContext.cs +++ b/TourismApp/Data/AppDbContext.cs @@ -4,39 +4,25 @@ namespace TourismApp.Data { - /// - /// Represents the database session. - /// Inherits from IdentityDbContext to include all tables required for ASP.NET Core Identity (Users, Roles, Claims). - /// public class AppDbContext : IdentityDbContext { public AppDbContext(DbContextOptions options) : base(options) { } - // Domain Entities public DbSet Municipalities { get; set; } public DbSet PointsOfInterest { get; set; } public DbSet Events { get; set; } public DbSet News { get; set; } - - // Custom Security Entity for "Idempotent Registration" flow public DbSet PendingUsers { get; set; } public DbSet UserPreferences { get; set; } - /// - /// Configures the schema needed for the Identity framework and custom entity constraints. - /// protected override void OnModelCreating(ModelBuilder builder) { - // SECURITY NOTE: Always call base.OnModelCreating when using IdentityDbContext! - // Failing to do so will result in Identity tables not being created/mapped correctly. + base.OnModelCreating(builder); - // --- FLUENT API CONFIGURATION (Optional Future Hardening) --- - // Example: Enforce unique constraints or max lengths at DB level here. - // builder.Entity().HasIndex(m => m.Name).IsUnique(); } } } \ No newline at end of file diff --git a/TourismApp/Models/Dtos/PoiCardDto.cs b/TourismApp/Models/Dtos/PoiCardDto.cs index 2aa3388..b4f793e 100644 --- a/TourismApp/Models/Dtos/PoiCardDto.cs +++ b/TourismApp/Models/Dtos/PoiCardDto.cs @@ -2,7 +2,6 @@ namespace TourismApp.Models.Dtos { - // Questa classe mappa esattamente il JSON dell'API che mi hai mandato public class PoiCardDto { [JsonPropertyName("entityId")] @@ -20,11 +19,10 @@ public class PoiCardDto [JsonPropertyName("address")] public string Address { get; set; } - // Alcuni endpoint hanno campi extra, li mettiamo opzionali [JsonPropertyName("date")] - public string? Date { get; set; } // Per gli eventi + public string? Date { get; set; } [JsonPropertyName("classification")] - public string? Classification { get; set; } // Per gli hotel + public string? Classification { get; set; } } } \ No newline at end of file diff --git a/TourismApp/Models/Event.cs b/TourismApp/Models/Event.cs index 1f89fe0..d769be3 100644 --- a/TourismApp/Models/Event.cs +++ b/TourismApp/Models/Event.cs @@ -11,10 +11,9 @@ public class Event [Required] public string Name { get; set; } - public string? Category { get; set; } // badgeText (es. "Festival") + public string? Category { get; set; } public string? ImageUrl { get; set; } - // La data salvata come stringa (es. "17/05/2025") public string? DateDisplay { get; set; } public int MunicipalityId { get; set; } diff --git a/TourismApp/Models/Municipality.cs b/TourismApp/Models/Municipality.cs index 6e5a3df..c64da4d 100644 --- a/TourismApp/Models/Municipality.cs +++ b/TourismApp/Models/Municipality.cs @@ -11,10 +11,9 @@ public class Municipality public int Id { get; set; } [Required] - public string Name { get; set; } // es. "Gradara" + public string Name { get; set; } public string? ImageUrl { get; set; } - // Relazioni: Un comune ha tanti POI, Eventi e News public ICollection PointsOfInterest { get; set; } = new List(); public ICollection Events { get; set; } = new List(); public ICollection News { get; set; } = new List(); diff --git a/TourismApp/Models/News.cs b/TourismApp/Models/News.cs index 739fd98..b31bce5 100644 --- a/TourismApp/Models/News.cs +++ b/TourismApp/Models/News.cs @@ -11,7 +11,7 @@ public class News [Required] public string Title { get; set; } - public string? ReadingTime { get; set; } // badgeText (es. "6 minuti") + public string? ReadingTime { get; set; } public string? ImageUrl { get; set; } public int MunicipalityId { get; set; } diff --git a/TourismApp/Models/PendingUser.cs b/TourismApp/Models/PendingUser.cs index 17ad0a9..1d17a4f 100644 --- a/TourismApp/Models/PendingUser.cs +++ b/TourismApp/Models/PendingUser.cs @@ -10,8 +10,8 @@ public class PendingUser public string Surname { get; set; } public DateTime DateOfBirth { get; set; } public string Email { get; set; } - public string PasswordHash { get; set; } // salva la password già hashata - public string Token { get; set; } // token per conferma + public string PasswordHash { get; set; } + public string Token { get; set; } public DateTime CreatedAt { get; set; } } } diff --git a/TourismApp/Models/UserPreference.cs b/TourismApp/Models/UserPreference.cs index 625f971..bc1106c 100644 --- a/TourismApp/Models/UserPreference.cs +++ b/TourismApp/Models/UserPreference.cs @@ -8,20 +8,15 @@ public class UserPreference [Key] public int Id { get; set; } - // Collegamento all'utente loggato (Identity) [Required] public string UserId { get; set; } [ForeignKey("UserId")] public Users User { get; set; } - // La categoria (es. "Art", "Nature") - // Deve coincidere con le stringhe salvate nella tabella PointOfInterest [Required] public string Category { get; set; } - // Il punteggio (es. 1.0 se selezionato, 0.0 se no) - // Usiamo double per poter mettere pesi decimali in futuro (es. 0.5) public double Score { get; set; } } } \ No newline at end of file diff --git a/TourismApp/Services/AiService.cs b/TourismApp/Services/AiService.cs index 030a33d..c85370b 100644 --- a/TourismApp/Services/AiService.cs +++ b/TourismApp/Services/AiService.cs @@ -2,7 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace TourismApp.Services // Assicurati che il namespace sia TourismApp.Services +namespace TourismApp.Services { public class AiService { @@ -23,7 +23,7 @@ public async Task GetAnswerAsync(string userMessage) { var payload = new { - model = "llama3.1", // O il modello che hai installato + model = "llama3.1", messages = new[] { new { role = "system", content = SystemInstruction }, @@ -39,8 +39,6 @@ public async Task GetAnswerAsync(string userMessage) var response = await _httpClient.PostAsync(OllamaUrl, jsonContent); response.EnsureSuccessStatusCode(); var strResponse = await response.Content.ReadAsStringAsync(); - - // Deserializzazione rapida using var doc = JsonDocument.Parse(strResponse); return doc.RootElement.GetProperty("message").GetProperty("content").GetString(); } diff --git a/TourismApp/Services/ExploreService.cs b/TourismApp/Services/ExploreService.cs index 24f5536..f7f7ca2 100644 --- a/TourismApp/Services/ExploreService.cs +++ b/TourismApp/Services/ExploreService.cs @@ -7,10 +7,6 @@ namespace TourismApp.Services { - /// - /// Service Layer for Explore features. - /// Isolates database queries from the Controller, improving testability and security. - /// public class ExploreService { private readonly AppDbContext _db; @@ -20,7 +16,6 @@ public ExploreService(AppDbContext db) _db = db; } - // Recupera le News filtrate public async Task> GetNewsAsync(string query) { var newsQuery = _db.News.Include(n => n.Municipality).AsQueryable(); @@ -33,7 +28,6 @@ public async Task> GetNewsAsync(string query) return await newsQuery.AsNoTracking().ToListAsync(); } - // Recupera i POI filtrati public async Task> GetPoisAsync(string query, string category) { var poiQuery = _db.PointsOfInterest.Include(p => p.Municipality).AsQueryable(); @@ -54,7 +48,6 @@ public async Task> GetPoisAsync(string query, string categ return await poiQuery.AsNoTracking().ToListAsync(); } - // Recupera i Comuni public async Task> GetMunicipalitiesAsync(string query) { var munQuery = _db.Municipalities.AsQueryable(); @@ -67,24 +60,20 @@ public async Task> GetMunicipalitiesAsync(string query) return await munQuery.AsNoTracking().ToListAsync(); } - // Recupera gli Eventi (logica più complessa) public async Task> GetEventsAsync(string query, string eventCategory, string date) { var eventsQuery = _db.Events.Include(e => e.Municipality).AsQueryable(); - // Filtro Categoria Evento if (!string.IsNullOrWhiteSpace(eventCategory)) { eventsQuery = eventsQuery.Where(e => e.Category != null && e.Category.Contains(eventCategory)); } - // Filtro Data if (!string.IsNullOrWhiteSpace(date)) { eventsQuery = eventsQuery.Where(e => e.DateDisplay == date); } - // Ricerca Testuale if (!string.IsNullOrWhiteSpace(query)) { eventsQuery = eventsQuery.Where(e => @@ -95,7 +84,6 @@ public async Task> GetEventsAsync(string query, string eventCategory return await eventsQuery.AsNoTracking().ToListAsync(); } - // Helper per ottenere le date disponibili (per la dropdown) public async Task> GetAvailableEventDatesAsync() { return await _db.Events diff --git a/TourismApp/Services/IEmailService.cs b/TourismApp/Services/IEmailService.cs index 901c472..3193529 100644 --- a/TourismApp/Services/IEmailService.cs +++ b/TourismApp/Services/IEmailService.cs @@ -7,13 +7,11 @@ namespace TourismApp.Services { - // 1. DEFINIZIONE INTERFACCIA (Il "Contratto") public interface IEmailService { Task SendEmailAsync(string toEmail, string subject, string message); } - // 2. IMPLEMENTAZIONE (La Logica Reale) public class EmailService : IEmailService { private readonly IConfiguration _configuration; @@ -29,13 +27,11 @@ public async Task SendEmailAsync(string toEmail, string subject, string message) { try { - // Lettura configurazione sicura da appsettings.json var smtpServer = _configuration["EmailSettings:SmtpServer"]; var portString = _configuration["EmailSettings:Port"]; var senderEmail = _configuration["EmailSettings:SenderEmail"]; var senderPassword = _configuration["EmailSettings:SenderPassword"]; - // Validazione base per evitare crash se manca la config if (string.IsNullOrEmpty(smtpServer) || string.IsNullOrEmpty(senderEmail)) { throw new Exception("Configurazione Email mancante in appsettings.json"); @@ -43,7 +39,6 @@ public async Task SendEmailAsync(string toEmail, string subject, string message) int port = int.TryParse(portString, out var p) ? p : 587; - // Creazione Messaggio var email = new MimeMessage(); email.From.Add(new MailboxAddress("TourismApp Team", senderEmail)); email.To.Add(new MailboxAddress("", toEmail)); @@ -54,10 +49,8 @@ public async Task SendEmailAsync(string toEmail, string subject, string message) Text = message }; - // Invio SMTP using (var smtp = new SmtpClient()) { - // Bypass certificati SSL (utile in dev, rimuovere in prod se hai certificati validi) smtp.ServerCertificateValidationCallback = (s, c, h, e) => true; await smtp.ConnectAsync(smtpServer, port, false); @@ -71,7 +64,7 @@ public async Task SendEmailAsync(string toEmail, string subject, string message) catch (Exception ex) { _logger.LogError($"Errore invio email a {toEmail}: {ex.Message}"); - throw; // Rilancia l'errore per gestirlo nel Controller + throw; } } } diff --git a/TourismApp/Services/RankingServices.cs b/TourismApp/Services/RankingServices.cs index 1a9ed1a..dc11ee7 100644 --- a/TourismApp/Services/RankingServices.cs +++ b/TourismApp/Services/RankingServices.cs @@ -15,33 +15,28 @@ public RankingService(AppDbContext context) public async Task> GetPersonalizedFeedAsync(string userId) { - // 1. Prendi tutti i POI dal DB (inclusi i dati del comune per le foto) var allPois = await _context.PointsOfInterest .Include(p => p.Municipality) .ToListAsync(); - // 2. Se l'utente è anonimo, restituisci lista alfabetica if (string.IsNullOrEmpty(userId)) { return allPois.OrderBy(p => p.Name).ToList(); } - // 3. Prendi le categorie preferite dall'utente (es. "Food", "Art") var userCategories = await _context.UserPreferences .Where(up => up.UserId == userId && up.Score > 0) .Select(up => up.Category) .ToListAsync(); - // Se l'utente non ha preferenze, lista normale if (!userCategories.Any()) { return allPois.OrderBy(p => p.Name).ToList(); } - // 4. L'ALGORITMO: Ordina mettendo PRIMA quelli che matchano le categorie var sortedList = allPois - .OrderByDescending(poi => userCategories.Contains(poi.Category) ? 1 : 0) // 1 = Match, 0 = No Match - .ThenBy(poi => poi.Name) // A parità di match, usa l'alfabeto + .OrderByDescending(poi => userCategories.Contains(poi.Category) ? 1 : 0) + .ThenBy(poi => poi.Name) .ToList(); return sortedList; diff --git a/TourismApp/Services/TourismDataService.cs b/TourismApp/Services/TourismDataService.cs index 675cce6..63d5147 100644 --- a/TourismApp/Services/TourismDataService.cs +++ b/TourismApp/Services/TourismDataService.cs @@ -19,15 +19,11 @@ public async Task> SearchAsync(string userQuestion) userQuestion = userQuestion.ToLower(); var results = new List(); - // Recupero ID del comune var municipalityId = await _context.Municipalities .Where(m => m.Name == MUNICIPALITY_NAME) .Select(m => m.Id) .FirstAsync(); - // ======================= - // NEWS - tutte le news - // ======================= var newsKeywords = new[] { "news", "notizia", "notizie", "novità", "aggiornamenti" }; if (newsKeywords.Any(k => userQuestion.Contains(k)) || userQuestion.Contains("matelica")) { @@ -40,9 +36,6 @@ public async Task> SearchAsync(string userQuestion) results.AddRange(news); } - // ======================= - // POI - tutti o filtrati per categoria - // ======================= var categories = new[] { "art", "nature", "food", "sleep", "shop", "fun", "route", "organization" }; foreach (var cat in categories) { @@ -59,7 +52,6 @@ public async Task> SearchAsync(string userQuestion) } } - // Se non ha specificato categoria ma chiede POI generici if (userQuestion.Contains("museo") || userQuestion.Contains("monumento") || userQuestion.Contains("chiesa") || userQuestion.Contains("luogo")) { @@ -73,9 +65,6 @@ public async Task> SearchAsync(string userQuestion) results.AddRange(pois); } - // ======================= - // EVENTS - tutti o evento specifico - // ======================= if (userQuestion.Contains("evento") || userQuestion.Contains("festival") || userQuestion.Contains("quando")) { var events = await _context.Events @@ -85,7 +74,6 @@ public async Task> SearchAsync(string userQuestion) results.AddRange(events); } - // Matching evento per nome preciso (es. “Festival del Verdicchioâ€) var namedEvents = await _context.Events .Where(e => e.MunicipalityId == municipalityId && (userQuestion.Contains(e.Name.ToLower()) || e.Name.ToLower().Contains(userQuestion))) @@ -93,7 +81,6 @@ public async Task> SearchAsync(string userQuestion) .ToListAsync(); results.AddRange(namedEvents); - // Matching POI per nome var namedPois = await _context.PointsOfInterest .Where(p => p.MunicipalityId == municipalityId && (userQuestion.Contains(p.Name.ToLower()) || p.Name.ToLower().Contains(userQuestion))) @@ -104,9 +91,6 @@ public async Task> SearchAsync(string userQuestion) .ToListAsync(); results.AddRange(namedPois); - // ======================= - // Se non ci sono risultati -> fallback - // ======================= if (!results.Any()) { results.Add("NESSUN DATO"); diff --git a/TourismApp/ViewModels/QuestionnaireViewModel.cs b/TourismApp/ViewModels/QuestionnaireViewModel.cs index 75a9ca1..25751cb 100644 --- a/TourismApp/ViewModels/QuestionnaireViewModel.cs +++ b/TourismApp/ViewModels/QuestionnaireViewModel.cs @@ -4,7 +4,6 @@ namespace TourismApp.ViewModels { public class QuestionnaireViewModel { - // --- SEZIONE 1: INTERESSI (Checkbox) --- [Display(Name = "Arte, Storia e Cultura")] public bool InterestArt { get; set; } @@ -20,10 +19,6 @@ public class QuestionnaireViewModel [Display(Name = "Svago e Relax")] public bool InterestFun { get; set; } - - // --- SEZIONE 2: TIPO DI VIAGGIO (Radio Button) --- - // RINOMINATO da TravelStyle a TravelType per matchare View e Controller - //[Required(ErrorMessage = "Seleziona il tuo stile di viaggio")] (opzionale) public string? TravelType { get; set; } } } \ No newline at end of file diff --git a/TourismApp/ViewModels/RegisterViewModel.cs b/TourismApp/ViewModels/RegisterViewModel.cs index ba38003..0eaf138 100644 --- a/TourismApp/ViewModels/RegisterViewModel.cs +++ b/TourismApp/ViewModels/RegisterViewModel.cs @@ -2,14 +2,11 @@ namespace TourismApp.ViewModels { - /// - /// View Model for User Registration. - /// Includes strict validation logic (Input Validation) to ensure data integrity and security before hitting the controller. - /// + public class RegisterViewModel { [Required(ErrorMessage = "Name is required")] - [StringLength(50, ErrorMessage = "Name cannot exceed 50 characters.")] // SECURITY: Prevent DB truncation/DoS + [StringLength(50, ErrorMessage = "Name cannot exceed 50 characters.")] [Display(Name = "Name")] public string Name { get; set; } @@ -20,7 +17,7 @@ public class RegisterViewModel [Required(ErrorMessage = "Date of birth is required")] [DataType(DataType.Date)] - [Display(Name = "Date of Birth")] // Fixed typo from "Birth of date" + [Display(Name = "Date of Birth")] public DateTime DateOfBirth { get; set; } [Required(ErrorMessage = "Email is required")] @@ -32,8 +29,6 @@ public class RegisterViewModel [Required(ErrorMessage = "Password is required")] [DataType(DataType.Password)] [Display(Name = "Set Password")] - // SECURITY NOTE: Enforces complexity policy (1 Upper, 1 Lower, 1 Digit, Min 8 chars) - // This regex mitigates Weak Password vulnerabilities. [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$", ErrorMessage = "Your password must contain at least 1 lowercase letter, 1 uppercase letter, 1 number, and be at least 8 characters long.")] public string Password { get; set; } diff --git a/TourismApp/Views/Account/Register.cshtml b/TourismApp/Views/Account/Register.cshtml index bb8aa83..3d12563 100644 --- a/TourismApp/Views/Account/Register.cshtml +++ b/TourismApp/Views/Account/Register.cshtml @@ -83,7 +83,6 @@ await Html.RenderPartialAsync("_ValidationScriptsPartial.cshtml"); }