using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authorization; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Oqtane.Shared; using Oqtane.Enums; using Oqtane.Infrastructure; using SZUAbsolventenverein.Module.PremiumArea.Services; using Oqtane.Controllers; using System.Net; using System.Threading.Tasks; using SZUAbsolventenverein.Module.PremiumArea.Models; using System.IO; using System; using Oqtane.Models; using Microsoft.AspNetCore.Hosting; using Oqtane.Repository; using Oqtane.Security; using System.Linq; using Oqtane.Managers; using SZUAbsolventenverein.Module.PremiumArea.Repository; namespace SZUAbsolventenverein.Module.PremiumArea.Controllers { [Route(ControllerRoutes.ApiRoute)] public class EngineerApplicationController : ModuleControllerBase { private readonly IEngineerApplicationService _service; private readonly IFileRepository _files; private readonly IFolderRepository _folders; private readonly IUserManager _users; private readonly IUserPremiumRepository _premiums; private readonly IHttpContextAccessor _accessor; private readonly IWebHostEnvironment _environment; public EngineerApplicationController(IEngineerApplicationService service, IFileRepository files, IFolderRepository folders, IUserManager users, IUserPremiumRepository premiums, ILogManager logger, IHttpContextAccessor accessor, IWebHostEnvironment environment) : base(logger, accessor) { _service = service; _files = files; _folders = folders; _users = users; _premiums = premiums; _accessor = accessor; _environment = environment; } // GET: api/?moduleid=x [HttpGet] [Authorize(Policy = PolicyNames.ViewModule)] public async Task> Get(string moduleid) { int ModuleId; if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) { return await _service.GetApplicationsAsync(ModuleId); } else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Get Attempt {ModuleId}", moduleid); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; return null; } } // GET: api//status/Approved?moduleid=x [HttpGet("status/{status}")] [Authorize(Policy = PolicyNames.ViewModule)] public async Task> GetByStatus(string status, string moduleid) { int ModuleId; if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) { return await _service.GetApplicationsAsync(ModuleId, status); } else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication GetByStatus Attempt {ModuleId}", moduleid); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; return null; } } // GET api//5 [HttpGet("{id}")] [Authorize(Policy = PolicyNames.ViewModule)] public async Task Get(int id, string moduleid) { int ModuleId; if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) { return await _service.GetApplicationAsync(id, ModuleId); } else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Get Attempt {Id} {ModuleId}", id, moduleid); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; return null; } } // POST api/ [HttpPost] [Authorize(Policy = PolicyNames.ViewModule)] // Users can Create public async Task Post([FromBody] EngineerApplication Application) { if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId)) { try { _logger.Log(LogLevel.Information, this, LogFunction.Create, "DEBUG: Attempting to save application. UserId: {UserId}, ModuleId: {ModuleId}, FileId: {FileId}", Application.UserId, Application.ModuleId, Application.FileId); // Manual validation before EF Core sees it if (Application.UserId == 0) _logger.Log(LogLevel.Warning, this, LogFunction.Create, "DEBUG: UserId is 0!"); if (Application.FileId == null || Application.FileId == 0) _logger.Log(LogLevel.Warning, this, LogFunction.Create, "DEBUG: FileId is null or 0!"); var result = await _service.AddApplicationAsync(Application); _logger.Log(LogLevel.Information, this, LogFunction.Create, "DEBUG: Save successful!"); return result; } catch (Exception ex) { var innerMessage = ex.InnerException?.Message ?? "No inner exception"; _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "CRITICAL DB ERROR: {Message}. Inner: {Inner}", ex.Message, innerMessage); // Force output to console so the user sees it immediately Console.WriteLine("========================================"); Console.WriteLine($"!!! DATABASE INSERT FAILED !!!"); Console.WriteLine($"Error: {ex.Message}"); Console.WriteLine($"Inner: {innerMessage}"); Console.WriteLine($"Stack: {ex.StackTrace}"); Console.WriteLine("========================================"); HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; return null; } } else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Post Attempt {Application}", Application); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; return null; } } // PUT api//5 [HttpPut("{id}")] [Authorize(Policy = PolicyNames.ViewModule)] // Users can Edit own (Service checks ownership) public async Task Put(int id, [FromBody] EngineerApplication Application) { if (ModelState.IsValid && Application.ApplicationId == id && IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId)) { try { return await _service.UpdateApplicationAsync(Application); } catch (Exception ex) { _logger.Log(LogLevel.Error, this, LogFunction.Update, ex, "Error updating application: {Message}. Inner: {Inner}", ex.Message, ex.InnerException?.Message); HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; return null; } } else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Put Attempt {Application}", Application); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; return null; } } // DELETE api//5 [HttpDelete("{id}")] [Authorize(Policy = PolicyNames.ViewModule)] public async Task Delete(int id, string moduleid) { int ModuleId; if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) { await _service.DeleteApplicationAsync(id, ModuleId); } else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Delete Attempt {Id} {ModuleId}", id, moduleid); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } [HttpPost("approve/{id}")] [Authorize(Policy = PolicyNames.EditModule)] public async Task Approve(int id, string moduleid) { int ModuleId; if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) { await _service.ApproveApplicationAsync(id, ModuleId); } else { HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } [HttpGet("log")] [AllowAnonymous] public IActionResult GetLog() { try { var repo = (EngineerApplicationRepository)_service.GetType().GetField("_repository", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) .GetValue(_service); var factory = (IDbContextFactory)repo.GetType().GetField("_factory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(repo); using var db = factory.CreateDbContext(); var connection = db.Database.GetDbConnection(); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = "PRAGMA table_info(SZUAbsolventenvereinEngineerApplications);"; var columns = new List(); using var reader = command.ExecuteReader(); while (reader.Read()) { columns.Add($"{reader["name"]} ({reader["type"]})"); } if (columns.Count == 0) { // Maybe it's rewritten? command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%Engineer%';"; using var reader2 = command.ExecuteReader(); while (reader2.Read()) { columns.Add($"Found table: {reader2["name"]}"); } } return Ok(columns); } catch (Exception ex) { _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Error getting columns: {Message}. Inner: {Inner}", ex.Message, ex.InnerException?.Message); return Ok($"Error: {ex.Message}"); } } [HttpPost("upload")] [Authorize(Policy = PolicyNames.ViewModule)] public async Task Upload(string moduleid) { int ModuleId; if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) { try { if (Request.Form.Files.Count == 0) return BadRequest("No file uploaded"); var file = Request.Form.Files[0]; if (file.ContentType != "application/pdf") return BadRequest("Only PDF files are allowed"); var alias = _accessor.HttpContext.Items["Alias"] as Alias; // Retained original alias retrieval var siteId = alias.SiteId; var folderPath = "EngineerApplications"; var folder = _folders.GetFolder(siteId, folderPath); if (folder == null) { try { // Create folder folder = new Folder { SiteId = siteId, ParentId = null, Name = "EngineerApplications", Path = folderPath, PermissionList = new List { new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.Edit, RoleNames.Admin, true) } }; folder = _folders.AddFolder(folder); } catch (Exception ex) { _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Error creating folder: {Message}", ex.Message); return BadRequest($"Folder creation failed: {ex.Message}"); } } var ext = Path.GetExtension(file.FileName).ToLower(); if (ext != ".pdf") return BadRequest("Invalid file extension"); var tenantId = alias.TenantId; var uploadPath = Path.Combine(_environment.ContentRootPath, "Content", "Tenants", tenantId.ToString(), "Sites", siteId.ToString(), folderPath); if (!Directory.Exists(uploadPath)) Directory.CreateDirectory(uploadPath); var uniqueName = $"{Guid.NewGuid()}{ext}"; var filePath = Path.Combine(uploadPath, uniqueName); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } var fileObj = new Oqtane.Models.File { FolderId = folder.FolderId, Name = uniqueName, Extension = ext.Substring(1), Size = (int)file.Length, ImageHeight = 0, ImageWidth = 0 }; try { var addedFile = _files.AddFile(fileObj); return Ok(new { FileId = addedFile.FileId, FileName = file.FileName }); } catch (Exception ex) { _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Error saving file record to DB: {Message}. Inner: {Inner}", ex.Message, ex.InnerException?.Message); // Critical: This is where we suspect the DbUpdateException Console.WriteLine($"UPLOAD DB ERROR: {ex.Message} | {ex.InnerException?.Message}"); return BadRequest($"Database error during file registration: {ex.Message}"); } } catch (Exception ex) { _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "General Upload Error: {Message}", ex.Message); return BadRequest(ex.Message); } } return StatusCode((int)HttpStatusCode.Forbidden); } [HttpPost("report/{id}")] [Authorize(Policy = PolicyNames.ViewModule)] public async Task Report(int id, [FromBody] string reason, string moduleid) { int ModuleId; if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) { await _service.ReportApplicationAsync(id, ModuleId, reason); } else { HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } private async Task ServeFile(int fileId, string downloadName) { var file = _files.GetFile(fileId); if (file != null) { var path = _files.GetFilePath(file); if (System.IO.File.Exists(path)) { var bytes = await System.IO.File.ReadAllBytesAsync(path); return File(bytes, "application/pdf", downloadName ?? file.Name); } } return NotFound(); } } }