DB Migrtation geändert und PDF upload funktioniert

This commit is contained in:
2026-02-19 11:48:44 +01:00
parent 1e88a86be1
commit b51b37a6e8
13 changed files with 741 additions and 524 deletions

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
@@ -33,7 +34,9 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
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)
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;
@@ -56,7 +59,8 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Get Attempt {ModuleId}", moduleid);
_logger.Log(LogLevel.Error, this, LogFunction.Security,
"Unauthorized EngineerApplication Get Attempt {ModuleId}", moduleid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
@@ -72,9 +76,10 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
{
return await _service.GetApplicationsAsync(ModuleId, status);
}
else
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication GetByStatus Attempt {ModuleId}", moduleid);
_logger.Log(LogLevel.Error, this, LogFunction.Security,
"Unauthorized EngineerApplication GetByStatus Attempt {ModuleId}", moduleid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
@@ -85,17 +90,18 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
[Authorize(Policy = PolicyNames.ViewModule)]
public async Task<EngineerApplication> 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;
}
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/<controller>
@@ -105,11 +111,44 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
{
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId))
{
return await _service.AddApplicationAsync(Application);
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);
_logger.Log(LogLevel.Error, this, LogFunction.Security,
"Unauthorized EngineerApplication Post Attempt {Application}", Application);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
@@ -120,13 +159,26 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
[Authorize(Policy = PolicyNames.ViewModule)] // Users can Edit own (Service checks ownership)
public async Task<EngineerApplication> Put(int id, [FromBody] EngineerApplication Application)
{
if (ModelState.IsValid && Application.ApplicationId == id && IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId))
if (ModelState.IsValid && Application.ApplicationId == id &&
IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId))
{
return await _service.UpdateApplicationAsync(Application);
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);
_logger.Log(LogLevel.Error, this, LogFunction.Security,
"Unauthorized EngineerApplication Put Attempt {Application}", Application);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
@@ -144,12 +196,12 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Delete Attempt {Id} {ModuleId}", id, moduleid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
_logger.Log(LogLevel.Error, this, LogFunction.Security,
"Unauthorized EngineerApplication Delete Attempt {Id} {ModuleId}", id, moduleid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
// POST api/<controller>/approve/5
[HttpPost("approve/{id}")]
[Authorize(Policy = PolicyNames.EditModule)]
public async Task Approve(int id, string moduleid)
@@ -157,16 +209,57 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
int ModuleId;
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
{
// We need to clear IsReported flag as well.
// Since the Service handles the logic, we should probably update it there.
// But if I can't find it easily, I can do it here if I get the app first.
// _service.ApproveApplicationAsync might just set Status="Approved".
// I should verify where the service logic is.
await _service.ApproveApplicationAsync(id, ModuleId);
}
else
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
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<PremiumAreaContext>)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<string>();
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}");
}
}
@@ -177,63 +270,95 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
int ModuleId;
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
{
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;
var siteId = alias.SiteId;
var folderPath = "EngineerApplications";
var folder = _folders.GetFolder(siteId, folderPath);
if (folder == null)
try
{
// Create folder
folder = new Folder
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)
{
SiteId = siteId,
ParentId = null,
Name = "EngineerApplications",
Path = folderPath,
PermissionList = new List<Permission>
try
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
// Create folder
folder = new Folder
{
SiteId = siteId,
ParentId = null,
Name = "EngineerApplications",
Path = folderPath,
PermissionList = new List<Permission>
{
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
};
folder = _folders.AddFolder(folder);
}
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);
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}");
}
}
var fileObj = new Oqtane.Models.File
catch (Exception ex)
{
FolderId = folder.FolderId,
Name = uniqueName,
Extension = ext.Substring(1),
Size = (int)file.Length,
ImageHeight = 0,
ImageWidth = 0
};
var addedFile = _files.AddFile(fileObj);
return Ok(new { FileId = addedFile.FileId, FileName = file.FileName });
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "General Upload Error: {Message}",
ex.Message);
return BadRequest(ex.Message);
}
}
return StatusCode((int)HttpStatusCode.Forbidden);
}
@@ -246,71 +371,25 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
{
await _service.ReportApplicationAsync(id, ModuleId, reason);
}
else
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
[HttpGet("download/{id}")]
[Authorize]
public async Task<IActionResult> Download(int id, string moduleid)
{
int ModuleId;
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
{
var app = await _service.GetApplicationAsync(id, ModuleId);
if (app == null) return NotFound();
var alias = _accessor.HttpContext.Items["Alias"] as Alias;
// Access Rules:
// 1. Admin
if (_accessor.HttpContext.User.IsInRole(RoleNames.Admin))
{
return await ServeFile(app.FileId.Value, app.PdfFileName);
}
// 2. Owner
var username = _accessor.HttpContext.User.Identity.Name;
var currentUserId = -1;
if (username != null)
{
var u = _users.GetUser(username, alias.SiteId);
if (u != null) currentUserId = u.UserId;
}
if (currentUserId == app.UserId)
{
return await ServeFile(app.FileId.Value, app.PdfFileName);
}
// 3. Premium User AND Published/Approved
if (app.Status == "Approved" || app.Status == "Published")
{
var premium = _premiums.GetUserPremium(currentUserId);
if (premium != null && premium.PremiumUntil.HasValue && premium.PremiumUntil.Value > DateTime.UtcNow)
{
return await ServeFile(app.FileId.Value, app.PdfFileName);
}
}
return StatusCode((int)HttpStatusCode.Forbidden);
}
return StatusCode((int)HttpStatusCode.Forbidden);
}
private async Task<IActionResult> 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);
}
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();
}
}