Woohoo working version gooo
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Elias Jansson
2025-06-04 18:01:05 +02:00
parent 84c6c45a0b
commit c44fbfdca9
7 changed files with 214 additions and 114 deletions

View File

@@ -102,18 +102,70 @@ namespace Aberwyn.Controllers
} }
[HttpPost] [HttpPost]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
public IActionResult ImportMealsFromProd() public IActionResult ImportMenusFromCustom(string dbHost, int dbPort, string dbName, string dbUser, string dbPassword)
{ {
var prodService = MenuService.CreateWithSetup(_env); var connStr = $"Server={dbHost};Port={dbPort};Database={dbName};Uid={dbUser};Pwd={dbPassword};";
var devService = new MenuService(_context); // injicerad context används var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
var prodMeals = prodService.GetMealsDetailed(); using var customContext = new ApplicationDbContext(optionsBuilder.Options);
var sourceService = new MenuService(customContext);
var devService = new MenuService(_context);
foreach (var meal in prodMeals) var sourceMenus = sourceService.GetAllWeeklyMenus();
var devMeals = devService.GetMeals();
foreach (var menu in sourceMenus)
{
var newMenu = new WeeklyMenu
{
DayOfWeek = menu.DayOfWeek,
WeekNumber = menu.WeekNumber,
Year = menu.Year,
Cook = menu.Cook,
BreakfastMealId = null,
LunchMealId = null,
DinnerMealId = null
};
if (!string.IsNullOrEmpty(menu.BreakfastMealName))
newMenu.BreakfastMealId = devMeals.FirstOrDefault(m => m.Name == menu.BreakfastMealName)?.Id;
if (!string.IsNullOrEmpty(menu.LunchMealName))
newMenu.LunchMealId = devMeals.FirstOrDefault(m => m.Name == menu.LunchMealName)?.Id;
if (!string.IsNullOrEmpty(menu.DinnerMealName))
newMenu.DinnerMealId = devMeals.FirstOrDefault(m => m.Name == menu.DinnerMealName)?.Id;
_context.WeeklyMenus.Add(newMenu);
}
_context.SaveChanges();
TempData["Message"] = $"✅ Import av veckomenyer från extern databas klar ({sourceMenus.Count}).";
return RedirectToAction("Index");
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult ImportMealsFromCustom(string dbHost, int dbPort, string dbName, string dbUser, string dbPassword)
{
var connStr = $"Server={dbHost};Port={dbPort};Database={dbName};Uid={dbUser};Pwd={dbPassword};";
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr), mySqlOptions => mySqlOptions.CommandTimeout(180));
using var customContext = new ApplicationDbContext(optionsBuilder.Options);
var customService = new MenuService(customContext);
var devService = new MenuService(_context);
try
{
var importedMeals = customService.GetMealsDetailed(); // Ska inkludera Ingredients
foreach (var meal in importedMeals)
{ {
// Kopiera utan ID (för att undvika konflikt) och spara
var newMeal = new Meal var newMeal = new Meal
{ {
Id = meal.Id, // 👈 Viktigt!
Name = meal.Name, Name = meal.Name,
Description = meal.Description, Description = meal.Description,
ProteinType = meal.ProteinType, ProteinType = meal.ProteinType,
@@ -128,6 +180,7 @@ namespace Aberwyn.Controllers
ImageMimeType = meal.ImageMimeType, ImageMimeType = meal.ImageMimeType,
Ingredients = meal.Ingredients.Select(i => new Ingredient Ingredients = meal.Ingredients.Select(i => new Ingredient
{ {
MealId = meal.Id, // 👈 Koppla till rätt måltid
Quantity = i.Quantity, Quantity = i.Quantity,
Item = i.Item Item = i.Item
}).ToList() }).ToList()
@@ -136,61 +189,32 @@ namespace Aberwyn.Controllers
devService.SaveOrUpdateMealWithIngredients(newMeal); devService.SaveOrUpdateMealWithIngredients(newMeal);
} }
return Content("Import klar!"); TempData["Message"] = $"✅ {importedMeals.Count} måltider importerade från extern databas.";
}
catch (Exception ex)
{
TempData["Message"] = $"❌ Fel vid import: {ex.Message}";
} }
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult ImportMenusFromProd()
{
var prodService = MenuService.CreateWithConfig(_configuration, _env, useProdDb: true);
var devService = new MenuService(_context);
var allProdMenus = prodService.GetAllWeeklyMenus();
var allMeals = devService.GetMeals();
foreach (var menu in allProdMenus)
{
var newMenu = new WeeklyMenu
{
DayOfWeek = menu.DayOfWeek,
WeekNumber = menu.WeekNumber,
Year = menu.Year,
Cook = menu.Cook,
BreakfastMealId = null,
LunchMealId = null,
DinnerMealId = null
};
if (!string.IsNullOrEmpty(menu.BreakfastMealName))
newMenu.BreakfastMealId = allMeals.FirstOrDefault(m => m.Name == menu.BreakfastMealName)?.Id;
if (!string.IsNullOrEmpty(menu.LunchMealName))
newMenu.LunchMealId = allMeals.FirstOrDefault(m => m.Name == menu.LunchMealName)?.Id;
if (!string.IsNullOrEmpty(menu.DinnerMealName))
newMenu.DinnerMealId = allMeals.FirstOrDefault(m => m.Name == menu.DinnerMealName)?.Id;
_context.WeeklyMenus.Add(newMenu);
}
_context.SaveChanges();
TempData["Message"] = "Import av veckomenyer klar.";
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
[HttpPost] [HttpPost]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
public IActionResult ImportBudgetFromProd() public IActionResult ImportBudgetFromCustom(string dbHost, int dbPort, string dbName, string dbUser, string dbPassword)
{ {
// Hämta connection till produktion var connStr = $"Server={dbHost};Port={dbPort};Database={dbName};Uid={dbUser};Pwd={dbPassword};";
using var prodContext = ApplicationDbContextFactory.CreateWithConfig(_env, true); var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
// Importera definitioner först using var sourceContext = new ApplicationDbContext(optionsBuilder.Options);
var prodCategoryDefs = prodContext.BudgetCategoryDefinitions.ToList();
var prodItemDefs = prodContext.BudgetItemDefinitions.ToList();
foreach (var def in prodCategoryDefs) var categoryDefs = sourceContext.BudgetCategoryDefinitions.ToList();
var itemDefs = sourceContext.BudgetItemDefinitions.ToList();
foreach (var def in categoryDefs)
{ {
if (!_context.BudgetCategoryDefinitions.Any(d => d.Name == def.Name)) if (!_context.BudgetCategoryDefinitions.Any(d => d.Name == def.Name))
{ {
@@ -199,11 +223,10 @@ namespace Aberwyn.Controllers
Name = def.Name, Name = def.Name,
Color = def.Color ?? "#cccccc" Color = def.Color ?? "#cccccc"
}); });
} }
} }
foreach (var def in prodItemDefs) foreach (var def in itemDefs)
{ {
if (!_context.BudgetItemDefinitions.Any(d => d.Name == def.Name)) if (!_context.BudgetItemDefinitions.Any(d => d.Name == def.Name))
{ {
@@ -211,19 +234,17 @@ namespace Aberwyn.Controllers
} }
} }
_context.SaveChanges(); // Se till att ID:n finns för FK:n nedan _context.SaveChanges();
// Ladda definitioner i minnet för snabb lookup
var devCategoryDefs = _context.BudgetCategoryDefinitions.ToList(); var devCategoryDefs = _context.BudgetCategoryDefinitions.ToList();
var devItemDefs = _context.BudgetItemDefinitions.ToList(); var devItemDefs = _context.BudgetItemDefinitions.ToList();
// Importera budgetperioder med kategorier och items var periods = sourceContext.BudgetPeriods
var prodPeriods = prodContext.BudgetPeriods
.Include(p => p.Categories) .Include(p => p.Categories)
.ThenInclude(c => c.Items) .ThenInclude(c => c.Items)
.ToList(); .ToList();
foreach (var period in prodPeriods) foreach (var period in periods)
{ {
var exists = _context.BudgetPeriods var exists = _context.BudgetPeriods
.Any(p => p.Year == period.Year && p.Month == period.Month); .Any(p => p.Year == period.Year && p.Month == period.Month);
@@ -259,11 +280,13 @@ namespace Aberwyn.Controllers
} }
_context.SaveChanges(); _context.SaveChanges();
TempData["Message"] = "✅ Import av budgetdata från produktion är klar."; TempData["Message"] = $"✅ Import av budgetdata från extern databas klar ({periods.Count} månader).";
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
//Todo //Todo
[HttpGet] [HttpGet]
@@ -314,8 +337,30 @@ namespace Aberwyn.Controllers
return Ok(); return Ok();
} }
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult TestDbConnection(string dbHost, int dbPort, string dbName, string dbUser, string dbPassword)
{
var connStr = $"Server={dbHost};Port={dbPort};Database={dbName};Uid={dbUser};Pwd={dbPassword};";
try
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
using var context = new ApplicationDbContext(builder.Options);
context.Database.OpenConnection();
context.Database.CloseConnection();
return Json(new { success = true, message = "✅ Anslutning lyckades!" });
} }
catch (Exception ex)
{
return Json(new { success = false, message = $"❌ Anslutning misslyckades: {ex.Message}" });
}
}
}
public class AdminUserViewModel public class AdminUserViewModel
{ {

View File

@@ -10,12 +10,13 @@ namespace Aberwyn.Controllers
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly IHostEnvironment _env; private readonly IHostEnvironment _env;
private readonly MenuService _menuService; private readonly MenuService _menuService;
public MealController(MenuService menuService, IConfiguration configuration, IHostEnvironment env)
public MealController(IConfiguration configuration, IHostEnvironment env)
{ {
_menuService = menuService;
_configuration = configuration; _configuration = configuration;
_env = env; _env = env;
} }
[HttpGet] [HttpGet]
public IActionResult View(int id, bool edit = false) public IActionResult View(int id, bool edit = false)
{ {

View File

@@ -16,6 +16,9 @@ namespace Aberwyn.Data
base.OnModelCreating(builder); base.OnModelCreating(builder);
builder.Entity<WeeklyMenu>().ToTable("WeeklyMenu"); builder.Entity<WeeklyMenu>().ToTable("WeeklyMenu");
builder.Entity<Meal>()
.Property(m => m.Id)
.ValueGeneratedNever();
} }

View File

@@ -16,37 +16,6 @@ public class MenuService
_context = context; _context = context;
} }
// Detta är en alternativ konstruktör används manuellt vid t.ex. import
public static MenuService CreateWithConfig(IConfiguration config, IHostEnvironment env, bool useProdDb = false)
{
var basePath = env.ContentRootPath ?? Directory.GetCurrentDirectory();
var setupPath = Path.Combine(basePath, "infrastructure", "setup.json");
if (!File.Exists(setupPath))
throw new FileNotFoundException("setup.json saknas i infrastructure/");
var setupJson = File.ReadAllText(setupPath);
var setup = System.Text.Json.JsonSerializer.Deserialize<SetupSettings>(setupJson)!;
if (!setup.IsConfigured || string.IsNullOrWhiteSpace(setup.DbPassword))
throw new InvalidOperationException("setup.json är ofullständig.");
var csBuilder = new MySqlConnector.MySqlConnectionStringBuilder
{
Server = setup.DbHost,
Port = (uint)setup.DbPort,
Database = setup.DbName,
UserID = setup.DbUser,
Password = setup.DbPassword,
AllowUserVariables = true
};
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseMySql(csBuilder.ConnectionString, ServerVersion.AutoDetect(csBuilder.ConnectionString));
var context = new ApplicationDbContext(builder.Options);
return new MenuService(context);
}
public static MenuService CreateWithSetup(IHostEnvironment env) public static MenuService CreateWithSetup(IHostEnvironment env)
{ {
var setup = SetupLoader.Load(env); var setup = SetupLoader.Load(env);
@@ -83,14 +52,25 @@ public class MenuService
meal.Name = meal.Name.Trim(); meal.Name = meal.Name.Trim();
meal.CreatedAt = meal.CreatedAt == default ? DateTime.Now : meal.CreatedAt; meal.CreatedAt = meal.CreatedAt == default ? DateTime.Now : meal.CreatedAt;
if (meal.Id == 0) var existing = _context.Meals
_context.Meals.Add(meal); .AsNoTracking()
.FirstOrDefault(m => m.Id == meal.Id);
if (existing == null)
{
// Nytt objekt försök behålla ID:t från prod
_context.Entry(meal).State = EntityState.Added;
}
else else
{
// Befintlig uppdatera
_context.Meals.Update(meal); _context.Meals.Update(meal);
}
_context.SaveChanges(); _context.SaveChanges();
} }
public List<WeeklyMenu> GetAllWeeklyMenus() public List<WeeklyMenu> GetAllWeeklyMenus()
{ {
var menus = _context.WeeklyMenus.ToList(); var menus = _context.WeeklyMenus.ToList();
@@ -139,10 +119,12 @@ public List<WeeklyMenu> GetAllWeeklyMenus()
public List<Meal> GetMealsDetailed() public List<Meal> GetMealsDetailed()
{ {
return _context.Meals return _context.Meals
.Include(m => m.Ingredients) // 🧠 detta behövs!
.OrderByDescending(m => m.CreatedAt) .OrderByDescending(m => m.CreatedAt)
.ToList(); .ToList();
} }
public Meal GetMealById(int id) public Meal GetMealById(int id)
{ {
var meal = _context.Meals var meal = _context.Meals

View File

@@ -2,10 +2,10 @@
"AdminUsername": "admin", "AdminUsername": "admin",
"AdminEmail": "admin@localhost", "AdminEmail": "admin@localhost",
"AdminPassword": "Admin123!", "AdminPassword": "Admin123!",
"IsConfigured": false, "IsConfigured": true,
"DbHost": null, "DbHost": "192.168.1.108",
"DbPort": 3306, "DbPort": 3306,
"DbName": null, "DbName": "lewel_prod",
"DbUser": null, "DbUser": "lewel",
"DbPassword": null "DbPassword": "W542.Hl;)%ta"
} }

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Aberwyn.Data; using Aberwyn.Data;
using Aberwyn.Models; using Aberwyn.Models;

View File

@@ -84,20 +84,88 @@
<button type="submit" class="btn btn-warning mt-2">Skicka testnotis</button> <button type="submit" class="btn btn-warning mt-2">Skicka testnotis</button>
</form> </form>
<hr /> <div class="card mb-4">
<h3>Importera måltider från produktion</h3> <div class="card-header bg-secondary text-white">
<form method="post" asp-action="ImportMealsFromProd"> Importera från annan databas
<button type="submit" class="btn btn-danger">Importera alla måltider</button> </div>
</form> <div class="card-body">
<form method="post" asp-action="ImportMenusFromProd"> <form id="customDbForm">
<button type="submit" class="btn btn-danger mt-2">Importera veckomenyer</button> <div class="form-group">
</form> <label>Server:</label>
<form method="post" asp-action="ImportBudgetFromProd"> <input type="text" name="dbHost" class="form-control" required />
<button type="submit" class="btn btn-danger mt-2">Importera budgetdata</button> </div>
<div class="form-group">
<label>Port:</label>
<input type="number" name="dbPort" class="form-control" value="3306" required />
</div>
<div class="form-group">
<label>Databas:</label>
<input type="text" name="dbName" class="form-control" required />
</div>
<div class="form-group">
<label>Användarnamn:</label>
<input type="text" name="dbUser" class="form-control" required />
</div>
<div class="form-group">
<label>Lösenord:</label>
<input type="password" name="dbPassword" class="form-control" required />
</div>
</form> </form>
<button class="btn btn-info mt-3" onclick="testDbConnection()">🔌 Testa anslutning</button>
<div id="testResult" class="mt-2"></div>
<button class="btn btn-primary mt-3" onclick="submitTo('ImportMealsFromCustom')">🍽 Importera måltider</button>
<button class="btn btn-primary mt-3" onclick="submitTo('ImportMenusFromCustom')">🗓 Importera menyer</button>
<button class="btn btn-primary mt-3" onclick="submitTo('ImportBudgetFromCustom')">💰 Importera budget</button>
</div>
</div>
<script> <script>
function collectFormData() {
const form = document.getElementById("customDbForm");
return new FormData(form); // 🔁 Byt ut till ren FormData istället för URLSearchParams
}
function submitTo(action) {
const data = collectFormData();
fetch(`/admin/${action}`, {
method: "POST",
body: data
})
.then(async res => {
const text = await res.text();
if (res.ok) {
alert(`✅ ${action} lyckades`);
} else {
alert(`❌ Fel vid ${action}:\n${text}`);
}
});
}
function testDbConnection() {
const data = collectFormData();
fetch(`/admin/TestDbConnection`, {
method: "POST",
body: data
})
.then(res => res.json())
.then(data => {
const div = document.getElementById("testResult");
div.innerText = data.message;
div.className = data.success ? "text-success mt-2" : "text-danger mt-2";
})
.catch(err => {
document.getElementById("testResult").innerText = "❌ Ett tekniskt fel uppstod.";
console.error(err);
});
}
async function sendPush(event) { async function sendPush(event) {
event.preventDefault(); // 🚫 stoppa formuläret från att göra vanlig POST event.preventDefault(); // 🚫 stoppa formuläret från att göra vanlig POST