Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 2 additions & 26 deletions TourismApp/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
// ==========================

[HttpGet]
public IActionResult Login(string returnUrl = null)

Check warning on line 35 in TourismApp/Controllers/AccountController.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
ViewData["ReturnUrl"] = returnUrl;
return View();
Expand All @@ -40,7 +40,7 @@

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)

Check warning on line 43 in TourismApp/Controllers/AccountController.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
ViewData["ReturnUrl"] = returnUrl;
if (!ModelState.IsValid) return View(model);
Expand All @@ -57,15 +57,13 @@

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");
}

Expand All @@ -87,10 +85,6 @@
return RedirectToAction("Index", "Home");
}

// ==========================
// REGISTRAZIONE
// ==========================

[HttpGet]
public IActionResult Register()
{
Expand Down Expand Up @@ -180,7 +174,7 @@
Email = pendingUser.Email,
UserName = pendingUser.Email,
EmailConfirmed = true,
PasswordHash = pendingUser.PasswordHash // Hash recuperato
PasswordHash = pendingUser.PasswordHash
};

var result = await _userManager.CreateAsync(user);
Expand All @@ -195,36 +189,26 @@
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<IActionResult> 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 = $"<p>Click here to reset your password: <a href='{resetLink}'>Reset Password</a></p>";

Expand All @@ -233,14 +217,12 @@
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)
{
Expand All @@ -251,7 +233,6 @@
return View(new ChangePasswordViewModel { Token = token, Email = email });
}

// 5. POST: Salvataggio nuova password
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
Expand Down Expand Up @@ -279,17 +260,12 @@
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);
Expand Down
2 changes: 1 addition & 1 deletion TourismApp/Controllers/ChatController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace TourismApp.Controllers
{
[Route("api/[controller]")] // Questo crea l'indirizzo: /api/chat
[Route("api/[controller]")]
[ApiController]
public class ChatController : ControllerBase
{
Expand Down
11 changes: 2 additions & 9 deletions TourismApp/Controllers/ExploreController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
_exploreService = exploreService;
}

// Ho aggiunto 'sort' ai parametri per ordinamento lato server (name asc/desc).
public async Task<IActionResult> Index(string section = "poi", string category = null, string q = null, string eventCategory = null, string date = null, string sort = null)

Check warning on line 27 in TourismApp/Controllers/ExploreController.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 27 in TourismApp/Controllers/ExploreController.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 27 in TourismApp/Controllers/ExploreController.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 27 in TourismApp/Controllers/ExploreController.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 27 in TourismApp/Controllers/ExploreController.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
var userId = _userManager.GetUserId(User);

Expand All @@ -36,13 +35,10 @@
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();
Expand All @@ -53,9 +49,7 @@
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)
Expand All @@ -67,14 +61,13 @@
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")
Expand Down
13 changes: 3 additions & 10 deletions TourismApp/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -14,9 +14,7 @@ public class HomeController : Controller
private readonly ILogger<HomeController> _logger;
private readonly UserManager<Users> _userManager;
private readonly AppDbContext _context;
// private readonly RankingService _rankingService; // Scommenta quando userai il ranking

// Iniettiamo UserManager e DbContext
public HomeController(ILogger<HomeController> logger,
UserManager<Users> userManager,
AppDbContext context)
Expand All @@ -26,18 +24,13 @@ public HomeController(ILogger<HomeController> 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();
}

Expand Down
3 changes: 0 additions & 3 deletions TourismApp/Controllers/PreferencesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public async Task<IActionResult> 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"
};
Expand All @@ -59,7 +58,6 @@ public async Task<IActionResult> 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();
Expand All @@ -72,7 +70,6 @@ public async Task<IActionResult> 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);
Expand Down
16 changes: 1 addition & 15 deletions TourismApp/Data/AppDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,25 @@

namespace TourismApp.Data
{
/// <summary>
/// Represents the database session.
/// Inherits from IdentityDbContext to include all tables required for ASP.NET Core Identity (Users, Roles, Claims).
/// </summary>
public class AppDbContext : IdentityDbContext<Users>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}

// Domain Entities
public DbSet<Municipality> Municipalities { get; set; }
public DbSet<PointOfInterest> PointsOfInterest { get; set; }
public DbSet<Event> Events { get; set; }
public DbSet<News> News { get; set; }

// Custom Security Entity for "Idempotent Registration" flow
public DbSet<PendingUser> PendingUsers { get; set; }

public DbSet<UserPreference> UserPreferences { get; set; }

/// <summary>
/// Configures the schema needed for the Identity framework and custom entity constraints.
/// </summary>
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<Municipality>().HasIndex(m => m.Name).IsUnique();
}
}
}
6 changes: 2 additions & 4 deletions TourismApp/Models/Dtos/PoiCardDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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; }
}
}
3 changes: 1 addition & 2 deletions TourismApp/Models/Event.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
3 changes: 1 addition & 2 deletions TourismApp/Models/Municipality.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations;

using System.ComponentModel.DataAnnotations;

Check warning on line 4 in TourismApp/Models/Municipality.cs

View workflow job for this annotation

GitHub Actions / build

The using directive for 'System.ComponentModel.DataAnnotations' appeared previously in this namespace

namespace TourismApp.Models
{
Expand All @@ -11,10 +11,9 @@
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<PointOfInterest> PointsOfInterest { get; set; } = new List<PointOfInterest>();
public ICollection<Event> Events { get; set; } = new List<Event>();
public ICollection<News> News { get; set; } = new List<News>();
Expand Down
2 changes: 1 addition & 1 deletion TourismApp/Models/News.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
4 changes: 2 additions & 2 deletions TourismApp/Models/PendingUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
5 changes: 0 additions & 5 deletions TourismApp/Models/UserPreference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
6 changes: 2 additions & 4 deletions TourismApp/Services/AiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -23,7 +23,7 @@ public async Task<string> 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 },
Expand All @@ -39,8 +39,6 @@ public async Task<string> 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();
}
Expand Down
Loading
Loading