using FitActiveSiteV2.Classi;
using FitActiveSiteV2.Services.SediWebhook;
using FitActiveSiteV2.DBModel;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
using System.Security.Claims;
namespace FitActiveSiteV2.Controllers
{
public class AdminClubController : Controller
{
private readonly FitActiveAppTestContext _db;
private readonly IWebHostEnvironment _env;
private readonly ILogger
_logger;
// Limiti applicati sia lato server sia, nella view, lato browser.
// Il controllo server resta quello realmente vincolante.
private const long MaxCarouselImageBytes = 8L * 1024 * 1024; // 8 MB per foto
private const long MaxCarouselTotalBytes = 24L * 1024 * 1024; // totale foto per invio
private const long MaxPalinsestoImageBytes = 10L * 1024 * 1024; // 10 MB per palinsesto
private const long MaxCarouselRequestBytes = 28L * 1024 * 1024; // include overhead multipart
private const long MaxPalinsestoRequestBytes = 12L * 1024 * 1024;
private const long MaxImagePixels = 60_000_000; // protezione immagini anomale
public AdminClubController(FitActiveAppTestContext db, IWebHostEnvironment env, ILogger
logger)
{
_db = db;
_env = env;
_logger = logger;
}
// ======================================
// REGOLE BASE (come gli altri controller)
// ======================================
private bool IsDevOrSamuele()
{
var u = (UserName ?? "").Trim().ToUpperInvariant();
var t = (Token ?? "").Trim().ToUpperInvariant();
return u == "DEVELOPER" || u == "SAMUELE"
|| t == "DEVELOPER" || t == "SAMUELE";
}
// Super amministrativi: Super, ILARIA, DEVELOPER e SAMUELE.
private bool IsSuperLike()
=> IsSuper() || IsDevOrSamuele();
private string? Token
{
get
{
var sessionToken = HttpContext.Session.GetString("token");
return !string.IsNullOrWhiteSpace(sessionToken)
? sessionToken
: HttpContext.User?.FindFirst("AdminAuthority")?.Value;
}
}
private string? SessionUser => HttpContext.Session.GetString("sessionUser");
private string? UserName => HttpContext.User?.Identity?.Name;
private bool IsLogged()
=> !string.IsNullOrWhiteSpace(Token)
|| !string.IsNullOrWhiteSpace(SessionUser)
|| string.Equals(UserName, "ILARIA", StringComparison.OrdinalIgnoreCase)
|| IsDevOrSamuele();
private bool IsSuper()
=> string.Equals(UserName, "ILARIA", StringComparison.OrdinalIgnoreCase)
|| string.Equals(Token, "Super", StringComparison.OrdinalIgnoreCase);
private bool IsSede()
=> string.Equals(Token, "Sede", StringComparison.OrdinalIgnoreCase);
private async Task
HasClubAccessAsync()
{
// Conserva le autorizzazioni amministrative e Sede già esistenti.
if (IsSuperLike() || IsSede())
return true;
if (HttpContext.User?.Identity?.IsAuthenticated != true)
return false;
// Per gli altri account il diritto nasce dallo stato attuale del club.
// Una modifica Opened 1 -> 3 viene riletta al prossimo accesso.
var ownClub = await GetLoggedSedeClubAsync();
return ownClub != null && ownClub.Opened == 3;
}
private IActionResult GoLogin() => RedirectToAction("Index", "Admin");
private static string NormalizeClubKey(string? idOrTitle)
{
if (string.IsNullOrWhiteSpace(idOrTitle))
return "";
var s = Uri.UnescapeDataString(idOrTitle.Trim());
if (s.StartsWith("FitActive ", StringComparison.OrdinalIgnoreCase))
s = s.Substring("FitActive ".Length);
else if (s.StartsWith("FitActive", StringComparison.OrdinalIgnoreCase))
s = s.Substring("FitActive".Length);
return s
.Replace(" ", "")
.Replace("-", "")
.Trim()
.ToLowerInvariant();
}
private static string NormalizeTitle(string? title)
=> (title ?? "").Trim();
private async Task
FindClubByKeyAsync(string key)
{
key = NormalizeClubKey(key);
if (string.IsNullOrWhiteSpace(key))
return null;
// La normalizzazione viene fatta nello stesso modo sia per l'URL
// sia per il titolo del DB, così non ci sono differenze tra sede,
// titolo completo e slug.
var list = await _db.ClubNews.AsNoTracking().ToListAsync();
return list.FirstOrDefault(c =>
NormalizeClubKey(c.Title) == key);
}
private string GetLoggedSedeKey()
{
// La stessa chiave è usata dalla sidebar per associare l'account al club.
// È valida anche per un account senza token Sede o Type 6/9.
var raw = !string.IsNullOrWhiteSpace(SessionUser)
? SessionUser
: UserName;
return NormalizeClubKey(raw);
}
private async Task
GetLoggedSedeClubAsync()
{
if (!IsLogged())
return null;
var key = GetLoggedSedeKey();
return await FindClubByKeyAsync(key);
}
private bool CanManageClub(ClubNew? club)
{
if (club == null)
return false;
if (IsSuperLike())
return true;
var ownKey = GetLoggedSedeKey();
var isOwnClub = !string.IsNullOrWhiteSpace(ownKey)
&& ownKey == NormalizeClubKey(club.Title);
if (!isOwnClub)
return false;
// Il nuovo accesso automatico riguarda esclusivamente la propria sede
// aperta. Non assegna permessi su altri club né cambia il Type del login.
return IsSede()
|| (HttpContext.User?.Identity?.IsAuthenticated == true
&& club.Opened == 3);
}
private async Task
RedirectToAllowedClubAsync(string? preferredKey = null)
{
if (IsSuperLike())
{
var key = NormalizeClubKey(preferredKey);
return string.IsNullOrWhiteSpace(key)
? RedirectToAction(nameof(Elenco_Club))
: RedirectToAction(nameof(ModifyClub), new { id = key });
}
var ownClub = await GetLoggedSedeClubAsync();
if (CanManageClub(ownClub))
return RedirectToAction(nameof(ModifyClub), new { id = NormalizeClubKey(ownClub!.Title) });
return GoLogin();
}
private async Task
FindHiddenByClubTitleAsync(string? clubTitle)
{
if (string.IsNullOrWhiteSpace(clubTitle)) return null;
return await _db.ClubFlagHiddens.FirstOrDefaultAsync(h => h.Title == clubTitle);
}
private async Task
GetOrCreateHiddenForClubAsync(ClubNew club)
{
var hidden = await FindHiddenByClubTitleAsync(club.Title);
if (hidden != null) return hidden;
var hasFacebook = !string.IsNullOrWhiteSpace(club.Fblink);
var hasPhone = !string.IsNullOrWhiteSpace(club.Phone);
hidden = new ClubFlagHidden
{
Title = club.Title,
Description = 1,
Palinsesto = (byte)(string.IsNullOrWhiteSpace(club.ImgPalinsesto) ? 0 : 1),
DoveSiamo = 1,
VideoYt = (byte)(string.IsNullOrWhiteSpace(club.LinkYt) ? 0 : 1),
TitleFlag = 1,
OrariFlag = 1,
OrariTextFlag = 1,
Email = 1,
Phone = (byte)(hasPhone ? 1 : 0),
Via = 1,
Tour3D = (byte)(string.IsNullOrWhiteSpace(club.Tour3D) ? 0 : 1),
Fblink = (byte)(hasFacebook ? 1 : 0),
VideoCarousel = 0,
Gallery = 1,
FbLinkString = (byte)(string.IsNullOrWhiteSpace(club.FbLinkString) ? 0 : 1)
};
_db.ClubFlagHiddens.Add(hidden);
return hidden;
}
private string ClubFolderOnDisk(string clubKey)
{
return Path.Combine(_env.WebRootPath, "images", "Sedi", clubKey);
}
private string PalinsestiFolderOnDisk()
{
return Path.Combine(_env.WebRootPath, "i-club", "admin", "ScanFile", "UploadCorsi");
}
private static string SafeFilePrefix(string? value, int clubId)
{
var normalized = NormalizeClubKey(value);
var safe = new string(normalized.Where(char.IsLetterOrDigit).ToArray());
return string.IsNullOrWhiteSpace(safe) ? $"club{clubId}" : safe;
}
private void DeleteWebRootFileIfExists(string? relativePath, string? exceptFullPath = null)
{
if (string.IsNullOrWhiteSpace(relativePath)) return;
var relative = relativePath.Trim().TrimStart('/', '\\')
.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
var webRoot = Path.GetFullPath(_env.WebRootPath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
var fullPath = Path.GetFullPath(Path.Combine(webRoot, relative));
// Impedisce che un percorso anomalo letto dal DB esca da wwwroot.
if (!fullPath.StartsWith(webRoot, StringComparison.OrdinalIgnoreCase)) return;
if (!string.IsNullOrWhiteSpace(exceptFullPath)
&& string.Equals(fullPath, Path.GetFullPath(exceptFullPath), StringComparison.OrdinalIgnoreCase)) return;
if (System.IO.File.Exists(fullPath))
System.IO.File.Delete(fullPath);
}
private List
ReadClubImages(string clubKey)
{
try
{
var folder = ClubFolderOnDisk(clubKey);
if (!Directory.Exists(folder))
folder = Path.Combine(_env.WebRootPath, "images", "Sedi", "Default");
var folderName = new DirectoryInfo(folder).Name;
var files = Directory.GetFiles(folder)
.Where(p => !p.Contains(".DS_Store"))
.Select(p => new FileModel
{
Name = "/images/Sedi/" + folderName + "/" + Path.GetFileName(p),
FileName = "images/Sedi/" + folderName + "/" + Path.GetFileName(p)
})
.ToList();
return files;
}
catch
{
return new List
();
}
}
private static bool IsTooVertical(Stream stream, double maxRatio = 1.000001)
{
stream.Position = 0;
var info = SixLabors.ImageSharp.Image.Identify(stream);
if (info == null) return true;
var ratio = (double)info.Height / info.Width;
return ratio > maxRatio;
}
private static void ValidateImageOrThrow(IFormFile? file, long maxBytes)
{
if (file == null || file.Length <= 0)
throw new Exception("File mancante.");
if (file.Length > maxBytes)
{
var maxMb = maxBytes / 1024 / 1024;
throw new Exception($"Immagine troppo pesante. Dimensione massima: {maxMb} MB.");
}
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (ext != ".jpg" && ext != ".jpeg" && ext != ".png" && ext != ".webp")
throw new Exception("Formato non supportato. Usa JPG/JPEG/PNG/WEBP.");
// Non ci fidiamo della sola estensione: ImageSharp deve riconoscere
// e decodificare realmente il contenuto del file.
using (var identifyStream = file.OpenReadStream())
{
var info = SixLabors.ImageSharp.Image.Identify(identifyStream);
if (info == null || info.Width <= 0 || info.Height <= 0)
throw new Exception("Il file selezionato non è un'immagine valida.");
if ((long)info.Width * info.Height > MaxImagePixels)
throw new Exception("Risoluzione immagine troppo elevata.");
}
using var decodeStream = file.OpenReadStream();
using var decodedImage = SixLabors.ImageSharp.Image.Load(decodeStream);
}
private static async Task
SaveClubImageAsync(IFormFile file, string destinationFolder)
{
ValidateImageOrThrow(file, MaxCarouselImageBytes);
using var imageStream = file.OpenReadStream();
using var image = await SixLabors.ImageSharp.Image.LoadAsync(imageStream);
// sistema eventuale rotazione EXIF da telefono/fotocamera
image.Mutate(x => x.AutoOrient());
// formato finale 16:9
const int targetWidth = 1920;
const int targetHeight = 1080;
const double targetRatio = 16.0 / 9.0;
var currentRatio = (double)image.Width / image.Height;
if (currentRatio > targetRatio)
{
// troppo larga: taglio i lati
var newWidth = (int)(image.Height * targetRatio);
var x = (image.Width - newWidth) / 2;
image.Mutate(i => i.Crop(new Rectangle(x, 0, newWidth, image.Height)));
}
else if (currentRatio < targetRatio)
{
// troppo alta: taglio sopra/sotto
var newHeight = (int)(image.Width / targetRatio);
var y = (image.Height - newHeight) / 2;
image.Mutate(i => i.Crop(new Rectangle(0, y, image.Width, newHeight)));
}
image.Mutate(x => x.Resize(targetWidth, targetHeight));
var cleanName = Path.GetFileNameWithoutExtension(file.FileName)
.Replace(" ", "_")
.Replace("-", "_");
var fileName = $"{cleanName}_{DateTime.UtcNow.Ticks}.jpg";
var path = Path.Combine(destinationFolder, fileName);
try
{
await image.SaveAsJpegAsync(path, new JpegEncoder { Quality = 85 });
return path;
}
catch
{
if (System.IO.File.Exists(path)) System.IO.File.Delete(path);
throw;
}
}
private void CleanupNewImages(IEnumerable
paths)
{
foreach (var path in paths)
{
try { if (System.IO.File.Exists(path)) System.IO.File.Delete(path); }
catch (Exception ex) { _logger.LogWarning(ex, "Pulizia immagine non completata."); }
}
}
// ======================================
// VIEWS PRINCIPALI
// ======================================
[HttpGet]
public async Task
Elenco_Club()
{
if (!IsLogged())
return GoLogin();
// L'elenco completo dei club è riservato esclusivamente ai Super.
// Gli account di sede, compresi quelli abilitati automaticamente
// dall'apertura, vengono rimandati esclusivamente alla propria palestra.
if (!IsSuperLike())
{
var ownClub = await GetLoggedSedeClubAsync();
if (CanManageClub(ownClub))
return RedirectToAction(nameof(ModifyClub), new { id = NormalizeClubKey(ownClub!.Title) });
TempData["Errore"] = ownClub == null
? "Non è stato possibile associare il tuo utente a una sede."
: "La tua sede è ancora in prevendita.";
return GoLogin();
}
var clubs = await _db.ClubNews
.AsNoTracking()
.OrderBy(x => x.Title)
.ToListAsync();
ViewBag.InfoSede = clubs;
ViewBag.Regioni = clubs
.Select(c => c.Region)
.Where(r => !string.IsNullOrWhiteSpace(r))
.Distinct()
.OrderBy(r => r)
.ToList();
return View();
}
[HttpGet]
public IActionResult CreateClub()
{
if (!IsLogged()) return GoLogin();
if (!IsSuperLike()) return GoLogin();
ViewBag.CanManageAdvanced = IsDevOrSamuele();
var defaultClub = _db.ClubNews.AsNoTracking().FirstOrDefault(c => c.Title == "FitActive Bovisio Masciago");
var hidden = _db.ClubFlagHiddens.AsNoTracking().Where(h => h.Title == "FitActive Bovisio Masciago").ToList();
ViewBag.CampiNascosti = hidden;
ViewBag.InfoSede = defaultClub != null ? new List
{ defaultClub } : new List
();
ViewBag.Slider = ReadClubImages("bovisiomasciago");
ViewBag.All = _db.ClubNews.AsNoTracking().ToList();
return View();
}
[HttpGet]
public async Task
ModifyClub(string? id)
{
// =========================================
// CONTROLLO LOGIN E PERMESSI
// =========================================
if (!IsLogged())
return GoLogin();
if (!await HasClubAccessAsync())
return Forbid();
ClubNew? club = null;
bool isSuper = IsSuperLike();
// Qualsiasi account non amministrativo ammesso dal controllo sopra
// può caricare soltanto il proprio club, anche senza Type 6/9.
bool isSede = !isSuper;
// =========================================
// UTENTE SEDE
// =========================================
if (isSede)
{
// Recupera esclusivamente la sede
// associata all'utente autenticato.
club = await GetLoggedSedeClubAsync();
if (club == null)
{
TempData["Errore"] =
"Impossibile trovare la sede associata al tuo account. " +
"Verifica l'associazione tra utente e palestra.";
return RedirectToAction("Index", "Admin");
}
// Impedisce di modificare altre sedi.
string ownKey = NormalizeClubKey(club.Title);
string requestedKey = NormalizeClubKey(id);
if (!string.IsNullOrWhiteSpace(requestedKey)
&& requestedKey != ownKey)
{
return RedirectToAction(
nameof(ModifyClub),
new { id = ownKey }
);
}
}
else
{
// =========================================
// SUPER / DEVELOPER / SAMUELE
// =========================================
string key = NormalizeClubKey(id);
if (string.IsNullOrWhiteSpace(key))
return RedirectToAction(nameof(Elenco_Club));
club = await FindClubByKeyAsync(key);
if (club == null)
{
TempData["Errore"] = "Club non trovato.";
return RedirectToAction(nameof(Elenco_Club));
}
}
// =========================================
// CONTROLLO FINALE AUTORIZZAZIONE
// =========================================
if (club == null)
return NotFound();
if (!CanManageClub(club))
return Forbid();
// =========================================
// GESTIONE STATO SEDE
// =========================================
// Opened = 1: Prevendita
// Opened = 3: Sede aperta
//
// Super e utenti con il ruolo Sede mantengono i permessi già previsti.
// Per gli altri account, Opened = 3 abilita automaticamente la propria sede.
bool isPreOpening = club.Opened == 1;
bool isOpened = club.Opened == 3;
string clubKey = NormalizeClubKey(club.Title);
// =========================================
// RECUPERO / CREAZIONE FLAG
// =========================================
var hidden = await GetOrCreateHiddenForClubAsync(club);
if (hidden.Id == 0)
await _db.SaveChangesAsync();
// =========================================
// PERMESSI VIEW
// =========================================
ViewBag.CanManageAdvanced = IsDevOrSamuele();
ViewBag.CanRenameClub = isSuper;
ViewBag.IsSuperLike = isSuper;
ViewBag.IsSede = isSede;
ViewBag.CanEditClub = true;
ViewBag.CanChangeOpenedStatus = isSuper;
// =========================================
// INFORMAZIONI STATO
// =========================================
ViewBag.IsPreOpening = isPreOpening;
ViewBag.IsOpened = isOpened;
ViewBag.OpenedStatus = club.Opened;
// =========================================
// DATI SEDE
// =========================================
ViewBag.InfoSede = new List
{
club
};
ViewBag.CampiNascosti = new List
{
hidden
};
// =========================================
// IMMAGINI
// =========================================
ViewBag.Slider = ReadClubImages(clubKey);
// =========================================
// ELENCO SEDI
// =========================================
// I Super possono vedere tutte le sedi.
// Gli utenti Sede vedono solo la propria.
ViewBag.All = isSuper
? await _db.ClubNews
.AsNoTracking()
.OrderBy(c => c.Title)
.ToListAsync()
: new List
{ club };
// =========================================
// VIEW
// =========================================
return View();
}
// ======================================
// AZIONI AJAX / UPDATE TESTI
// ======================================
[HttpPost]
public async Task
ModifyAll(
int id,
string? Titolo,
string? Descrizione,
string? Orari,
string? Email,
string? Via,
string? Paese,
string? Phone,
string? FacebookPageUrl,
string? InstagramUrl)
{
if (!IsLogged() || !await HasClubAccessAsync())
return Unauthorized();
string? oldGallery = null;
string? newGallery = null;
var databaseSaved = false;
try
{
var club =
await _db.ClubNews
.FirstOrDefaultAsync(c => c.Id == id);
if (club == null)
return NotFound($"Club con id={id} non trovato.");
if (!CanManageClub(club))
return Forbid();
// ============================================================
// RECUPERO / CREAZIONE FLAG DELLA SEDE
// ============================================================
var hidden =
await GetOrCreateHiddenForClubAsync(club);
// ============================================================
// TITOLO
// SOLO SUPER / DEVELOPER / SAMUELE
// ============================================================
var oldTitle =
club.Title;
if (IsSuperLike() &&
!string.IsNullOrWhiteSpace(Titolo))
{
var newTitle =
Titolo.Trim();
if (!string.Equals(
oldTitle,
newTitle,
StringComparison.Ordinal))
{
var oldKey = NormalizeClubKey(oldTitle);
var newKey = NormalizeClubKey(newTitle);
if (string.IsNullOrWhiteSpace(newKey) || newKey.Contains("..")
|| newKey.IndexOfAny(new[] { '/', '\\' }) >= 0)
return BadRequest("Nome sede non valido.");
var otherTitles = await _db.ClubNews.AsNoTracking().Where(x => x.Id != id).Select(x => x.Title).ToListAsync();
if (otherTitles.Any(t => NormalizeClubKey(t) == newKey)) return BadRequest("Nome sede o URL già utilizzato.");
if (oldKey != newKey && !string.IsNullOrWhiteSpace(oldKey))
{
var source = ClubFolderOnDisk(oldKey);
var destination = ClubFolderOnDisk(newKey);
if (Directory.Exists(source))
{
if (Directory.Exists(destination)) return BadRequest("Cartella immagini del nuovo nome già esistente.");
Directory.Move(source, destination);
oldGallery = source;
newGallery = destination;
}
}
club.Title = newTitle;
// Mantengo sincronizzato ClubFlagHidden
hidden.Title =
newTitle;
}
}
// ============================================================
// DESCRIZIONE
// ============================================================
var descrizionePulita =
Descrizione?.Trim() ?? "";
club.Description =
descrizionePulita;
hidden.Description =
(byte)(
!string.IsNullOrWhiteSpace(descrizionePulita)
? 1
: 0
);
// ============================================================
// ORARI
// ============================================================
var orariPuliti =
Orari?.Trim() ?? "";
club.Orari =
orariPuliti;
hidden.OrariFlag =
(byte)(
!string.IsNullOrWhiteSpace(orariPuliti)
? 1
: 0
);
// ============================================================
// EMAIL
// ============================================================
var emailPulita =
Email?.Trim() ?? "";
club.Email =
emailPulita;
hidden.Email =
(byte)(
!string.IsNullOrWhiteSpace(emailPulita)
? 1
: 0
);
// ============================================================
// TELEFONO
// ============================================================
var telefonoPulito =
Phone?.Trim() ?? "";
club.Phone =
telefonoPulito;
hidden.Phone =
(byte)(
!string.IsNullOrWhiteSpace(telefonoPulito)
? 1
: 0
);
// ============================================================
// INDIRIZZO
// ============================================================
var indirizzoPulito =
Via?.Trim() ?? "";
club.Address =
indirizzoPulito;
// Tengo sincronizzato anche OnlyStreet
club.OnlyStreet =
indirizzoPulito;
hidden.Via =
(byte)(
!string.IsNullOrWhiteSpace(indirizzoPulito)
? 1
: 0
);
// ============================================================
// PAESE
// ============================================================
if (!string.IsNullOrWhiteSpace(Paese))
{
club.Country =
Paese.Trim();
}
// ============================================================
// FACEBOOK PUBBLICO
//
// ATTENZIONE:
// FbLinkString = link normale Facebook
// Fblink = plugin iframe Facebook
//
// Il plugin rimane sotto controllo Developer/Samuele.
// ============================================================
var facebookPulito =
FacebookPageUrl?.Trim() ?? "";
club.FbLinkString =
facebookPulito;
hidden.FbLinkString =
(byte)(
!string.IsNullOrWhiteSpace(facebookPulito)
? 1
: 0
);
// ============================================================
// INSTAGRAM
// ============================================================
var instagramPulito =
InstagramUrl?.Trim() ?? "";
club.LinkInsta =
instagramPulito;
/*
* NOTA:
*
* Nel ClubFlagHidden attuale NON risulta esserci
* una colonna Instagram / LinkInsta.
*
* Quindi aggiorniamo ClubNew.LinkInsta,
* ma non possiamo impostare un flag Instagram
* finché non aggiungiamo la colonna nel DB/model.
*/
// ============================================================
// NON TOCCARE LE REGOLE DEVELOPER
// ============================================================
//
// NON modifichiamo qui:
//
// hidden.Fblink
// club.Fblink
//
// Latitude
// Longitude
//
// DoveSiamo
// Palinsesto
// VideoCarousel
// Gallery
//
// Queste restano sotto le regole già presenti.
// ============================================================
await _db.SaveChangesAsync();
databaseSaved = true;
return Ok(
"Dati e flag della sede aggiornati correttamente."
);
}
catch (Exception ex)
{
if (!databaseSaved && oldGallery != null && newGallery != null)
{
try
{
if (Directory.Exists(newGallery) && !Directory.Exists(oldGallery)) Directory.Move(newGallery, oldGallery);
}
catch (Exception restoreEx) { _logger.LogError(restoreEx, "Ripristino cartella sede {Id} non riuscito", id); }
}
_logger.LogError(
ex,
"Errore ModifyAll id={Id}",
id
);
return StatusCode(
500,
ex.InnerException?.Message
?? ex.Message
);
}
}
[HttpPost]
public async Task
ModifyFacebook(int id, string? FbLinkString)
{
if (!IsLogged() || !await HasClubAccessAsync()) return Unauthorized();
if (!IsDevOrSamuele()) return Unauthorized();
try
{
var club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == id);
if (club == null) return NotFound();
if (!CanManageClub(club)) return Forbid();
var fb = FbLinkString?.Trim() ?? "";
club.Fblink = fb;
// NON salvare se la colonna è corta
// club.FbLinkString = fb;
var hidden = await FindHiddenByClubTitleAsync(club.Title);
if (hidden != null)
{
var hasFb = !string.IsNullOrWhiteSpace(fb);
hidden.Fblink = (byte)(hasFb ? 1 : 0);
hidden.FbLinkString = (byte)(string.IsNullOrWhiteSpace(club.FbLinkString) ? 0 : 1);
}
await _db.SaveChangesAsync();
return Ok();
}
catch (Exception ex)
{
return StatusCode(500, ex.InnerException?.Message ?? ex.Message);
}
}
[HttpPost]
public async Task
ModifyDoveSiamo(string Latitude, string Longitude, int id)
{
if (!IsLogged() || !await HasClubAccessAsync()) return Unauthorized();
if (!IsDevOrSamuele()) return Unauthorized();
var club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == id);
if (club == null) return NotFound();
if (!CanManageClub(club)) return Forbid();
club.Latitude = Latitude ?? "";
club.Longitude = Longitude ?? "";
await _db.SaveChangesAsync();
return Ok();
}
[HttpPost]
public async Task
ModifyTour3D(string tour3D, int id)
{
if (!IsLogged() || !await HasClubAccessAsync()) return Unauthorized();
var club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == id);
if (club == null) return NotFound();
if (!CanManageClub(club)) return Forbid();
club.Tour3D = tour3D ?? "";
var hidden = await FindHiddenByClubTitleAsync(club.Title);
if (hidden != null)
hidden.Tour3D = (byte)(string.IsNullOrWhiteSpace(club.Tour3D) ? 0 : 1);
await _db.SaveChangesAsync();
return Ok();
}
[HttpPost]
public IActionResult Facebook(string id)
{
try
{
if (string.IsNullOrWhiteSpace(id)) return Content("Errore");
var tmp = id.Replace("https://www.facebook.com/", "");
if (tmp.EndsWith("/")) tmp = tmp.Substring(0, tmp.Length - 1);
var fb = "https://www.facebook.com/plugins/page.php?href=https%3A%2F%2Fwww.facebook.com%2F" +
tmp +
"%2F&tabs&width=340&small_header=false&adapt_container_width=true&hide_cover=false&show_facepile=true&appId=864719213636229";
return Content(fb);
}
catch
{
return Content("Errore");
}
}
[HttpPost]
public async Task
ModificaVideoYT(int id, string UrlVideo, bool Trascinabile)
{
if (!IsLogged() || !await HasClubAccessAsync()) return Unauthorized();
try
{
var embed = UrlToEmbed.UrlToEmbedCode(UrlVideo);
if (Trascinabile)
return Content(embed);
var club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == id);
if (club == null) return NotFound();
if (!CanManageClub(club)) return Forbid();
club.LinkYt = embed;
var hidden = await FindHiddenByClubTitleAsync(club.Title);
if (hidden != null) hidden.VideoYt = 1;
await _db.SaveChangesAsync();
return Content(embed);
}
catch
{
return Content("Errore");
}
}
// ======================================
// CREATE CLUB (form CreateFunction)
// ======================================
[HttpPost]
[ValidateAntiForgeryToken]
[RequestSizeLimit(MaxCarouselRequestBytes)]
public async Task
CreateFunction(
string? Title,
string? Phone,
string? Via,
string? Email,
string? Paese,
string? Orari,
int? Cosa,
string? Faces,
string? FbLink,
string? Lat,
string? Long,
string? VideoYT,
string? Regione,
string? Instagram,
string? Description,
int? Pali,
List
? postedFiles
)
{
ViewBag.CanManageAdvanced = IsDevOrSamuele();
if (!IsLogged() || !IsSuperLike()) return GoLogin();
var newImages = new List
();
var databaseSaved = false;
try
{
var title = NormalizeTitle(Title);
if (string.IsNullOrWhiteSpace(title))
{
TempData["Errore"] = "Titolo sede mancante.";
return RedirectToAction(nameof(CreateClub));
}
if (postedFiles != null && postedFiles.Count > 0)
{
if (postedFiles.Count > 20)
throw new Exception("Puoi caricare al massimo 20 immagini per volta.");
if (postedFiles.Sum(file => file.Length) > MaxCarouselTotalBytes)
throw new Exception("Le immagini selezionate superano il limite complessivo di 24 MB.");
foreach (var file in postedFiles)
ValidateImageOrThrow(file, MaxCarouselImageBytes);
}
var requestedKey = NormalizeClubKey(title);
if (string.IsNullOrWhiteSpace(requestedKey) || requestedKey.Contains("..")
|| requestedKey.IndexOfAny(new[] { '/', '\\' }) >= 0)
throw new Exception("Nome sede non valido.");
var existingTitles = await _db.ClubNews.AsNoTracking().Select(x => x.Title).ToListAsync();
if (existingTitles.Any(t => NormalizeClubKey(t) == requestedKey))
throw new Exception("Esiste già una sede con questo nome o URL.");
var publicFacebook = (FbLink ?? "").Trim();
var pluginFacebook = IsDevOrSamuele() ? (Faces ?? "").Trim() : "";
var video = (VideoYT ?? "").Trim();
var youtubeEmbed = string.IsNullOrEmpty(video) ? ""
: video.Contains("/embed/", StringComparison.OrdinalIgnoreCase) ? video : UrlToEmbed.UrlToEmbedCode(video);
var club = new ClubNew
{
Title = title,
Tour3D = "",
Phone = Phone ?? "",
Address = Via ?? "",
Country = Paese ?? "",
DataApertura = DateTime.Now.Year.ToString(),
Description = (Description ?? "").Trim(),
Email = Email ?? "",
Fblink = pluginFacebook,
FbLinkString = publicFacebook,
ImgPalinsesto = "",
Img = "/images/sedi/Main/" + title.Replace("FitActive ", "").Replace(" ", "") + "Main.jpg",
Latitude = Lat ?? "",
Longitude = Long ?? "",
LinkInsta = Instagram ?? "",
Metres = 1000,
OnlyStreet = Via ?? "",
Opened = 1, // 1 = Prevendita, 3 = Aperta
Orari = Orari ?? "",
Region = Regione ?? "",
State = Paese ?? "",
Placeid = "",
LinkYt = youtubeEmbed
};
_db.ClubNews.Add(club);
var hasFb = !string.IsNullOrWhiteSpace(pluginFacebook);
var hasPhone = !string.IsNullOrWhiteSpace(Phone);
var hidden = new ClubFlagHidden
{
Title = title,
Description = (byte)(string.IsNullOrWhiteSpace(club.Description) ? 0 : 1),
Palinsesto = 0, // Nessun file palinsesto ancora caricato.
DoveSiamo = 1,
VideoYt = (byte)(string.IsNullOrWhiteSpace(youtubeEmbed) ? 0 : 1),
TitleFlag = 1,
OrariFlag = 1,
OrariTextFlag = 1,
Email = 1,
Phone = (byte)(hasPhone ? 1 : 0),
Via = 1,
Tour3D = 0,
Fblink = (byte)(hasFb ? 1 : 0),
VideoCarousel = 0,
Gallery = 1,
FbLinkString = (byte)(string.IsNullOrWhiteSpace(publicFacebook) ? 0 : 1)
};
_db.ClubFlagHiddens.Add(hidden);
var key = NormalizeClubKey(title);
if (postedFiles != null && postedFiles.Count > 0)
{
var folder = ClubFolderOnDisk(key);
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
foreach (var f in postedFiles)
{
newImages.Add(await SaveClubImageAsync(f, folder));
}
}
// Sede, flag ed evento sono confermati nella stessa transazione EF.
await _db.SaveChangesAsync();
databaseSaved = true;
return RedirectToAction(nameof(ModifyClub), new { id = key });
}
catch (Exception ex)
{
if (!databaseSaved) CleanupNewImages(newImages);
_logger.LogError(ex, "Errore CreateFunction");
TempData["Errore"] = ex.InnerException?.Message ?? ex.Message;
return RedirectToAction(nameof(CreateClub));
}
}
// ======================================
// UPLOAD IMMAGINI CAROSELLO (InsertRow)
// ======================================
[HttpPost]
[ValidateAntiForgeryToken]
[RequestSizeLimit(MaxCarouselRequestBytes)]
public async Task
InsertRow(List
? postedFiles, int clubId, string? Sede)
{
if (!IsLogged() || !await HasClubAccessAsync())
return GoLogin();
ClubNew? club = null;
if (clubId > 0)
club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == clubId);
if (club == null && !string.IsNullOrWhiteSpace(Sede))
{
var fallback = await FindClubByKeyAsync(NormalizeClubKey(Sede));
if (fallback != null)
club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == fallback.Id);
}
if (club == null)
{
TempData["Errore"] = "Club non trovato.";
return await RedirectToAllowedClubAsync(Sede);
}
if (!CanManageClub(club))
return Forbid();
var clubKey = NormalizeClubKey(club.Title);
var newImages = new List
();
var databaseSaved = false;
try
{
if (postedFiles == null || postedFiles.Count == 0)
{
TempData["Errore"] = "Seleziona almeno un file.";
return RedirectToAction(nameof(ModifyClub), new { id = clubKey });
}
if (postedFiles.Count > 20)
{
TempData["Errore"] = "Puoi caricare al massimo 20 immagini per volta.";
return RedirectToAction(nameof(ModifyClub), new { id = clubKey });
}
if (postedFiles.Sum(file => file.Length) > MaxCarouselTotalBytes)
{
TempData["Errore"] = "Le immagini selezionate superano il limite complessivo di 24 MB.";
return RedirectToAction(nameof(ModifyClub), new { id = clubKey });
}
foreach (var file in postedFiles)
ValidateImageOrThrow(file, MaxCarouselImageBytes);
var folder = ClubFolderOnDisk(clubKey);
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
foreach (var file in postedFiles)
newImages.Add(await SaveClubImageAsync(file, folder));
SedeWebhookQueue.Add(_db, club, "gallery");
await _db.SaveChangesAsync();
databaseSaved = true;
TempData["Successo"] = "Immagini caricate correttamente.";
return RedirectToAction(nameof(ModifyClub), new { id = clubKey });
}
catch (Exception ex)
{
if (!databaseSaved) CleanupNewImages(newImages);
_logger.LogError(ex, "Errore caricamento immagini per clubId={ClubId}", club.Id);
TempData["Errore"] = "Errore durante il caricamento immagini: " + ex.Message;
return RedirectToAction(nameof(ModifyClub), new { id = clubKey });
}
}
// ======================================
// DELETE IMMAGINE (DeleteRow)
// ======================================
[HttpPost]
[ValidateAntiForgeryToken]
public async Task
DeleteRow(string Nome, int clubId)
{
if (!IsLogged() || !await HasClubAccessAsync())
return Unauthorized();
try
{
var club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == clubId);
if (club == null)
return NotFound("Club non trovato.");
if (!CanManageClub(club))
return Forbid();
var rel = (Nome ?? "").Trim().TrimStart('/', '\\')
.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
if (string.IsNullOrWhiteSpace(rel))
return BadRequest("Percorso immagine mancante.");
var webRoot = Path.GetFullPath(_env.WebRootPath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
var fullPath = Path.GetFullPath(Path.Combine(webRoot, rel));
var allowedFolder = Path.GetFullPath(ClubFolderOnDisk(NormalizeClubKey(club.Title)))
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
// Impedisce path traversal e impedisce a una sede di cancellare
// file di un'altra sede o le immagini della cartella Default.
if (!fullPath.StartsWith(allowedFolder, StringComparison.OrdinalIgnoreCase))
return BadRequest("Il file non appartiene a questa sede.");
string? backup = null;
if (System.IO.File.Exists(fullPath))
{
var folder = Path.Combine(_env.ContentRootPath, "App_Data", "SediWebhookBackup");
Directory.CreateDirectory(folder);
backup = Path.Combine(folder, Guid.NewGuid().ToString("N"));
System.IO.File.Move(fullPath, backup);
}
try
{
SedeWebhookQueue.Add(_db, club, "gallery");
await _db.SaveChangesAsync();
}
catch
{
if (backup != null && System.IO.File.Exists(backup)) System.IO.File.Move(backup, fullPath);
throw;
}
if (backup != null) CleanupNewImages(new[] { backup });
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore DeleteRow clubId={ClubId}", clubId);
return StatusCode(500, ex.InnerException?.Message ?? ex.Message);
}
}
// ======================================
// PALINSESTO (AggiungiPalinsesto)
// ======================================
[HttpPost]
[ValidateAntiForgeryToken]
[RequestSizeLimit(MaxPalinsestoRequestBytes)]
public async Task
AggiungiPalinsesto(
IFormFile? postedFiles,
int clubId)
{
if (!IsLogged() || !await HasClubAccessAsync())
{
return Json(new
{
success = false,
message = "Sessione non valida o accesso non autorizzato."
});
}
string? newFullPath = null;
try
{
// =====================================================
// CLUB
// =====================================================
var club = await _db.ClubNews
.FirstOrDefaultAsync(c => c.Id == clubId);
if (club == null)
{
return Json(new
{
success = false,
message = "Club non trovato."
});
}
// La sede può modificare esclusivamente il proprio club.
if (!CanManageClub(club))
{
return Json(new
{
success = false,
message = "Non sei autorizzato a modificare questo club."
});
}
// =====================================================
// VALIDAZIONE FILE
// =====================================================
ValidateImageOrThrow(
postedFiles,
MaxPalinsestoImageBytes
);
var safePrefix = SafeFilePrefix(
club.Title,
club.Id
);
var oldRelativePath =
club.ImgPalinsesto;
// =====================================================
// CARTELLA PALINSESTI
// =====================================================
var dirFisica =
PalinsestiFolderOnDisk();
if (!Directory.Exists(dirFisica))
{
Directory.CreateDirectory(
dirFisica
);
}
// =====================================================
// NOME FILE
// =====================================================
var ext = Path
.GetExtension(postedFiles!.FileName)
.ToLowerInvariant();
var uniqueCode = Guid
.NewGuid()
.ToString("N")
.Substring(0, 12);
var fileName =
$"{safePrefix}_{uniqueCode}{ext}";
newFullPath =
Path.Combine(
dirFisica,
fileName
);
// =====================================================
// SALVATAGGIO FILE
// =====================================================
using (
var stream = new FileStream(
newFullPath,
FileMode.CreateNew,
FileAccess.Write,
FileShare.None
)
)
{
await postedFiles.CopyToAsync(
stream
);
}
// =====================================================
// DATABASE
// =====================================================
club.ImgPalinsesto =
$"i-club/admin/ScanFile/UploadCorsi/{fileName}"
.Replace("\\", "/");
var hidden =
await GetOrCreateHiddenForClubAsync(
club
);
hidden.Palinsesto = 1;
await _db.SaveChangesAsync();
// =====================================================
// CANCELLA VECCHIO PALINSESTO
// Solo dopo che DB + nuovo file sono salvi.
// =====================================================
try
{
DeleteWebRootFileIfExists(
oldRelativePath,
newFullPath
);
}
catch (Exception cleanupEx)
{
_logger.LogWarning(
cleanupEx,
"Impossibile eliminare il vecchio palinsesto del club {ClubId}",
club.Id
);
}
// =====================================================
// NESSUN REDIRECT
// Ritorniamo JSON alla stessa pagina.
// =====================================================
return Json(new
{
success = true,
message =
"Palinsesto caricato e attivato correttamente.",
imageUrl =
"/" + club.ImgPalinsesto.TrimStart('/'),
clubId =
club.Id
});
}
catch (Exception ex)
{
// =====================================================
// PULIZIA FILE ORFANO
// =====================================================
if (
!string.IsNullOrWhiteSpace(newFullPath) &&
System.IO.File.Exists(newFullPath)
)
{
try
{
System.IO.File.Delete(
newFullPath
);
}
catch (Exception cleanupEx)
{
_logger.LogWarning(
cleanupEx,
"Impossibile eliminare un palinsesto non completato"
);
}
}
_logger.LogError(
ex,
"Errore upload palinsesto per clubId={ClubId}",
clubId
);
return Json(new
{
success = false,
message =
"Errore durante il caricamento del palinsesto: " +
(ex.InnerException?.Message ?? ex.Message)
});
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task
SetPalinsestoStatus(int clubId, bool attivo, string NomeSedeAttuale)
{
if (!IsLogged() || !await HasClubAccessAsync()) return GoLogin();
if (!IsDevOrSamuele()) return Unauthorized();
try
{
var club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == clubId);
if (club == null)
{
TempData["Errore"] = "Club non trovato.";
return RedirectToAction(nameof(ModifyClub), new { id = NomeSedeAttuale });
}
if (!CanManageClub(club)) return Forbid();
if (attivo && string.IsNullOrWhiteSpace(club.ImgPalinsesto))
{
TempData["Errore"] = "Non puoi attivare il palinsesto perché non è stato ancora caricato alcun file.";
return RedirectToAction(nameof(ModifyClub), new { id = NomeSedeAttuale });
}
var hidden = await GetOrCreateHiddenForClubAsync(club);
hidden.Palinsesto = (byte)(attivo ? 1 : 0);
await _db.SaveChangesAsync();
TempData["Successo"] = attivo
? "Palinsesto attivato (flag DB = 1)."
: "Palinsesto disattivato (flag DB = 0).";
return RedirectToAction(nameof(ModifyClub), new { id = NomeSedeAttuale });
}
catch (Exception ex)
{
TempData["Errore"] = "Errore aggiornando stato palinsesto: " + ex.Message;
return RedirectToAction(nameof(ModifyClub), new { id = NomeSedeAttuale });
}
}
// ======================================
// OPENED STATUS
// ======================================
[HttpPost]
[ValidateAntiForgeryToken]
public async Task
ModifyOpenedStatus(
int id,
int openedStatus,
string NomeSedeAttuale)
{
// =========================================
// CONTROLLO LOGIN
// =========================================
if (!IsLogged())
return GoLogin();
// Soltanto gli amministratori possono
// cambiare lo stato apertura della sede.
if (!IsSuperLike())
return Forbid();
// =========================================
// VALIDAZIONE STATO
// =========================================
if (openedStatus != 1 && openedStatus != 3)
{
TempData["Errore"] =
"Stato non valido. Utilizzare 1 per prevendita " +
"oppure 3 per sede aperta.";
return await RedirectToAllowedClubAsync(NomeSedeAttuale);
}
// =========================================
// RECUPERO SEDE
// =========================================
var club = await _db.ClubNews
.FirstOrDefaultAsync(c => c.Id == id);
if (club == null)
{
TempData["Errore"] = "Club non trovato.";
return await RedirectToAllowedClubAsync(NomeSedeAttuale);
}
// =========================================
// CONTROLLO AUTORIZZAZIONE
// =========================================
if (!CanManageClub(club))
return Forbid();
// =========================================
// AGGIORNAMENTO STATO
// =========================================
try
{
club.Opened = openedStatus;
await _db.SaveChangesAsync();
TempData["Successo"] = openedStatus == 3
? "La sede è stata impostata come aperta."
: "La sede è stata impostata in prevendita.";
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Errore modifica Opened sede ID {ClubId}",
id
);
TempData["Errore"] =
"Si è verificato un errore durante " +
"l'aggiornamento dello stato della sede.";
}
// =========================================
// RITORNO ALLA SEDE
// =========================================
return RedirectToAction(
nameof(ModifyClub),
new
{
id = NormalizeClubKey(club.Title)
}
);
}
// ======================================
// REMOVE ELEMENTO
// 1 => rimuovi palinsesto file
// 2 => rimuovi linkYT
// ======================================
[HttpPost]
[ValidateAntiForgeryToken]
public async Task
RemoveElemento(int id, int Elemento, string NomeSedeAttuale)
{
if (!IsLogged() || !await HasClubAccessAsync())
return Unauthorized();
try
{
var club = await _db.ClubNews.FirstOrDefaultAsync(c => c.Id == id);
if (club == null)
return NotFound("Club non trovato.");
if (!CanManageClub(club))
return Forbid();
if (Elemento == 2)
{
club.LinkYt = "";
var hiddenYt = await FindHiddenByClubTitleAsync(club.Title);
if (hiddenYt != null)
hiddenYt.VideoYt = 0;
await _db.SaveChangesAsync();
return Content("Eseguito");
}
if (Elemento == 1)
{
var oldRelativePath = club.ImgPalinsesto;
club.ImgPalinsesto = "";
var hidden = await GetOrCreateHiddenForClubAsync(club);
hidden.Palinsesto = 0;
await _db.SaveChangesAsync();
try
{
DeleteWebRootFileIfExists(oldRelativePath);
}
catch (Exception cleanupEx)
{
_logger.LogWarning(cleanupEx, "Impossibile eliminare il file palinsesto del club {ClubId}", club.Id);
}
return Content("Eseguito");
}
return Content("Niente");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore RemoveElemento id={Id} elemento={Elemento}", id, Elemento);
return StatusCode(500, ex.InnerException?.Message ?? ex.Message);
}
}
[HttpGet]
public async Task
StatoWebhookSede(int id)
{
if (!IsLogged()) return Unauthorized();
if (!IsSuperLike()) return Forbid();
var events = await _db.Set
().AsNoTracking().Where(x => x.ClubId == id)
.OrderByDescending(x => x.Id).Take(20).Select(x => new
{
x.Id, x.EventId, x.ClubId, x.EventType, x.Reason, x.CreatedUtc, x.Attempts,
x.LastStatusCode, x.LastError, x.DeliveredUtc, x.NextAttemptUtc, x.DeliverySequence
}).ToListAsync();
return Json(events);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task
ReinviaSedeApp(int id)
{
if (!IsLogged()) return Unauthorized();
if (!IsSuperLike()) return Forbid();
var club = await _db.ClubNews.FirstOrDefaultAsync(x => x.Id == id);
if (club == null) return NotFound("Sede non trovata.");
var message = SedeWebhookQueue.Add(_db, club, "manual");
await _db.SaveChangesAsync();
return Json(new { success = true, stato = "In coda", clubId = id, message.EventId });
}
}
}
Regjistrohu në newsletter-in tonë!
Faleminderit që u regjistrove në Newsletter-in tonë!
Kjo faqe interneti përdor cookies për të përmirësuar përvojën e navigimit të përdoruesve dhe për të mbledhur informacione mbi mënyrën e përdorimit të faqes.
Mund të gjesh më shumë hollësi në politikën tonë të privatësisë
këtu.
Duke vazhduar navigimin, pranon përdorimin e cookies; përndryshe mund të largohesh nga faqja.