Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5ae7fedef | ||
|
|
8d7cf86d4d | ||
|
|
81417b2a1c | ||
|
|
c0463a8f5b | ||
|
|
c4f1a1ca81 | ||
|
|
c523552d2f | ||
|
|
1e16dc5f18 | ||
|
|
313c4d6c60 | ||
|
|
0cbe46c93a | ||
|
|
55a118f8c9 | ||
|
|
056cac794b | ||
|
|
df2aaa38a3 | ||
|
|
d06dfed0e2 | ||
|
|
2161ebf1e8 | ||
|
|
42546af4f3 | ||
|
|
bd39a45dd2 | ||
|
|
cd2c2ac50d | ||
|
|
2bbd67e37d | ||
|
|
a2e14c73df | ||
|
|
c2161bdb91 | ||
|
|
49d32b0854 | ||
|
|
80b0e825b2 | ||
|
|
53b4a5e97d | ||
|
|
335112e044 | ||
|
|
9299e29ea6 | ||
|
|
5a17df917d | ||
|
|
8aed8d16b6 | ||
|
|
64aa9cf716 | ||
|
|
f63ccc2a38 | ||
|
|
a6fd3d720f | ||
|
|
77f6d2a475 | ||
|
|
85f559a607 | ||
|
|
0c2f131fff | ||
|
|
e96696f6be | ||
|
|
704e206476 | ||
|
|
6b19f08d6b | ||
|
|
146c557c25 | ||
|
|
fb24ffbf03 | ||
|
|
274f98baa4 | ||
|
|
80ffad6d86 | ||
|
|
5b0a8386ad | ||
|
|
4b8c54d38d | ||
|
|
cc96802637 | ||
|
|
95811ce3f8 | ||
|
|
4a7a2c30c9 | ||
|
|
8ebbb803e8 | ||
|
|
380978959b | ||
|
|
072369fa17 | ||
|
|
45994a9439 | ||
|
|
3ef872ac8c | ||
|
|
051ef625ba | ||
|
|
07f6451c5a | ||
|
|
979b05f2ca | ||
|
|
aefc653e22 | ||
|
|
4f14918b02 | ||
|
|
f8a33123d0 | ||
|
|
95bb989e07 | ||
|
|
004ac0c696 | ||
|
|
f0642e4587 | ||
|
|
2504fab3e7 | ||
|
|
a4229594bf | ||
|
|
55a254457b | ||
|
|
67670cc380 | ||
|
|
e26a809122 | ||
|
|
b1f20de393 | ||
|
|
c84699fd16 | ||
|
|
46299cb7f2 | ||
|
|
9f40b3f8a0 | ||
|
|
6cb2ebf3c8 | ||
|
|
871fe3a070 | ||
|
|
22ae26d488 | ||
|
|
b785051a89 | ||
|
|
0e58ffb735 | ||
|
|
76656bb6a8 | ||
|
|
113cce73ad | ||
|
|
fb62f076a0 | ||
|
|
6a43435950 | ||
|
|
f71be26ae4 | ||
|
|
d1e4901eee | ||
|
|
fc78ec0813 |
51
.drone.yml
51
.drone.yml
@@ -2,42 +2,27 @@ kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
volumes:
|
||||
- name: dockersock
|
||||
host:
|
||||
path: /var/run/docker.sock
|
||||
|
||||
steps:
|
||||
- name: build-dotnet
|
||||
image: alpine
|
||||
commands:
|
||||
- echo "Docker build will handle dotnet publish"
|
||||
|
||||
- name: build-docker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: 192.168.1.9:3000
|
||||
repo: 192.168.1.9:3000/tai/aberwyn/aberwyn
|
||||
username:
|
||||
- name: docker-build
|
||||
image: docker:24
|
||||
volumes:
|
||||
- name: dockersock
|
||||
path: /var/run/docker.sock
|
||||
environment:
|
||||
GITEA_USERNAME:
|
||||
from_secret: gitea_username
|
||||
password:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
dockerfile: Aberwyn/Dockerfile
|
||||
context: .
|
||||
tags:
|
||||
- latest
|
||||
insecure: true
|
||||
|
||||
- name: restart-unraid-container
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host: 192.168.1.108
|
||||
port: 22
|
||||
username:
|
||||
from_secret: unraid_ssh_user
|
||||
key:
|
||||
from_secret: unraid_ssh_private_key
|
||||
script:
|
||||
- docker pull 192.168.1.9:3000/tai/aberwyn/aberwyn:latest
|
||||
- docker stop aberwyn || true
|
||||
- docker rm aberwyn || true
|
||||
- docker volume create aberwyn_config || true
|
||||
- docker run -d --name=aberwyn --net=br0 -e TZ=Europe/Berlin -p 80:80 -v aberwyn_config:/app/infrastructure 192.168.1.9:3000/tai/aberwyn/aberwyn:latest
|
||||
commands:
|
||||
- export DOCKER_BUILDKIT=1
|
||||
- docker buildx create --use --driver docker-container || true
|
||||
- echo "$GITEA_TOKEN" | docker login 192.168.1.9:3000 -u "$GITEA_USERNAME" --password-stdin
|
||||
- docker buildx build --builder default --tag 192.168.1.9:3000/tai/aberwyn/aberwyn:latest --tag 192.168.1.9:3000/tai/aberwyn/aberwyn:${DRONE_COMMIT_SHA:0:7} --cache-from=type=registry,ref=192.168.1.9:3000/tai/aberwyn/aberwyn:buildcache --push -f Aberwyn/Dockerfile .
|
||||
|
||||
- name: notify-result
|
||||
image: alpine
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -365,3 +365,4 @@ FodyWeavers.xsd
|
||||
# Setupfil för Aberwyn
|
||||
infrastructure/setup.json
|
||||
Aberwyn/Infrastructure/setup.json
|
||||
/Aberwyn/Data/infrastructure/setup2.json
|
||||
|
||||
@@ -23,21 +23,6 @@ steps:
|
||||
- latest
|
||||
insecure: true
|
||||
|
||||
- name: restart-unraid-container
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host: 192.168.1.108
|
||||
port: 22
|
||||
username:
|
||||
from_secret: unraid_ssh_user
|
||||
key:
|
||||
from_secret: unraid_ssh_private_key
|
||||
script:
|
||||
- docker pull 192.168.1.9:3000/tai/aberwyn/aberwyn:latest
|
||||
- docker stop Aberwyn || true
|
||||
- docker rm Aberwyn || true
|
||||
- docker run -d --name='Aberwyn' --net='br0' -e TZ='Europe/Berlin' -p 80:80 192.168.1.9:3000/tai/aberwyn/aberwyn:latest
|
||||
|
||||
- name: notify-result
|
||||
image: alpine
|
||||
when:
|
||||
@@ -52,3 +37,7 @@ steps:
|
||||
else
|
||||
curl -X POST http://192.168.1.196:8123/api/webhook/aberwyn_update_failed
|
||||
fi
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
|
||||
@@ -10,16 +10,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Views\NewFolder\**" />
|
||||
<Content Remove="Views\NewFolder\**" />
|
||||
<EmbeddedResource Remove="Views\NewFolder\**" />
|
||||
<None Remove="Views\NewFolder\**" />
|
||||
<Content Remove="Views\Rss\_RssListPartial.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngularJS.Core" Version="1.8.2" />
|
||||
<PackageReference Include="BencodeNET" Version="5.0.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
|
||||
<PackageReference Include="Lib.Net.Http.WebPush" Version="3.3.1" />
|
||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||
|
||||
<!-- Entity Framework Core 6 -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.36" />
|
||||
@@ -45,7 +44,10 @@
|
||||
<!-- Övrigt -->
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.18" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.9" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.2" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="WebPush" Version="1.0.12" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -19,32 +19,13 @@
|
||||
<span asp-validation-for="Input.UserName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" />
|
||||
<label asp-for="Input.Password" class="form-label"></label>
|
||||
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" />
|
||||
<span asp-validation-for="Input.Password" class="text-danger"></span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="checkbox">
|
||||
<label asp-for="Input.RememberMe" class="form-label">
|
||||
<input class="form-check-input" asp-for="Input.RememberMe" />
|
||||
@Html.DisplayNameFor(m => m.Input.RememberMe)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button id="login-submit" type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
|
||||
</p>
|
||||
<p>
|
||||
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
|
||||
</p>
|
||||
<p>
|
||||
<a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Aberwyn.Areas.Identity.Pages.Account
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
[Display(Name = "Användarnamn")]
|
||||
[Display(Name = "Username")]
|
||||
public string UserName { get; set; }
|
||||
|
||||
[Required]
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Aberwyn.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var period = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
@@ -43,8 +44,8 @@ namespace Aberwyn.Controllers
|
||||
var dto = new BudgetDto
|
||||
{
|
||||
Id = period.Id,
|
||||
Year = period.Year,
|
||||
Month = period.Month,
|
||||
Year = period.Year ?? 0,
|
||||
Month = period.Month ?? 0,
|
||||
Categories = period.Categories
|
||||
.OrderBy(cat => cat.Order)
|
||||
.Select(cat => new BudgetCategoryDto
|
||||
@@ -61,7 +62,8 @@ namespace Aberwyn.Controllers
|
||||
Amount = i.Amount,
|
||||
IsExpense = i.IsExpense,
|
||||
IncludeInSummary = i.IncludeInSummary,
|
||||
BudgetItemDefinitionId = i.BudgetItemDefinitionId
|
||||
BudgetItemDefinitionId = i.BudgetItemDefinitionId,
|
||||
PaymentStatus = i.PaymentStatus
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
};
|
||||
@@ -74,6 +76,75 @@ namespace Aberwyn.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("byname/{name}")]
|
||||
public async Task<IActionResult> GetBudgetByName(string name)
|
||||
{
|
||||
var period = _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.AsEnumerable() // hämta från db och gör resten i minnet
|
||||
.FirstOrDefault(p => p.Name != null &&
|
||||
p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
|
||||
if (period == null)
|
||||
{
|
||||
return Ok(new BudgetDto
|
||||
{
|
||||
Name = name,
|
||||
Categories = new List<BudgetCategoryDto>()
|
||||
});
|
||||
}
|
||||
|
||||
var dto = new BudgetDto
|
||||
{
|
||||
Id = period.Id,
|
||||
Name = period.Name,
|
||||
Year = period.Year ?? 0,
|
||||
Month = period.Month ?? 0,
|
||||
Categories = period.Categories
|
||||
.OrderBy(cat => cat.Order)
|
||||
.Select(cat => new BudgetCategoryDto
|
||||
{
|
||||
Id = cat.Id,
|
||||
Name = cat.Name,
|
||||
Color = cat.Color,
|
||||
Items = cat.Items
|
||||
.OrderBy(i => i.Order)
|
||||
.Select(i => new BudgetItemDto
|
||||
{
|
||||
Id = i.Id,
|
||||
Name = i.Name,
|
||||
Amount = i.Amount,
|
||||
IsExpense = i.IsExpense,
|
||||
IncludeInSummary = i.IncludeInSummary,
|
||||
BudgetItemDefinitionId = i.BudgetItemDefinitionId,
|
||||
PaymentStatus = i.PaymentStatus
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
return Ok(dto);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPut("updatePaymentStatus")]
|
||||
public IActionResult UpdatePaymentStatus([FromBody] PaymentStatusUpdateDto dto)
|
||||
{
|
||||
if (dto == null)
|
||||
return BadRequest("dto is null");
|
||||
|
||||
var item = _context.BudgetItems.Find(dto.ItemId);
|
||||
if (item == null) return NotFound();
|
||||
|
||||
item.PaymentStatus = (PaymentStatus)dto.Status;
|
||||
_context.SaveChanges();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[HttpPut("category/{id}")]
|
||||
public async Task<IActionResult> UpdateCategory(int id, [FromBody] BudgetCategoryDto updatedCategory)
|
||||
{
|
||||
@@ -183,11 +254,37 @@ namespace Aberwyn.Controllers
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreatePeriod([FromBody] BudgetPeriod newPeriod)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(newPeriod.Name))
|
||||
{
|
||||
var existingNamed = await _context.BudgetPeriods
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == newPeriod.Name.ToLower());
|
||||
|
||||
if (existingNamed != null)
|
||||
return Conflict("En budget med detta namn finns redan.");
|
||||
|
||||
_context.BudgetPeriods.Add(newPeriod);
|
||||
await _context.SaveChangesAsync();
|
||||
return CreatedAtAction(nameof(GetBudget), new { year = newPeriod.Year, month = newPeriod.Month }, newPeriod);
|
||||
return Ok(new { id = newPeriod.Id });
|
||||
}
|
||||
|
||||
if (newPeriod.Year.HasValue && newPeriod.Month.HasValue)
|
||||
{
|
||||
var existing = await _context.BudgetPeriods
|
||||
.FirstOrDefaultAsync(p => p.Year == newPeriod.Year && p.Month == newPeriod.Month);
|
||||
|
||||
if (existing != null)
|
||||
return Conflict("En budget för denna månad finns redan.");
|
||||
|
||||
_context.BudgetPeriods.Add(newPeriod);
|
||||
await _context.SaveChangesAsync();
|
||||
return Ok(new { id = newPeriod.Id });
|
||||
}
|
||||
|
||||
return BadRequest("Varken namn eller år/månad angivet.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPut("item/{id}")]
|
||||
public async Task<IActionResult> UpdateItem(int id, [FromBody] BudgetItem updatedItem)
|
||||
{
|
||||
@@ -270,6 +367,73 @@ namespace Aberwyn.Controllers
|
||||
return Ok(new { id = newItem.Id });
|
||||
}
|
||||
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetAllBudgets()
|
||||
{
|
||||
var periods = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.OrderByDescending(p => p.Year)
|
||||
.ThenByDescending(p => p.Month)
|
||||
.ToListAsync();
|
||||
|
||||
var result = periods.Select(p => new
|
||||
{
|
||||
id = p.Id,
|
||||
name = p.Name,
|
||||
year = p.Year,
|
||||
month = p.Month,
|
||||
categories = p.Categories
|
||||
.OrderBy(c => c.Order)
|
||||
.Select(c => new
|
||||
{
|
||||
id = c.Id,
|
||||
name = c.Name,
|
||||
color = c.Color,
|
||||
total = c.Items.Sum(i => i.Amount),
|
||||
items = c.Items
|
||||
.OrderBy(i => i.Order)
|
||||
.Select(i => new
|
||||
{
|
||||
id = i.Id,
|
||||
name = i.Name,
|
||||
amount = i.Amount,
|
||||
isExpense = i.IsExpense,
|
||||
includeInSummary = i.IncludeInSummary
|
||||
}).ToList()
|
||||
}).ToList(),
|
||||
total = p.Categories.Sum(c => c.Items.Sum(i => i.Amount))
|
||||
});
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
// DELETE: api/budget/byname/{name}
|
||||
[HttpDelete("byname/{name}")]
|
||||
public async Task<IActionResult> DeleteByName(string name)
|
||||
{
|
||||
var period = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == name.ToLower());
|
||||
|
||||
if (period == null)
|
||||
return NotFound();
|
||||
|
||||
foreach (var category in period.Categories)
|
||||
{
|
||||
_context.BudgetItems.RemoveRange(category.Items);
|
||||
}
|
||||
|
||||
_context.BudgetCategories.RemoveRange(period.Categories);
|
||||
_context.BudgetPeriods.Remove(period);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpDelete("item/{id}")]
|
||||
@@ -314,19 +478,12 @@ namespace Aberwyn.Controllers
|
||||
return BadRequest("Ogiltig data.");
|
||||
|
||||
var period = await _context.BudgetPeriods
|
||||
.FirstOrDefaultAsync(p => p.Year == newCategoryDto.Year && p.Month == newCategoryDto.Month);
|
||||
.FirstOrDefaultAsync(p => p.Id == newCategoryDto.BudgetPeriodId);
|
||||
|
||||
if (period == null)
|
||||
{
|
||||
period = new BudgetPeriod
|
||||
{
|
||||
Year = newCategoryDto.Year,
|
||||
Month = newCategoryDto.Month
|
||||
};
|
||||
_context.BudgetPeriods.Add(period);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
return NotFound("Kunde inte hitta angiven budgetperiod.");
|
||||
|
||||
// 🔁 fortsätt som tidigare…
|
||||
var definition = await _context.BudgetCategoryDefinitions
|
||||
.FirstOrDefaultAsync(d => d.Name.ToLower() == newCategoryDto.Name.ToLower());
|
||||
|
||||
@@ -356,6 +513,8 @@ namespace Aberwyn.Controllers
|
||||
return Ok(new { id = category.Id });
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpDelete("category/{id}")]
|
||||
public async Task<IActionResult> DeleteCategory(int id)
|
||||
{
|
||||
@@ -374,7 +533,7 @@ namespace Aberwyn.Controllers
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("copy/{year:int}/{month:int}")]
|
||||
/*[HttpPost("copy/{year:int}/{month:int}")]
|
||||
public async Task<IActionResult> CopyFromPreviousMonth(int year, int month)
|
||||
{
|
||||
var targetPeriod = await _context.BudgetPeriods
|
||||
@@ -420,9 +579,333 @@ namespace Aberwyn.Controllers
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok();
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
|
||||
// Gemensam intern metod
|
||||
private async Task<BudgetPeriod?> CopyBudgetAsync(
|
||||
BudgetPeriod targetPeriod,
|
||||
BudgetPeriod sourcePeriod)
|
||||
{
|
||||
if (sourcePeriod == null) return null;
|
||||
|
||||
targetPeriod.Categories = sourcePeriod.Categories.Select(cat => new BudgetCategory
|
||||
{
|
||||
Name = cat.Name,
|
||||
Color = cat.Color,
|
||||
Order = cat.Order,
|
||||
BudgetCategoryDefinitionId = cat.BudgetCategoryDefinitionId,
|
||||
Items = cat.Items.Select(item => new BudgetItem
|
||||
{
|
||||
Name = item.Name,
|
||||
Amount = item.Amount,
|
||||
IsExpense = item.IsExpense,
|
||||
IncludeInSummary = item.IncludeInSummary,
|
||||
Order = item.Order,
|
||||
BudgetItemDefinitionId = item.BudgetItemDefinitionId
|
||||
}).ToList()
|
||||
}).ToList();
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return targetPeriod;
|
||||
}
|
||||
[HttpPost("copy/byname/{targetName}")]
|
||||
public async Task<IActionResult> CopyToNamedBudget(
|
||||
string targetName,
|
||||
[FromQuery] string? from,
|
||||
[FromQuery] int? fromYear,
|
||||
[FromQuery] int? fromMonth)
|
||||
{
|
||||
var targetPeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == targetName.ToLower());
|
||||
|
||||
if (targetPeriod == null)
|
||||
{
|
||||
targetPeriod = new BudgetPeriod { Name = targetName };
|
||||
_context.BudgetPeriods.Add(targetPeriod);
|
||||
}
|
||||
else if (targetPeriod.Categories.Any())
|
||||
{
|
||||
return BadRequest("Det finns redan data för denna budget.");
|
||||
}
|
||||
|
||||
// Hämta källperiod
|
||||
var sourcePeriod = await FindSourcePeriod(from, fromYear, fromMonth);
|
||||
if (sourcePeriod == null)
|
||||
return NotFound("Ingen budget hittades att kopiera från.");
|
||||
|
||||
await CopyBudgetAsync(targetPeriod, sourcePeriod);
|
||||
return Ok(new { id = targetPeriod.Id });
|
||||
}
|
||||
[HttpPost("copy/{year:int}/{month:int}")]
|
||||
public async Task<IActionResult> CopyToYearMonth(
|
||||
int year,
|
||||
int month,
|
||||
[FromQuery] string? from,
|
||||
[FromQuery] int? fromYear,
|
||||
[FromQuery] int? fromMonth)
|
||||
{
|
||||
var targetPeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Year == year && p.Month == month);
|
||||
|
||||
if (targetPeriod == null)
|
||||
{
|
||||
targetPeriod = new BudgetPeriod { Year = year, Month = month };
|
||||
_context.BudgetPeriods.Add(targetPeriod);
|
||||
}
|
||||
else if (targetPeriod.Categories.Any())
|
||||
{
|
||||
return BadRequest("Det finns redan data för denna månad.");
|
||||
}
|
||||
|
||||
// Hämta källperiod
|
||||
var sourcePeriod = await FindSourcePeriod(from, fromYear, fromMonth, year, month);
|
||||
if (sourcePeriod == null)
|
||||
return NotFound("Ingen data att kopiera från.");
|
||||
|
||||
await CopyBudgetAsync(targetPeriod, sourcePeriod);
|
||||
return Ok(new { id = targetPeriod.Id });
|
||||
}
|
||||
private async Task<BudgetPeriod?> FindSourcePeriod(
|
||||
string? from,
|
||||
int? fromYear,
|
||||
int? fromMonth,
|
||||
int? defaultYear = null,
|
||||
int? defaultMonth = null)
|
||||
{
|
||||
BudgetPeriod? sourcePeriod = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(from))
|
||||
{
|
||||
sourcePeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == from.ToLower());
|
||||
}
|
||||
else if (fromYear.HasValue && fromMonth.HasValue)
|
||||
{
|
||||
sourcePeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Year == fromYear && p.Month == fromMonth);
|
||||
}
|
||||
else if (defaultYear.HasValue && defaultMonth.HasValue)
|
||||
{
|
||||
var previous = new DateTime(defaultYear.Value, defaultMonth.Value, 1).AddMonths(-1);
|
||||
sourcePeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Year == previous.Year && p.Month == previous.Month);
|
||||
}
|
||||
|
||||
return sourcePeriod;
|
||||
}
|
||||
|
||||
[HttpGet("metadata")]
|
||||
public async Task<IActionResult> GetMetadata([FromQuery] int? year, [FromQuery] int? month)
|
||||
{
|
||||
var categoriesQuery = _context.BudgetCategories
|
||||
.Include(c => c.BudgetPeriod)
|
||||
.Include(c => c.Items)
|
||||
.ThenInclude(i => i.BudgetItemDefinition)
|
||||
.AsQueryable();
|
||||
|
||||
if (year.HasValue)
|
||||
categoriesQuery = categoriesQuery.Where(c => c.BudgetPeriod.Year == year.Value);
|
||||
if (month.HasValue)
|
||||
categoriesQuery = categoriesQuery.Where(c => c.BudgetPeriod.Month == month.Value);
|
||||
|
||||
var categories = await categoriesQuery.ToListAsync();
|
||||
|
||||
var categoryDefs = categories
|
||||
.Where(c => c.BudgetCategoryDefinitionId.HasValue)
|
||||
.GroupBy(c => c.Definition?.Name ?? c.Name)
|
||||
.Select(g => new {
|
||||
Name = g.Key,
|
||||
Color = g.First().Color ?? "#cccccc" // direkt från kategorin
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var categoryLabels = categories
|
||||
.GroupBy(c => c.Name)
|
||||
.Select(g => new {
|
||||
Name = g.Key,
|
||||
Color = g.First().Color ?? "#cccccc"
|
||||
})
|
||||
.ToList();
|
||||
|
||||
|
||||
var itemDefs = categories
|
||||
.SelectMany(c => c.Items)
|
||||
.Select(i => i.BudgetItemDefinition?.Name ?? i.Name)
|
||||
.Distinct()
|
||||
.OrderBy(n => n)
|
||||
.ToList();
|
||||
|
||||
var itemLabels = categories
|
||||
.SelectMany(c => c.Items)
|
||||
.Select(i => i.Name)
|
||||
.Distinct()
|
||||
.OrderBy(n => n)
|
||||
.ToList();
|
||||
|
||||
Console.WriteLine($"Metadata: {categories.Count} categories, {itemLabels.Count} items");
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
CategoryDefinitions = categoryDefs,
|
||||
CategoryLabels = categoryLabels,
|
||||
ItemDefinitions = itemDefs,
|
||||
ItemLabels = itemLabels
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("report/spreadsheet")]
|
||||
public async Task<IActionResult> GetBudgetReport(
|
||||
[FromQuery] int? year = null,
|
||||
[FromQuery] List<int>? itemDefinitionIds = null,
|
||||
[FromQuery] List<int>? categoryDefinitionIds = null,
|
||||
[FromQuery] string? itemLabel = null,
|
||||
[FromQuery] string? categoryLabel = null,
|
||||
[FromQuery] bool includeCategoryDefinitions = false,
|
||||
[FromQuery] bool includeCategoryLabels = false,
|
||||
[FromQuery] bool includeItemDefinitions = true,
|
||||
[FromQuery] bool includeItemLabels = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ Ladda navigationsproperties för definitions
|
||||
var query = _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Definition) // CategoryDefinition
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.ThenInclude(i => i.BudgetItemDefinition) // ItemDefinition
|
||||
.Where(p => p.Year.HasValue && p.Month.HasValue)
|
||||
.AsQueryable();
|
||||
|
||||
if (year.HasValue)
|
||||
query = query.Where(p => p.Year == year);
|
||||
|
||||
var periods = await query
|
||||
.OrderByDescending(p => p.Year)
|
||||
.ThenByDescending(p => p.Month)
|
||||
.ToListAsync();
|
||||
|
||||
var reportData = periods.Select(p =>
|
||||
{
|
||||
var filteredCategories = p.Categories
|
||||
.Where(c =>
|
||||
(categoryDefinitionIds == null || categoryDefinitionIds.Count == 0 ||
|
||||
(c.BudgetCategoryDefinitionId.HasValue &&
|
||||
categoryDefinitionIds.Contains(c.BudgetCategoryDefinitionId.Value))) &&
|
||||
(string.IsNullOrEmpty(categoryLabel) ||
|
||||
(c.Name ?? string.Empty).Contains(categoryLabel, StringComparison.OrdinalIgnoreCase))
|
||||
)
|
||||
.ToList(); // Viktigt: gå över till LINQ to Objects
|
||||
|
||||
var filteredItems = filteredCategories
|
||||
.SelectMany(c => c.Items)
|
||||
.Where(i =>
|
||||
i.IncludeInSummary &&
|
||||
(itemDefinitionIds == null || itemDefinitionIds.Count == 0 ||
|
||||
(i.BudgetItemDefinitionId.HasValue &&
|
||||
itemDefinitionIds.Contains(i.BudgetItemDefinitionId.Value))) &&
|
||||
(string.IsNullOrEmpty(itemLabel) ||
|
||||
(i.Name ?? string.Empty).Contains(itemLabel, StringComparison.OrdinalIgnoreCase))
|
||||
)
|
||||
.ToList();
|
||||
|
||||
// 🔹 Skapa kolumner
|
||||
var itemDefColumns = new Dictionary<string, decimal>();
|
||||
var itemLabelColumns = new Dictionary<string, decimal>();
|
||||
var catDefColumns = new Dictionary<string, decimal>();
|
||||
var catLabelColumns = new Dictionary<string, decimal>();
|
||||
|
||||
if (includeItemDefinitions)
|
||||
{
|
||||
foreach (var g in filteredItems
|
||||
.Where(i => i.BudgetItemDefinition != null)
|
||||
.GroupBy(i => i.BudgetItemDefinition!.Name))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(g.Key))
|
||||
itemDefColumns[g.Key] = g.Sum(i => i.IsExpense ? -i.Amount : i.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (includeItemLabels)
|
||||
{
|
||||
foreach (var g in filteredItems
|
||||
.Where(i => !string.IsNullOrEmpty(i.Name))
|
||||
.GroupBy(i => i.Name))
|
||||
{
|
||||
itemLabelColumns[g.Key] = g.Sum(i => i.IsExpense ? -i.Amount : i.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (includeCategoryDefinitions)
|
||||
{
|
||||
foreach (var g in filteredCategories
|
||||
.Where(c => c.Definition != null)
|
||||
.GroupBy(c => c.Definition!.Name))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(g.Key))
|
||||
{
|
||||
var total = g.SelectMany(c => c.Items)
|
||||
.Where(i => i.IncludeInSummary)
|
||||
.Sum(i => i.IsExpense ? -i.Amount : i.Amount);
|
||||
catDefColumns[g.Key] = total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeCategoryLabels)
|
||||
{
|
||||
foreach (var g in filteredCategories
|
||||
.Where(c => !string.IsNullOrEmpty(c.Name))
|
||||
.GroupBy(c => c.Name))
|
||||
{
|
||||
var total = g.SelectMany(c => c.Items)
|
||||
.Where(i => i.IncludeInSummary)
|
||||
.Sum(i => i.IsExpense ? -i.Amount : i.Amount);
|
||||
catLabelColumns[g.Key] = total;
|
||||
}
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
id = p.Id,
|
||||
year = p.Year ?? 0,
|
||||
month = p.Month ?? 0,
|
||||
income = filteredItems.Where(i => !i.IsExpense).Sum(i => i.Amount),
|
||||
expense = filteredItems.Where(i => i.IsExpense).Sum(i => i.Amount),
|
||||
net = filteredItems.Sum(i => i.IsExpense ? -i.Amount : i.Amount),
|
||||
itemDefinitions = itemDefColumns,
|
||||
itemLabels = itemLabelColumns,
|
||||
categoryDefinitions = catDefColumns,
|
||||
categoryLabels = catLabelColumns
|
||||
};
|
||||
});
|
||||
|
||||
return Ok(reportData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[GetBudgetReport] {ex}");
|
||||
return StatusCode(500, $"Ett fel uppstod i rapportgenereringen: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,47 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Aberwyn.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
[Authorize(Roles = "Budget")]
|
||||
public class BudgetController : Controller
|
||||
{
|
||||
[Authorize(Roles = "Budget")]
|
||||
public IActionResult Index()
|
||||
[Route("budget/{year:int}/{month:int}")]
|
||||
public IActionResult Index(int year, int month)
|
||||
{
|
||||
ViewData["HideSidebar"] = true;
|
||||
ViewBag.Year = year;
|
||||
ViewBag.Month = month;
|
||||
return View();
|
||||
}
|
||||
[Route("budget/list")]
|
||||
public IActionResult List()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("budget/{name}")]
|
||||
public IActionResult Index(string name)
|
||||
{
|
||||
ViewBag.BudgetName = name;
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("budget/elkostnad")]
|
||||
public IActionResult Elkostnad()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
// För fallback när ingen månad/år anges
|
||||
[Route("budget")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
return RedirectToAction("Index", new { year = now.Year, month = now.Month });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
23
Aberwyn/Controllers/ErrorController.cs
Normal file
23
Aberwyn/Controllers/ErrorController.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
public class ErrorController : Controller
|
||||
{
|
||||
[Route("Error/{statusCode}")]
|
||||
public IActionResult HttpStatusCodeHandler(int statusCode)
|
||||
{
|
||||
ViewData["ErrorCode"] = statusCode;
|
||||
return View("Error");
|
||||
}
|
||||
|
||||
[Route("Error")]
|
||||
public IActionResult Error()
|
||||
{
|
||||
var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
|
||||
ViewData["Exception"] = exceptionFeature?.Error;
|
||||
return View("Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Aberwyn.Services;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
@@ -19,24 +21,28 @@ namespace Aberwyn.Controllers
|
||||
private readonly IHostEnvironment _env;
|
||||
private readonly MenuService _menuService;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly PushNotificationService _notificationService;
|
||||
private readonly PizzaNotificationService _pizzaNotifier;
|
||||
|
||||
public FoodMenuController(MenuService menuService, IConfiguration configuration, IHostEnvironment env, ApplicationDbContext context)
|
||||
public FoodMenuController(MenuService menuService, IConfiguration configuration, IHostEnvironment env, ApplicationDbContext context, PushNotificationService notificationService, PizzaNotificationService pizzaNotificationService)
|
||||
{
|
||||
_menuService = menuService;
|
||||
|
||||
_configuration = configuration;
|
||||
_env = env;
|
||||
_context = context;
|
||||
_notificationService = notificationService;
|
||||
_pizzaNotifier = pizzaNotificationService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult PizzaOrder()
|
||||
{
|
||||
var pizzas = _menuService.GetMealsByCategory("Pizza")
|
||||
.Where(p => p.IsAvailable)
|
||||
.ToList();
|
||||
var meals = _menuService.GetMealsByCategoryName("Pizza", onlyAvailable: true);
|
||||
Console.WriteLine("Pizzas: ", meals);
|
||||
var dtoList = meals.Select(m => MealListDto.FromMeal(m)).ToList();
|
||||
ViewBag.Pizzas = dtoList;
|
||||
|
||||
ViewBag.Pizzas = pizzas;
|
||||
ViewBag.RestaurantIsOpen = GetRestaurantStatus();
|
||||
|
||||
int? lastId = HttpContext.Session.GetInt32("LastPizzaOrderId");
|
||||
@@ -53,6 +59,7 @@ namespace Aberwyn.Controllers
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult EditPizzaOrder(int id)
|
||||
{
|
||||
@@ -82,11 +89,11 @@ namespace Aberwyn.Controllers
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult PizzaOrder(string customerName, string pizzaName, string ingredients, int? orderId)
|
||||
public async Task<IActionResult> PizzaOrder(string customerName, string pizzaName, string ingredients, int? orderId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(customerName) || string.IsNullOrWhiteSpace(pizzaName))
|
||||
{
|
||||
TempData["Error"] = "Fyll i b<EFBFBD>de namn och pizza!";
|
||||
TempData["Error"] = "Fyll i både namn och pizza!";
|
||||
return RedirectToAction("PizzaOrder");
|
||||
}
|
||||
|
||||
@@ -102,10 +109,10 @@ namespace Aberwyn.Controllers
|
||||
order.CustomerName = customerName.Trim();
|
||||
order.PizzaName = pizzaName.Trim();
|
||||
order.IngredientsJson = ingredients;
|
||||
order.Status = "Unconfirmed";// <20>terst<73>ll status om du vill
|
||||
order.Status = "Unconfirmed";
|
||||
_context.SaveChanges();
|
||||
|
||||
TempData["Success"] = $"Din best<EFBFBD>llning har uppdaterats!";
|
||||
TempData["Success"] = $"Din beställning har uppdaterats!";
|
||||
return RedirectToAction("PizzaOrder");
|
||||
}
|
||||
}
|
||||
@@ -123,11 +130,11 @@ namespace Aberwyn.Controllers
|
||||
_context.PizzaOrders.Add(order);
|
||||
_context.SaveChanges();
|
||||
TempData["ForceShowForm"] = "true";
|
||||
|
||||
await _pizzaNotifier.NotifyPizzaSubscribersAsync(order.PizzaName, order.CustomerName);
|
||||
|
||||
HttpContext.Session.SetInt32("LastPizzaOrderId", order.Id);
|
||||
|
||||
TempData["Success"] = $"Tack {order.CustomerName}! Din pizza <EFBFBD>r best<EFBFBD>lld!";
|
||||
TempData["Success"] = $"Tack {order.CustomerName}! Din pizza är beställd!";
|
||||
return RedirectToAction("PizzaOrder");
|
||||
}
|
||||
|
||||
@@ -152,11 +159,15 @@ namespace Aberwyn.Controllers
|
||||
|
||||
var allMeals = _menuService.GetMeals();
|
||||
|
||||
var categories = _menuService.GetMealCategories();
|
||||
var pizzaCategory = categories.FirstOrDefault(c => c.Name.Equals("Pizza", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
viewModel.AvailablePizzas = allMeals
|
||||
.Where(m => m.Category == "Pizza")
|
||||
.Where(m => m.MealCategoryId == pizzaCategory?.Id)
|
||||
.OrderBy(m => m.Name)
|
||||
.ToList();
|
||||
|
||||
|
||||
ViewBag.RestaurantIsOpen = GetRestaurantStatus();
|
||||
|
||||
return View(viewModel);
|
||||
@@ -167,27 +178,70 @@ namespace Aberwyn.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Roles = "Chef")]
|
||||
public IActionResult UpdatePizzaOrder(int id, string status, string ingredientsJson)
|
||||
{
|
||||
var order = _context.PizzaOrders.FirstOrDefault(p => p.Id == id);
|
||||
if (order != null)
|
||||
public async Task<IActionResult> UpdatePizzaOrder(int id, string status, string ingredientsJson)
|
||||
{
|
||||
var order = await _context.PizzaOrders.FirstOrDefaultAsync(p => p.Id == id);
|
||||
if (order == null)
|
||||
return RedirectToAction("PizzaAdmin");
|
||||
|
||||
order.Status = status;
|
||||
order.IngredientsJson = ingredientsJson;
|
||||
_context.SaveChanges();
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Skicka pushnotiser till kopplade prenumeranter
|
||||
var subscribers = await _context.PushSubscribers
|
||||
.Where(s => s.PizzaOrderId == id)
|
||||
.ToListAsync();
|
||||
|
||||
var payload = $@"{{
|
||||
""title"": ""Din pizza! 🍕"",
|
||||
""body"": ""Statusuppdatering: {status}""
|
||||
}}";
|
||||
|
||||
foreach (var sub in subscribers)
|
||||
{
|
||||
try
|
||||
{
|
||||
_notificationService.SendNotification(sub.Endpoint, sub.P256DH, sub.Auth, payload);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Kunde inte skicka notis till {sub.Endpoint}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction("PizzaAdmin");
|
||||
}
|
||||
|
||||
|
||||
[Authorize(Roles = "Chef")]
|
||||
public IActionResult Veckomeny(int? week, int? year)
|
||||
{
|
||||
var menuService = _menuService;
|
||||
|
||||
var today = DateTime.Today;
|
||||
int resolvedWeek = week ?? ISOWeek.GetWeekOfYear(today);
|
||||
int resolvedYear = year ?? today.Year;
|
||||
|
||||
// Starta alltid från ett GILTIGT datum
|
||||
DateTime referenceDate;
|
||||
|
||||
if (week.HasValue && year.HasValue)
|
||||
{
|
||||
// Om week < 1 eller > 53 – tolka det som navigering
|
||||
// från närliggande vecka i stället för att krascha
|
||||
int safeWeek = Math.Clamp(week.Value, 1, 53);
|
||||
referenceDate = ISOWeek.ToDateTime(year.Value, safeWeek, DayOfWeek.Monday);
|
||||
|
||||
// Justera datumet med differensen (ex: week=0 → -1 vecka)
|
||||
referenceDate = referenceDate.AddDays((week.Value - safeWeek) * 7);
|
||||
}
|
||||
else
|
||||
{
|
||||
referenceDate = today;
|
||||
}
|
||||
|
||||
// Normalisera alltid via datum
|
||||
int resolvedWeek = ISOWeek.GetWeekOfYear(referenceDate);
|
||||
int resolvedYear = ISOWeek.GetYear(referenceDate);
|
||||
|
||||
var menus = menuService.GetWeeklyMenu(resolvedWeek, resolvedYear);
|
||||
|
||||
@@ -196,6 +250,11 @@ namespace Aberwyn.Controllers
|
||||
WeekNumber = resolvedWeek,
|
||||
Year = resolvedYear,
|
||||
WeeklyMenus = menus,
|
||||
WishList = _context.MealWishes
|
||||
.Include(w => w.RequestedByUser)
|
||||
.Where(w => !w.IsArchived)
|
||||
.OrderByDescending(w => w.CreatedAt)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
var recent = menuService
|
||||
@@ -319,7 +378,7 @@ namespace Aberwyn.Controllers
|
||||
}
|
||||
|
||||
entry.Cook = cook;
|
||||
|
||||
entry.Date = FirstDateOfISOWeek(year, week).AddDays(day);
|
||||
if (string.IsNullOrWhiteSpace(mealName))
|
||||
{
|
||||
switch (mealType.ToLower())
|
||||
@@ -454,6 +513,108 @@ namespace Aberwyn.Controllers
|
||||
return RedirectToAction("PizzaAdmin");
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Chef")]
|
||||
[HttpGet]
|
||||
public IActionResult Calculator()
|
||||
{
|
||||
var plans = _context.DoughPlans
|
||||
.OrderByDescending(p => p.Datum)
|
||||
.ThenByDescending(p => p.Id)
|
||||
.ToList();
|
||||
|
||||
ViewBag.Plans = plans;
|
||||
|
||||
return View(new DoughPlan { AntalPizzor = 8, ViktPerPizza = 220 });
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetRecentMenuEntries(int weeksBack = 4)
|
||||
{
|
||||
var today = DateTime.Today;
|
||||
|
||||
// Datum för måndag i nuvarande vecka
|
||||
int deltaToMonday = ((int)today.DayOfWeek + 6) % 7; // måndag=0, söndag=6
|
||||
var thisWeekMonday = today.AddDays(-deltaToMonday);
|
||||
|
||||
// Startdatum: måndag X veckor bak
|
||||
var startMonday = thisWeekMonday.AddDays(-7 * weeksBack);
|
||||
|
||||
// Slutdatum: söndag för förra veckan
|
||||
var endSunday = thisWeekMonday.AddDays(-1);
|
||||
|
||||
// Hämta alla veckomenyer inom intervallet
|
||||
var allMenus = _context.WeeklyMenus
|
||||
.Where(w => w.Date >= startMonday && w.Date <= endSunday)
|
||||
.OrderBy(w => w.Date)
|
||||
.ToList();
|
||||
|
||||
// Hämta alla relevanta meal IDs
|
||||
var mealIds = allMenus
|
||||
.SelectMany(w => new[] { w.BreakfastMealId, w.LunchMealId, w.DinnerMealId })
|
||||
.Where(id => id.HasValue)
|
||||
.Select(id => id!.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var meals = _context.Meals
|
||||
.Where(m => mealIds.Contains(m.Id))
|
||||
.ToDictionary(m => m.Id, m => m.Name);
|
||||
|
||||
// Skapa entries
|
||||
var entries = allMenus
|
||||
.Select(w => new RecentMenuEntry
|
||||
{
|
||||
Date = w.Date,
|
||||
WeekNumber = ISOWeek.GetWeekOfYear(w.Date),
|
||||
Year = w.Date.Year,
|
||||
BreakfastMealName = w.BreakfastMealId.HasValue && meals.ContainsKey(w.BreakfastMealId.Value)
|
||||
? meals[w.BreakfastMealId.Value]
|
||||
: "—",
|
||||
LunchMealName = w.LunchMealId.HasValue && meals.ContainsKey(w.LunchMealId.Value)
|
||||
? meals[w.LunchMealId.Value]
|
||||
: "—",
|
||||
DinnerMealName = w.DinnerMealId.HasValue && meals.ContainsKey(w.DinnerMealId.Value)
|
||||
? meals[w.DinnerMealId.Value]
|
||||
: "—"
|
||||
})
|
||||
.OrderBy(e => e.Date)
|
||||
.ToList();
|
||||
|
||||
return Json(entries);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static DateTime FirstDateOfISOWeek(int year, int weekOfYear)
|
||||
{
|
||||
DateTime jan4 = new DateTime(year, 1, 4);
|
||||
int daysOffset = DayOfWeek.Thursday - jan4.DayOfWeek;
|
||||
|
||||
DateTime firstThursday = jan4.AddDays(daysOffset);
|
||||
var cal = System.Globalization.CultureInfo.CurrentCulture.Calendar;
|
||||
int firstWeek = cal.GetWeekOfYear(firstThursday, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
||||
|
||||
if (firstWeek <= 1)
|
||||
weekOfYear -= 1;
|
||||
|
||||
DateTime result = firstThursday.AddDays(weekOfYear * 7);
|
||||
return result.AddDays(-3); // tillbaka till måndag
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[Authorize(Roles = "Chef")]
|
||||
[HttpPost]
|
||||
public IActionResult SaveDoughPlan([FromBody] DoughPlan model)
|
||||
{
|
||||
if (model == null) return BadRequest();
|
||||
|
||||
_context.DoughPlans.Add(model);
|
||||
_context.SaveChanges();
|
||||
|
||||
return Json(new { success = true, id = model.Id });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,23 +13,38 @@ namespace Aberwyn.Controllers
|
||||
//private readonly BudgetService _budgetService;
|
||||
private readonly MenuService _menuService;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly TorrentService _torrentService;
|
||||
|
||||
|
||||
// Constructor to inject dependencies
|
||||
public HomeController(ApplicationDbContext applicationDbContext, ILogger<HomeController> logger, MenuService menuService)
|
||||
public HomeController(ApplicationDbContext applicationDbContext, ILogger<HomeController> logger, MenuService menuService, TorrentService torrentService)
|
||||
{
|
||||
_logger = logger;
|
||||
_menuService = menuService;
|
||||
_context = applicationDbContext;
|
||||
_torrentService = torrentService;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var isOpen = _context.AppSettings.FirstOrDefault(x => x.Key == "RestaurantIsOpen")?.Value == "True";
|
||||
ViewBag.RestaurantIsOpen = isOpen;
|
||||
return View();
|
||||
|
||||
var now = DateTime.Now;
|
||||
var showDate = now.Hour >= 20 ? now.Date.AddDays(1) : now.Date;
|
||||
|
||||
var todaysMenu = _menuService.GetMenuForDate(showDate);
|
||||
var userId = User.Identity?.Name ?? "guest";
|
||||
|
||||
// Awaita async-metoden
|
||||
var newCount = await _torrentService.GetUnseenTorrentCountAsync(userId);
|
||||
|
||||
ViewBag.NewTorrentCount = newCount;
|
||||
ViewBag.ShowDate = showDate;
|
||||
return View(todaysMenu);
|
||||
}
|
||||
|
||||
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
return View();
|
||||
|
||||
@@ -45,7 +45,10 @@ namespace Aberwyn.Controllers
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
ViewBag.Categories = _menuService.GetMealCategories()
|
||||
.Where(c => c.IsActive)
|
||||
.OrderBy(c => c.Name)
|
||||
.ToList();
|
||||
ViewData["IsEditing"] = edit;
|
||||
return View("View", meal);
|
||||
}
|
||||
@@ -164,5 +167,123 @@ namespace Aberwyn.Controllers
|
||||
//service.DeleteMeal(id);
|
||||
return RedirectToAction("Edit"); // eller tillbaka till lista
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin,Chef")]
|
||||
[HttpGet("/meal/categories")]
|
||||
public IActionResult Categories()
|
||||
{
|
||||
var categories = _menuService.GetMealCategories()
|
||||
.Select(cat => {
|
||||
cat.MealCount = _menuService.GetMealCountForCategory(cat.Id);
|
||||
return cat;
|
||||
}).ToList();
|
||||
|
||||
return View("MealCategories", categories);
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin,Chef")]
|
||||
[HttpPost("/meal/categories/save")]
|
||||
public IActionResult SaveCategory(MealCategory category)
|
||||
{
|
||||
_menuService.SaveOrUpdateCategory(category);
|
||||
return RedirectToAction("Categories");
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin,Chef")]
|
||||
[HttpPost("/meal/categories/delete")]
|
||||
public IActionResult DeleteCategory(int id)
|
||||
{
|
||||
_menuService.DeleteCategory(id);
|
||||
return RedirectToAction("Categories");
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("/meal/lab")]
|
||||
public IActionResult Lab(int? id)
|
||||
{
|
||||
if (id.HasValue)
|
||||
{
|
||||
var entry = _menuService.GetRecipeLabEntryById(id.Value);
|
||||
if (entry == null) return NotFound();
|
||||
|
||||
// Hämta versioner först när vi vet att entry finns
|
||||
entry.Versions = _menuService.GetLabVersionsForEntry(id.Value);
|
||||
return View("Lab", entry);
|
||||
}
|
||||
|
||||
// Skapa ett tomt labb-entry för formulär
|
||||
var newEntry = new RecipeLabEntry
|
||||
{
|
||||
Title = "",
|
||||
Inspiration = "",
|
||||
Notes = "",
|
||||
Tags = ""
|
||||
};
|
||||
|
||||
return View("Lab", newEntry);
|
||||
}
|
||||
// Lägg till dessa actions i din MealController
|
||||
|
||||
[HttpPost("/meal/lab/save")]
|
||||
public IActionResult SaveLabEntry(RecipeLabEntry entry)
|
||||
{
|
||||
if (entry.Id == 0)
|
||||
{
|
||||
// Ny entry
|
||||
entry.CreatedAt = DateTime.Now;
|
||||
_menuService.AddLabEntry(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Uppdatera befintlig
|
||||
_menuService.UpdateLabEntry(entry);
|
||||
}
|
||||
|
||||
return RedirectToAction("Lab", new { id = entry.Id });
|
||||
}
|
||||
[HttpPost("/meal/lab/create")]
|
||||
public IActionResult CreateLabEntry(RecipeLabEntry entry)
|
||||
{
|
||||
entry.CreatedAt = DateTime.Now;
|
||||
_menuService.AddLabEntry(entry);
|
||||
|
||||
return RedirectToAction("Lab", new { id = entry.Id });
|
||||
}
|
||||
|
||||
[HttpPost("/meal/lab/save-ingredients")]
|
||||
public IActionResult SaveLabIngredients(int RecipeLabEntryId, List<LabIngredient> Ingredients)
|
||||
{
|
||||
_menuService.SaveIngredientsForLabEntry(RecipeLabEntryId, Ingredients);
|
||||
return RedirectToAction("Lab", new { id = RecipeLabEntryId });
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("/meal/lab/addversion")]
|
||||
public IActionResult AddLabVersion(RecipeLabVersion version)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return RedirectToAction("Lab", new { id = version.RecipeLabEntryId });
|
||||
|
||||
var entry = _menuService.GetRecipeLabEntryById(version.RecipeLabEntryId);
|
||||
if (entry == null) return NotFound();
|
||||
|
||||
version.CreatedAt = DateTime.Now;
|
||||
|
||||
// Kopiera nuvarande ingredienser
|
||||
var copiedIngredients = entry.Ingredients
|
||||
.Select(i => new LabVersionIngredient
|
||||
{
|
||||
Quantity = i.Quantity,
|
||||
Item = i.Item
|
||||
})
|
||||
.ToList();
|
||||
|
||||
_menuService.SaveLabVersionWithIngredients(version, copiedIngredients);
|
||||
|
||||
return RedirectToAction("Lab", new { id = version.RecipeLabEntryId });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// MealMenuApiController.cs
|
||||
using Aberwyn.Models;
|
||||
using Aberwyn.Data;
|
||||
using Aberwyn.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Net.Http.Headers;
|
||||
@@ -27,6 +28,24 @@ namespace Aberwyn.Controllers
|
||||
return Ok(menu ?? new List<WeeklyMenu>());
|
||||
}
|
||||
|
||||
[HttpGet("getPublishedMeals")]
|
||||
public IActionResult GetPublishedMeals([FromQuery] bool includeUnpublished = false)
|
||||
{
|
||||
var meals = _menuService.GetMeals()
|
||||
.Where(m => includeUnpublished || m.IsPublished)
|
||||
.Select(m => new {
|
||||
m.Id,
|
||||
m.Name,
|
||||
m.Description,
|
||||
ThumbnailData = m.ThumbnailData != null ? Convert.ToBase64String(m.ThumbnailData) : null
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(meals);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet("getMeals")]
|
||||
public IActionResult GetMeals()
|
||||
{
|
||||
@@ -45,6 +64,23 @@ namespace Aberwyn.Controllers
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("today")]
|
||||
public IActionResult GetTodayMenu()
|
||||
{
|
||||
var today = DateTime.Today;
|
||||
var menu = _menuService.GetMenuForDate(today);
|
||||
|
||||
if (menu == null)
|
||||
return NotFound(new { message = "Ingen meny hittades för idag." });
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
date = today.ToString("yyyy-MM-dd"),
|
||||
lunch = menu.LunchMealName ?? "",
|
||||
dinner = menu.DinnerMealName ?? "",
|
||||
breakfast = menu.BreakfastMealName ?? ""
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[HttpPut("menu")]
|
||||
@@ -69,6 +105,10 @@ namespace Aberwyn.Controllers
|
||||
return StatusCode(500, "Failed to add meal.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#region Skolmat
|
||||
[HttpGet("skolmat")]
|
||||
public async Task<IActionResult> GetSkolmat(int week, [FromQuery] string sensor = "sensor.engelbrektsskolan")
|
||||
|
||||
79
Aberwyn/Controllers/MealRatingApiController.cs
Normal file
79
Aberwyn/Controllers/MealRatingApiController.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Aberwyn.Data;
|
||||
using Aberwyn.Models;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class MealRatingApiController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public MealRatingApiController(ApplicationDbContext context, UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
_context = context;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
[HttpGet("{mealId}")]
|
||||
public async Task<IActionResult> GetRating(int mealId)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
var rating = await _context.MealRatings
|
||||
.FirstOrDefaultAsync(r => r.MealId == mealId && r.UserId == user.Id);
|
||||
|
||||
return Ok(rating?.Rating ?? 0);
|
||||
}
|
||||
[HttpGet("average/{mealId}")]
|
||||
public async Task<IActionResult> GetAverageRating(int mealId)
|
||||
{
|
||||
var ratings = await _context.MealRatings
|
||||
.Where(r => r.MealId == mealId)
|
||||
.ToListAsync();
|
||||
|
||||
if (ratings.Count == 0)
|
||||
return Ok(0);
|
||||
|
||||
var avg = ratings.Average(r => r.Rating);
|
||||
return Ok(avg);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SetRating([FromBody] MealRatingDto model)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
var existing = await _context.MealRatings
|
||||
.FirstOrDefaultAsync(r => r.MealId == model.MealId && r.UserId == user.Id);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Rating = model.Rating;
|
||||
existing.CreatedAt = DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.MealRatings.Add(new MealRating
|
||||
{
|
||||
MealId = model.MealId,
|
||||
UserId = user.Id,
|
||||
Rating = model.Rating,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
124
Aberwyn/Controllers/MealWishController.cs
Normal file
124
Aberwyn/Controllers/MealWishController.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Aberwyn.Data;
|
||||
using Aberwyn.Models; // Byt till din namespace
|
||||
using Humanizer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class MealWishController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public MealWishController(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// 1. Skapa en ny önskan
|
||||
[HttpPost("create")]
|
||||
public async Task<ActionResult<MealWishDto>> Create([FromBody] CreateMealWishDto dto)
|
||||
{
|
||||
var wish = new MealWish
|
||||
{
|
||||
Name = dto.Name,
|
||||
Recipe = dto.Recipe
|
||||
};
|
||||
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous";
|
||||
wish.RequestedByUserId = userId;
|
||||
wish.CreatedAt = DateTime.UtcNow;
|
||||
wish.IsArchived = false;
|
||||
wish.IsImported = false;
|
||||
|
||||
_context.MealWishes.Add(wish);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(MealWishDto.FromEntity(wish)); // ✅ returnera DTO
|
||||
}
|
||||
|
||||
// 2. Hämta alla önskningar (admin)
|
||||
[HttpGet("all")]
|
||||
public async Task<IActionResult> GetAll(bool includeArchived = false)
|
||||
{
|
||||
var wishes = await _context.MealWishes
|
||||
.Include(w => w.RequestedByUser) // hämta användaren
|
||||
.Where(w => includeArchived || !w.IsArchived)
|
||||
.OrderByDescending(w => w.CreatedAt)
|
||||
.Select(w => new {
|
||||
w.Id,
|
||||
w.Name,
|
||||
w.Recipe,
|
||||
RequestedByUserName = w.RequestedByUser != null ? w.RequestedByUser.UserName : "Okänd",
|
||||
CreatedAt = w.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm"),
|
||||
w.IsArchived,
|
||||
w.IsImported
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(wishes);
|
||||
}
|
||||
|
||||
|
||||
// 3. Avfärda / arkivera
|
||||
[HttpPost("{id}/archive")]
|
||||
public async Task<IActionResult> Archive(int id)
|
||||
{
|
||||
var wish = await _context.MealWishes.FindAsync(id);
|
||||
if (wish == null) return NotFound();
|
||||
|
||||
wish.IsArchived = true;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(wish);
|
||||
}
|
||||
|
||||
// 4. Importera till Meals
|
||||
[HttpPost("{id}/import")]
|
||||
public async Task<IActionResult> Import(int id)
|
||||
{
|
||||
var wish = await _context.MealWishes.FindAsync(id);
|
||||
if (wish == null) return NotFound();
|
||||
|
||||
if (wish.IsImported)
|
||||
return BadRequest("Denna rätt har redan importerats.");
|
||||
|
||||
// Skapa en ny Meal
|
||||
var meal = new Meal
|
||||
{
|
||||
Name = wish.Name,
|
||||
Instructions = wish.Recipe,
|
||||
IsAvailable = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsPublished = false
|
||||
};
|
||||
|
||||
_context.Meals.Add(meal);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
wish.LinkedMealId = meal.Id;
|
||||
wish.IsImported = true;
|
||||
wish.IsArchived = true;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(wish);
|
||||
}
|
||||
|
||||
[HttpGet("search")]
|
||||
public async Task<IActionResult> Search([FromQuery] string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return BadRequest();
|
||||
|
||||
var meals = await _context.Meals
|
||||
.Where(m => m.Name.Contains(name))
|
||||
.OrderBy(m => m.Name)
|
||||
.Take(10)
|
||||
.Select(m => new { m.Id, m.Name })
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(meals);
|
||||
}
|
||||
}
|
||||
13
Aberwyn/Controllers/MovieController.cs
Normal file
13
Aberwyn/Controllers/MovieController.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
public class MovieController : Controller
|
||||
{
|
||||
[HttpGet("/movie/search")]
|
||||
public IActionResult Search()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,5 +59,8 @@ namespace Aberwyn.Controllers
|
||||
TempData["Success"] = "Beställningen har lagts!";
|
||||
return RedirectToAction("Order");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,37 +38,72 @@ namespace Aberwyn.Controllers
|
||||
return Ok(new { message = $"Skickade pizzanotiser till {count} användare." });
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("subscribe")]
|
||||
public async Task<IActionResult> Subscribe([FromBody] PushSubscription subscription)
|
||||
[HttpPost("subscribe-user")]
|
||||
public async Task<IActionResult> SubscribeUser([FromBody] PushSubscription subscription)
|
||||
{
|
||||
var existing = await _context.PushSubscribers
|
||||
.FirstOrDefaultAsync(s => s.Endpoint == subscription.Endpoint);
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
var existing = await _context.PushSubscribers
|
||||
.FirstOrDefaultAsync(s => s.Endpoint == subscription.Endpoint);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
var newSubscriber = new PushSubscriber
|
||||
var newSub = new PushSubscriber
|
||||
{
|
||||
Endpoint = subscription.Endpoint,
|
||||
P256DH = subscription.Keys["p256dh"],
|
||||
Auth = subscription.Keys["auth"],
|
||||
UserId = user.Id
|
||||
};
|
||||
|
||||
_context.PushSubscribers.Add(newSubscriber);
|
||||
_context.PushSubscribers.Add(newSub);
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.P256DH = subscription.Keys["p256dh"];
|
||||
existing.Auth = subscription.Keys["auth"];
|
||||
existing.UserId = user.Id;
|
||||
}
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("subscribe")]
|
||||
public async Task<IActionResult> Subscribe([FromBody] PushSubscriptionWithOrder dto)
|
||||
{
|
||||
var existing = await _context.PushSubscribers
|
||||
.FirstOrDefaultAsync(s => s.Endpoint == dto.Subscription.Endpoint);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
var newSub = new PushSubscriber
|
||||
{
|
||||
Endpoint = dto.Subscription.Endpoint,
|
||||
P256DH = dto.Subscription.Keys["p256dh"],
|
||||
Auth = dto.Subscription.Keys["auth"],
|
||||
PizzaOrderId = dto.PizzaOrderId
|
||||
};
|
||||
_context.PushSubscribers.Add(newSub);
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.P256DH = dto.Subscription.Keys["p256dh"];
|
||||
existing.Auth = dto.Subscription.Keys["auth"];
|
||||
existing.PizzaOrderId = dto.PizzaOrderId; // uppdatera kopplingen
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public class PushSubscriptionWithOrder
|
||||
{
|
||||
public PushSubscription Subscription { get; set; }
|
||||
public int PizzaOrderId { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("vapid-public-key")]
|
||||
public IActionResult GetVapidKey([FromServices] IConfiguration config)
|
||||
{
|
||||
@@ -109,6 +144,24 @@ namespace Aberwyn.Controllers
|
||||
return Ok($"Skickade notiser till {successCount} användare.");
|
||||
}
|
||||
|
||||
[HttpPost("unsubscribe")]
|
||||
public async Task<IActionResult> Unsubscribe([FromBody] PushUnsubscribeDto dto)
|
||||
{
|
||||
var sub = await _context.PushSubscribers.FirstOrDefaultAsync(s => s.Endpoint == dto.Endpoint);
|
||||
if (sub != null)
|
||||
{
|
||||
_context.PushSubscribers.Remove(sub);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public class PushUnsubscribeDto
|
||||
{
|
||||
public string Endpoint { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,43 +22,5 @@ namespace Aberwyn.Controllers
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> GetReport([FromBody] BudgetReportRequestDto request)
|
||||
{
|
||||
var start = new DateTime(request.StartYear, request.StartMonth, 1);
|
||||
var end = new DateTime(request.EndYear, request.EndMonth, 1);
|
||||
|
||||
var items = await _context.BudgetItems
|
||||
.Include(i => i.BudgetItemDefinition)
|
||||
.Include(i => i.BudgetCategory)
|
||||
.ThenInclude(c => c.BudgetPeriod)
|
||||
.Where(i =>
|
||||
i.BudgetCategory.BudgetPeriod.Year * 12 + i.BudgetCategory.BudgetPeriod.Month >= start.Year * 12 + start.Month &&
|
||||
i.BudgetCategory.BudgetPeriod.Year * 12 + i.BudgetCategory.BudgetPeriod.Month <= end.Year * 12 + end.Month &&
|
||||
request.DefinitionIds.Contains(i.BudgetItemDefinitionId ?? -1))
|
||||
.ToListAsync();
|
||||
|
||||
var grouped = items
|
||||
.GroupBy(i => new { i.BudgetCategory.BudgetPeriod.Year, i.BudgetCategory.BudgetPeriod.Month })
|
||||
.Select(g => new BudgetReportResultDto
|
||||
{
|
||||
Year = g.Key.Year,
|
||||
Month = g.Key.Month,
|
||||
Definitions = g
|
||||
.GroupBy(i => new { i.BudgetItemDefinitionId, i.BudgetItemDefinition.Name })
|
||||
.Select(dg => new DefinitionSumDto
|
||||
{
|
||||
DefinitionId = dg.Key.BudgetItemDefinitionId ?? 0,
|
||||
DefinitionName = dg.Key.Name,
|
||||
TotalAmount = dg.Sum(x => x.Amount)
|
||||
}).ToList()
|
||||
})
|
||||
.OrderBy(r => r.Year).ThenBy(r => r.Month)
|
||||
.ToList();
|
||||
|
||||
return Ok(grouped);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Aberwyn.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
@@ -6,9 +7,9 @@ namespace Aberwyn.Controllers
|
||||
[Authorize(Roles = "Budget")]
|
||||
public class ReportController : Controller
|
||||
{
|
||||
public IActionResult BudgetReport()
|
||||
public IActionResult Budget()
|
||||
{
|
||||
return View("BudgetReport");
|
||||
return View(new BudgetReportViewModel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
245
Aberwyn/Controllers/RssController.cs
Normal file
245
Aberwyn/Controllers/RssController.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using Aberwyn.Data;
|
||||
using BencodeNET.Torrents;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class RssController : Controller
|
||||
{
|
||||
private readonly ITorrentService _torrentService;
|
||||
private readonly ILogger<RssController> _logger;
|
||||
private readonly DelugeClient _deluge;
|
||||
private readonly MovieMetadataService _movieMetadataService;
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public RssController(
|
||||
ITorrentService torrentService,
|
||||
ILogger<RssController> logger,
|
||||
DelugeClient delugeClient,
|
||||
MovieMetadataService movieMetadataService,
|
||||
ApplicationDbContext context)
|
||||
{
|
||||
_torrentService = torrentService;
|
||||
_logger = logger;
|
||||
_deluge = delugeClient;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index(int page = 1, string sort = "date", string range = "all")
|
||||
{
|
||||
var pageSize = 20;
|
||||
var userId = User.Identity?.Name ?? "guest";
|
||||
|
||||
var torrents = await _torrentService.GetRecentTorrentsAsync(500);
|
||||
|
||||
// Filtrera på tidsintervall
|
||||
torrents = range switch
|
||||
{
|
||||
"day" => torrents.Where(t => t.PublishDate > DateTime.UtcNow.AddDays(-1)).ToList(),
|
||||
"week" => torrents.Where(t => t.PublishDate > DateTime.UtcNow.AddDays(-7)).ToList(),
|
||||
"month" => torrents.Where(t => t.PublishDate > DateTime.UtcNow.AddMonths(-1)).ToList(),
|
||||
_ => torrents
|
||||
};
|
||||
|
||||
// Sortera
|
||||
torrents = sort switch
|
||||
{
|
||||
"seeders" => torrents.OrderByDescending(t => t.Seeders).ToList(),
|
||||
"leechers" => torrents.OrderByDescending(t => t.Leechers).ToList(),
|
||||
"title" => torrents.OrderBy(t => t.MovieName).ToList(),
|
||||
_ => torrents.OrderByDescending(t => t.PublishDate).ToList(),
|
||||
};
|
||||
|
||||
// Hämta sedda torrents för användaren
|
||||
var seenHashes = await _context.UserTorrentSeen
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(x => x.InfoHash)
|
||||
.ToListAsync();
|
||||
|
||||
// Bygg viewmodels med IsNew
|
||||
var pagedItems = torrents
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(t => new TorrentListItemViewModel
|
||||
{
|
||||
InfoHash = t.InfoHash ?? "",
|
||||
Title = t.Title,
|
||||
MovieName = t.MovieName ?? "",
|
||||
PublishDate = t.PublishDate,
|
||||
Seeders = t.Seeders,
|
||||
Leechers = t.Leechers,
|
||||
TorrentUrl = t.TorrentUrl,
|
||||
Metadata = t.Metadata,
|
||||
IsDownloaded = t.IsDownloaded,
|
||||
IsNew = t.InfoHash != null && !seenHashes.Contains(t.InfoHash),
|
||||
AvailableOn = !string.IsNullOrEmpty(t.Metadata?.Providers)
|
||||
? t.Metadata.Providers.Split(',').ToList()
|
||||
: new List<string>()
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Markera som sedda
|
||||
var newSeen = pagedItems
|
||||
.Where(i => i.IsNew && !string.IsNullOrEmpty(i.InfoHash))
|
||||
.Select(i => new UserTorrentSeen
|
||||
{
|
||||
UserId = userId,
|
||||
InfoHash = i.InfoHash,
|
||||
SeenDate = DateTime.UtcNow
|
||||
});
|
||||
|
||||
_context.UserTorrentSeen.AddRange(newSeen);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var vm = new TorrentListViewModel
|
||||
{
|
||||
Items = pagedItems,
|
||||
CurrentPage = page,
|
||||
TotalPages = (int)Math.Ceiling(torrents.Count / (double)pageSize),
|
||||
CurrentSort = sort,
|
||||
CurrentRange = range
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Add(string torrentUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await _deluge.LoginAsync("deluge1"))
|
||||
{
|
||||
var success = await _deluge.AddTorrentUrlAsync(torrentUrl);
|
||||
|
||||
if (success)
|
||||
{
|
||||
// Hämta torrenten baserat på TorrentUrl
|
||||
var torrent = await _context.Set<TorrentItem>()
|
||||
.FirstOrDefaultAsync(t => t.TorrentUrl == torrentUrl);
|
||||
|
||||
if (torrent != null)
|
||||
{
|
||||
// Markera som nerladdad
|
||||
torrent.IsDownloaded = true;
|
||||
torrent.Status = TorrentStatus.Downloaded;
|
||||
_context.Update(torrent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skapa ny post om den inte finns
|
||||
var newTorrent = new TorrentItem
|
||||
{
|
||||
Title = torrentUrl, // byt till korrekt namnkälla om du har
|
||||
TorrentUrl = torrentUrl,
|
||||
PublishDate = DateTime.UtcNow,
|
||||
Status = TorrentStatus.Downloaded,
|
||||
IsDownloaded = true,
|
||||
RssSource = "Manuell", // eller sätt rätt källa
|
||||
};
|
||||
|
||||
_context.Add(newTorrent);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("SavedChanges to torrents");
|
||||
return Json(new
|
||||
{
|
||||
success = true,
|
||||
message = "Torrent tillagd i Deluge och markerad som nerladdad."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Json(new { success = false, message = "Misslyckades att lägga till torrent i Deluge." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Fel vid tillägg av torrent");
|
||||
return Json(new { success = false, message = "Ett fel uppstod vid tillägg av torrent." });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Upload(TorrentUploadViewModel model)
|
||||
{
|
||||
if (model.TorrentFile == null || model.TorrentFile.Length == 0)
|
||||
{
|
||||
ModelState.AddModelError("TorrentFile", "Vänligen välj en torrent-fil");
|
||||
return View("Index", model);
|
||||
}
|
||||
|
||||
if (!model.TorrentFile.FileName.EndsWith(".torrent", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ModelState.AddModelError("TorrentFile", "Endast .torrent filer är tillåtna");
|
||||
return View("Index", model);
|
||||
}
|
||||
|
||||
if (model.TorrentFile.Length > 10 * 1024 * 1024) // 10MB limit
|
||||
{
|
||||
ModelState.AddModelError("TorrentFile", "Filen är för stor (max 10MB)");
|
||||
return View("Index", model);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var torrentInfo = await _torrentService.ParseTorrentAsync(model.TorrentFile);
|
||||
|
||||
if (!string.IsNullOrEmpty(torrentInfo.ErrorMessage))
|
||||
{
|
||||
ModelState.AddModelError("", torrentInfo.ErrorMessage);
|
||||
return View("Index", model);
|
||||
}
|
||||
|
||||
torrentInfo = await _torrentService.FetchTrackerStatsAsync(torrentInfo);
|
||||
|
||||
model.TorrentInfo = torrentInfo;
|
||||
model.ShowResults = true;
|
||||
|
||||
return View("Index", model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Fel vid uppladdning av torrent");
|
||||
ModelState.AddModelError("", "Ett oväntat fel inträffade");
|
||||
return View("Index", model);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RefreshStats(string infoHash, string scrapeUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
var torrentInfo = new TorrentInfo
|
||||
{
|
||||
InfoHash = infoHash,
|
||||
ScrapeUrl = scrapeUrl,
|
||||
InfoHashBytes = Convert.FromHexString(infoHash.Replace("%", ""))
|
||||
};
|
||||
|
||||
var updatedInfo = await _torrentService.FetchTrackerStatsAsync(torrentInfo);
|
||||
|
||||
return Json(new
|
||||
{
|
||||
success = updatedInfo.HasTrackerData,
|
||||
seeders = updatedInfo.Seeders,
|
||||
leechers = updatedInfo.Leechers,
|
||||
completed = updatedInfo.Completed,
|
||||
error = updatedInfo.ErrorMessage
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Fel vid uppdatering av tracker-stats");
|
||||
return Json(new { success = false, error = "Fel vid uppdatering" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,15 @@ namespace Aberwyn.Controllers
|
||||
{
|
||||
private readonly IWebHostEnvironment _env;
|
||||
private readonly ILogger<SetupController> _logger;
|
||||
private readonly string _filePath;
|
||||
|
||||
public SetupController(IWebHostEnvironment env, ILogger<SetupController> logger)
|
||||
{
|
||||
_env = env;
|
||||
_logger = logger;
|
||||
var dataRoot = Path.Combine(Directory.GetCurrentDirectory(), "data"); // /app/data i containern
|
||||
_filePath = Path.Combine(dataRoot, "infrastructure", "setup.json");
|
||||
|
||||
}
|
||||
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
@@ -35,7 +39,7 @@ namespace Aberwyn.Controllers
|
||||
[HttpPost("reset")]
|
||||
public IActionResult Reset()
|
||||
{
|
||||
var path = Path.Combine(_env.ContentRootPath, "infrastructure", "setup.json");
|
||||
var path = _filePath;
|
||||
|
||||
var resetSettings = new SetupSettings
|
||||
{
|
||||
@@ -70,6 +74,7 @@ namespace Aberwyn.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Start setup write");
|
||||
// Bygg connection string säkert
|
||||
var baseConnBuilder = new MySqlConnectionStringBuilder
|
||||
{
|
||||
@@ -122,12 +127,10 @@ namespace Aberwyn.Controllers
|
||||
|
||||
// Sätt konfig-flagga tidigt
|
||||
model.IsConfigured = true;
|
||||
|
||||
_logger.LogDebug("Filepath" + _filePath);
|
||||
// Spara setup.json
|
||||
var filePath = Path.Combine(_env.ContentRootPath, "infrastructure", "setup.json");
|
||||
var json = JsonSerializer.Serialize(model, new JsonSerializerOptions { WriteIndented = true });
|
||||
System.IO.File.WriteAllText(filePath, json);
|
||||
|
||||
System.IO.File.WriteAllText(_filePath, json);
|
||||
// Roller och admin
|
||||
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Aberwyn.Models;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
@@ -14,11 +15,34 @@ namespace Aberwyn.Data
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
builder.Entity<TorrentItem>()
|
||||
.HasIndex(t => t.InfoHash)
|
||||
.IsUnique();
|
||||
|
||||
builder.Entity<TorrentItem>()
|
||||
.Property(t => t.Status)
|
||||
.HasConversion<string>();
|
||||
builder.Entity<WeeklyMenu>().ToTable("WeeklyMenu");
|
||||
builder.Entity<MealCategory>().HasData(
|
||||
new MealCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Pizza",
|
||||
Slug = "pizza",
|
||||
Icon = "🍕",
|
||||
Color = "#f97316",
|
||||
IsActive = true,
|
||||
DisplayOrder = 1
|
||||
}
|
||||
);
|
||||
builder.Entity<TorrentItem>()
|
||||
.OwnsOne(t => t.Metadata);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public DbSet<DoughPlan> DoughPlans { get; set; }
|
||||
|
||||
public DbSet<BudgetPeriod> BudgetPeriods { get; set; }
|
||||
public DbSet<BudgetCategory> BudgetCategories { get; set; }
|
||||
public DbSet<BudgetItem> BudgetItems { get; set; }
|
||||
@@ -33,6 +57,18 @@ namespace Aberwyn.Data
|
||||
public DbSet<Ingredient> Ingredients { get; set; }
|
||||
public DbSet<UserPreferences> UserPreferences { get; set; }
|
||||
public DbSet<StoredPushSubscription> PushSubscriptions { get; set; }
|
||||
public DbSet<MealCategory> MealCategories { get; set; }
|
||||
public DbSet<RecipeLabEntry> RecipeLabEntries { get; set; }
|
||||
public DbSet<RecipeLabVersion> RecipeLabVersions { get; set; }
|
||||
public DbSet<LabIngredient> LabIngredients { get; set; }
|
||||
public DbSet<LabVersionIngredient> LabVersionIngredients { get; set; }
|
||||
public DbSet<MealRating> MealRatings { get; set; }
|
||||
public DbSet<TorrentItem> TorrentItems { get; set; }
|
||||
public DbSet<RssFeed> RssFeeds { get; set; }
|
||||
public DbSet<DownloadRule> DownloadRules { get; set; }
|
||||
public DbSet<UserTorrentSeen> UserTorrentSeen { get; set; }
|
||||
public DbSet<MealWish> MealWishes { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
79
Aberwyn/Data/DelugeClient.cs
Normal file
79
Aberwyn/Data/DelugeClient.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using MySqlX.XDevAPI;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
public class DelugeClient
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _url;
|
||||
private string _sessionId;
|
||||
|
||||
public DelugeClient(HttpClient httpClient, string baseUrl = "http://192.168.1.3:8112/json")
|
||||
{
|
||||
_http = httpClient;
|
||||
_url = baseUrl;
|
||||
}
|
||||
|
||||
public async Task<bool> LoginAsync(string password)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
method = "auth.login",
|
||||
@params = new object[] { password },
|
||||
id = 1
|
||||
};
|
||||
|
||||
var response = await _http.PostAsJsonAsync(_url, payload);
|
||||
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
|
||||
// spara sessioncookie för framtida requests
|
||||
if (response.Headers.TryGetValues("Set-Cookie", out var cookies))
|
||||
{
|
||||
_sessionId = cookies.FirstOrDefault()?.Split(';')[0];
|
||||
if (!_http.DefaultRequestHeaders.Contains("Cookie"))
|
||||
_http.DefaultRequestHeaders.Add("Cookie", _sessionId);
|
||||
}
|
||||
|
||||
return json.GetProperty("result").GetBoolean();
|
||||
}
|
||||
|
||||
public async Task<bool> AddMagnetAsync(string magnetLink)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
method = "core.add_torrent_url",
|
||||
@params = new object[] { magnetLink, new { } },
|
||||
id = 2
|
||||
};
|
||||
|
||||
var response = await _http.PostAsJsonAsync(_url, payload);
|
||||
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
return json.GetProperty("result").ValueKind != JsonValueKind.Null;
|
||||
}
|
||||
public async Task<bool> AddTorrentUrlAsync(string torrentUrl)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
method = "core.add_torrent_url",
|
||||
@params = new object[]
|
||||
{
|
||||
torrentUrl,
|
||||
new
|
||||
{
|
||||
download_location = "/download/incomplete",
|
||||
move_completed = true,
|
||||
move_completed_path = "/media/Movies",
|
||||
}
|
||||
},
|
||||
id = 3
|
||||
};
|
||||
|
||||
var response = await _http.PostAsJsonAsync(_url, payload);
|
||||
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
|
||||
return json.GetProperty("result").ValueKind != JsonValueKind.Null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
103
Aberwyn/Data/HDTorrentsTrackerScraper.cs
Normal file
103
Aberwyn/Data/HDTorrentsTrackerScraper.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
public class HdTorrentsTrackerScraper
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<HdTorrentsTrackerScraper> _logger;
|
||||
|
||||
public HdTorrentsTrackerScraper(HttpClient httpClient, ILogger<HdTorrentsTrackerScraper> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<(int seeders, int leechers, int completed)> ScrapeHdTorrents(string infoHash, string downloadKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(infoHash) || string.IsNullOrEmpty(downloadKey))
|
||||
return (0, 0, 0);
|
||||
|
||||
// Konvertera hex InfoHash till URL-encoded bytes
|
||||
var infoHashBytes = HexStringToBytes(infoHash);
|
||||
|
||||
//%67%2e%d6%f9%30%e9%33%88%e3%1b%c6%f0%85%f5%f7%78%44%05%10%e7
|
||||
var encodedInfoHash = Uri.EscapeDataString(Encoding.GetEncoding("ISO-8859-1").GetString(infoHashBytes));
|
||||
var encodedInfoHashv2 = EncodeInfoHash(infoHash);
|
||||
|
||||
// Bygga scrape URL baserat på HD-Torrents format
|
||||
var scrapeUrl = $"https://hdts-announce.ru/scrape.php?pid=98d498dedff78ba0334f662d151eb19b7?info_hash={encodedInfoHashv2}";
|
||||
|
||||
_logger.LogInformation("Scraping HD-Torrents: {Url}", scrapeUrl);
|
||||
|
||||
var response = await _httpClient.GetAsync(scrapeUrl);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await response.Content.ReadAsByteArrayAsync(); // Fix: Content.ReadAsByteArrayAsync()
|
||||
return ParseBencodeResponse(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Scrape failed with status: {StatusCode}", response.StatusCode);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to scrape HD-Torrents for InfoHash: {InfoHash}", infoHash);
|
||||
}
|
||||
|
||||
return (0, 0, 0);
|
||||
}
|
||||
private static string EncodeInfoHash(string hex)
|
||||
{
|
||||
var bytes = HexStringToBytes(hex);
|
||||
return string.Concat(bytes.Select(b => $"%{b:X2}")).ToLower();
|
||||
}
|
||||
private static byte[] HexStringToBytes(string hex)
|
||||
{
|
||||
if (hex.Length % 2 != 0)
|
||||
throw new ArgumentException("Invalid hex string length.");
|
||||
|
||||
var bytes = new byte[hex.Length / 2];
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private (int seeders, int leechers, int completed) ParseBencodeResponse(byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Enkel bencode parsing för tracker response
|
||||
var response = System.Text.Encoding.UTF8.GetString(data);
|
||||
|
||||
// Tracker response är vanligtvis i format:
|
||||
// d5:filesd20:[info_hash]d8:completei[antal]e9:downloadedi[antal]e10:incompletei[antal]eee
|
||||
|
||||
// Hitta complete (seeders)
|
||||
var completeMatch = System.Text.RegularExpressions.Regex.Match(response, @"8:completei(\d+)e");
|
||||
var seeders = completeMatch.Success ? int.Parse(completeMatch.Groups[1].Value) : 0;
|
||||
|
||||
// Hitta incomplete (leechers)
|
||||
var incompleteMatch = System.Text.RegularExpressions.Regex.Match(response, @"10:incompletei(\d+)e");
|
||||
var leechers = incompleteMatch.Success ? int.Parse(incompleteMatch.Groups[1].Value) : 0;
|
||||
|
||||
// Hitta downloaded (completed)
|
||||
var downloadedMatch = System.Text.RegularExpressions.Regex.Match(response, @"9:downloadedi(\d+)e");
|
||||
var completed = downloadedMatch.Success ? int.Parse(downloadedMatch.Groups[1].Value) : 0;
|
||||
|
||||
return (seeders, leechers, completed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to parse bencode response");
|
||||
return (0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Aberwyn/Data/IRssProcessor.cs
Normal file
7
Aberwyn/Data/IRssProcessor.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
public interface IRssProcessor
|
||||
{
|
||||
Task ProcessRssFeeds();
|
||||
}
|
||||
}
|
||||
@@ -292,7 +292,7 @@ public List<Meal> GetMealsSlim(bool includeThumbnail = false)
|
||||
public List<Meal> GetMealsByCategory(string category)
|
||||
{
|
||||
return _context.Meals
|
||||
.Where(m => m.Category == category)
|
||||
//.Where(m => m.Category == category)
|
||||
.Include(m => m.Ingredients)
|
||||
.OrderBy(m => m.Name)
|
||||
.ToList();
|
||||
@@ -339,6 +339,71 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
||||
|
||||
return menus;
|
||||
}
|
||||
public WeeklyMenu? GetMenuForDate(DateTime date)
|
||||
{
|
||||
int week = ISOWeek.GetWeekOfYear(date);
|
||||
int year = date.Year;
|
||||
|
||||
int dayOfWeek = (int)date.DayOfWeek;
|
||||
if (dayOfWeek == 0) dayOfWeek = 7;
|
||||
|
||||
var menu = _context.WeeklyMenus
|
||||
.FirstOrDefault(w => w.WeekNumber == week && w.Year == year && w.DayOfWeek == dayOfWeek);
|
||||
|
||||
if (menu != null)
|
||||
{
|
||||
var mealIds = new[] { menu.BreakfastMealId, menu.LunchMealId, menu.DinnerMealId }
|
||||
.Where(id => id.HasValue)
|
||||
.Select(id => id.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var allMeals = _context.Meals
|
||||
.Where(m => mealIds.Contains(m.Id))
|
||||
.ToDictionary(m => m.Id);
|
||||
|
||||
if (menu.BreakfastMealId is int bId && allMeals.TryGetValue(bId, out var breakfast))
|
||||
{
|
||||
menu.BreakfastMealName = breakfast.Name;
|
||||
menu.BreakfastThumbnail = breakfast.ThumbnailData;
|
||||
}
|
||||
|
||||
if (menu.LunchMealId is int lId && allMeals.TryGetValue(lId, out var lunch))
|
||||
{
|
||||
menu.LunchMealName = lunch.Name;
|
||||
menu.LunchThumbnail = lunch.ThumbnailData;
|
||||
}
|
||||
|
||||
if (menu.DinnerMealId is int dId && allMeals.TryGetValue(dId, out var dinner))
|
||||
{
|
||||
menu.DinnerMealName = dinner.Name;
|
||||
menu.DinnerThumbnail = dinner.ThumbnailData;
|
||||
}
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
public List<Meal> GetMealsByCategoryName(string categoryName, string? searchTerm = null, bool onlyAvailable = false)
|
||||
{
|
||||
var query = _context.Meals
|
||||
.Include(m => m.Category)
|
||||
.Include(m => m.Ingredients)
|
||||
.Where(m => m.Category != null && m.Category.Name == categoryName);
|
||||
|
||||
if (onlyAvailable)
|
||||
query = query.Where(m => m.IsAvailable);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
string lowered = searchTerm.Trim().ToLower();
|
||||
query = query.Where(m => m.Name.ToLower().Contains(lowered));
|
||||
}
|
||||
|
||||
return query
|
||||
.OrderBy(m => m.Name)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
public List<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
|
||||
{
|
||||
@@ -370,5 +435,128 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
public List<MealCategory> GetMealCategories()
|
||||
{
|
||||
return _context.MealCategories.OrderBy(c => c.DisplayOrder).ToList();
|
||||
}
|
||||
public int GetMealCountForCategory(int categoryId)
|
||||
{
|
||||
return _context.Meals.Count(m => m.MealCategoryId == categoryId);
|
||||
}
|
||||
|
||||
public void SaveOrUpdateCategory(MealCategory cat)
|
||||
{
|
||||
if (cat.Id == 0)
|
||||
{
|
||||
_context.MealCategories.Add(cat);
|
||||
}
|
||||
else
|
||||
{
|
||||
var existing = _context.MealCategories.Find(cat.Id);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Name = cat.Name;
|
||||
existing.Slug = cat.Slug;
|
||||
existing.Description = cat.Description;
|
||||
existing.Icon = cat.Icon;
|
||||
existing.Color = cat.Color;
|
||||
existing.IsActive = cat.IsActive;
|
||||
existing.DisplayOrder = cat.DisplayOrder;
|
||||
}
|
||||
}
|
||||
_context.SaveChanges();
|
||||
}
|
||||
#region Lab
|
||||
public void DeleteCategory(int id)
|
||||
{
|
||||
var cat = _context.MealCategories.Find(id);
|
||||
if (cat != null)
|
||||
{
|
||||
_context.MealCategories.Remove(cat);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
}
|
||||
public RecipeLabEntry? GetRecipeLabEntryById(int id)
|
||||
{
|
||||
return _context.RecipeLabEntries
|
||||
.Include(e => e.Versions)
|
||||
.FirstOrDefault(e => e.Id == id);
|
||||
}
|
||||
|
||||
public void AddVersionToLabEntry(RecipeLabVersion version)
|
||||
{
|
||||
_context.RecipeLabVersions.Add(version);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
|
||||
public void AddLabEntry(RecipeLabEntry entry)
|
||||
{
|
||||
_context.RecipeLabEntries.Add(entry);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
public void SaveLabVersionWithIngredients(RecipeLabVersion version, List<LabVersionIngredient> ingredients)
|
||||
{
|
||||
_context.RecipeLabVersions.Add(version);
|
||||
_context.SaveChanges(); // så vi får ett ID
|
||||
|
||||
foreach (var ing in ingredients)
|
||||
{
|
||||
ing.RecipeLabVersionId = version.Id;
|
||||
}
|
||||
|
||||
_context.LabVersionIngredients.AddRange(ingredients);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
// Lägg till dessa metoder i din MenuService klass
|
||||
|
||||
public void UpdateLabEntry(RecipeLabEntry entry)
|
||||
{
|
||||
var existing = _context.RecipeLabEntries.Find(entry.Id);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Title = entry.Title;
|
||||
existing.Inspiration = entry.Inspiration;
|
||||
existing.Notes = entry.Notes;
|
||||
existing.Tags = entry.Tags;
|
||||
_context.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public RecipeLabEntry GetRecipeLabEntryWithIngredients(int id)
|
||||
{
|
||||
return _context.RecipeLabEntries
|
||||
.Include(e => e.Versions)
|
||||
.Include(e => e.Ingredients)
|
||||
.FirstOrDefault(e => e.Id == id);
|
||||
}
|
||||
public void SaveIngredientsForLabEntry(int labEntryId, List<LabIngredient> ingredients)
|
||||
{
|
||||
var existing = _context.LabIngredients
|
||||
.Where(i => i.RecipeLabEntryId == labEntryId)
|
||||
.ToList();
|
||||
|
||||
_context.LabIngredients.RemoveRange(existing);
|
||||
|
||||
foreach (var ing in ingredients)
|
||||
ing.RecipeLabEntryId = labEntryId;
|
||||
|
||||
_context.LabIngredients.AddRange(ingredients);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
|
||||
public List<RecipeLabVersion> GetLabVersionsForEntry(int entryId)
|
||||
{
|
||||
return _context.RecipeLabVersions
|
||||
.Where(v => v.RecipeLabEntryId == entryId)
|
||||
.Include(v => v.Ingredients)
|
||||
.OrderByDescending(v => v.CreatedAt)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
104
Aberwyn/Data/MovieMetadataService.cs
Normal file
104
Aberwyn/Data/MovieMetadataService.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
public class MovieMetadataService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _apiKey = "6a666b45";
|
||||
private readonly string _tpiKey = "6a666b45";
|
||||
|
||||
|
||||
public MovieMetadataService(HttpClient httpClient)
|
||||
{
|
||||
_http = httpClient;
|
||||
}
|
||||
|
||||
public async Task<MovieMetadata?> GetMovieAsync(string title, int? year = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
MovieMetadata? metadata = null;
|
||||
|
||||
// Först försök med titel + år
|
||||
if (year.HasValue)
|
||||
{
|
||||
var urlWithYear = $"https://www.omdbapi.com/?t={Uri.EscapeDataString(title)}&y={year.Value}&apikey={_apiKey}";
|
||||
metadata = await _http.GetFromJsonAsync<MovieMetadata>(urlWithYear);
|
||||
}
|
||||
|
||||
// Om inget hittas, försök bara med titel
|
||||
if (metadata == null || string.IsNullOrEmpty(metadata.Title))
|
||||
{
|
||||
var urlTitleOnly = $"https://www.omdbapi.com/?t={Uri.EscapeDataString(title)}&apikey={_apiKey}";
|
||||
metadata = await _http.GetFromJsonAsync<MovieMetadata>(urlTitleOnly);
|
||||
}
|
||||
|
||||
// Returnera metadata om något hittades
|
||||
return (metadata != null && !string.IsNullOrEmpty(metadata.Title)) ? metadata : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TmdbService
|
||||
{
|
||||
private readonly HttpClient _http = new();
|
||||
private readonly string _apiKey = "aef2f49296b77b9b9c269678d04bdbc6";
|
||||
private readonly string _country = "SE";
|
||||
|
||||
public async Task<string> GetWatchProvidersByTitleAsync(string title, int? year = null)
|
||||
{
|
||||
var query = Uri.EscapeDataString(title);
|
||||
var url = $"https://api.themoviedb.org/3/search/movie?api_key={_apiKey}&query={query}";
|
||||
if (year.HasValue)
|
||||
url += $"&year={year.Value}";
|
||||
|
||||
var searchResult = await _http.GetFromJsonAsync<TmdbSearchResponse>(url);
|
||||
var movie = searchResult?.Results?.FirstOrDefault();
|
||||
if (movie == null) return "";
|
||||
|
||||
var providersUrl = $"https://api.themoviedb.org/3/movie/{movie.Id}/watch/providers?api_key={_apiKey}";
|
||||
var providersResult = await _http.GetFromJsonAsync<WatchProvidersResponse>(providersUrl);
|
||||
|
||||
if (providersResult?.Results?.ContainsKey(_country) == true)
|
||||
{
|
||||
var seProviders = providersResult.Results[_country];
|
||||
var names = new List<string>();
|
||||
if (seProviders.Flatrate != null) names.AddRange(seProviders.Flatrate.Select(p => p.ProviderName));
|
||||
//if (seProviders.Rent != null) names.AddRange(seProviders.Rent.Select(p => p.ProviderName));
|
||||
//if (seProviders.Buy != null) names.AddRange(seProviders.Buy.Select(p => p.ProviderName));
|
||||
return string.Join(", ", names.Distinct());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// JSON-klasser för TMDb
|
||||
public class TmdbSearchResponse { public List<TmdbMovie> Results { get; set; } = new(); }
|
||||
public class TmdbMovie { public int Id { get; set; } }
|
||||
public class WatchProvidersResponse { public Dictionary<string, CountryProviders> Results { get; set; } = new(); }
|
||||
public class CountryProviders
|
||||
{
|
||||
public List<Provider>? Flatrate { get; set; }
|
||||
public List<Provider>? Rent { get; set; }
|
||||
public List<Provider>? Buy { get; set; }
|
||||
}
|
||||
public class Provider
|
||||
{
|
||||
[JsonPropertyName("provider_name")]
|
||||
public string ProviderName { get; set; } = "";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -40,7 +40,14 @@ namespace Aberwyn.Services
|
||||
s.Endpoint.StartsWith("https://"))
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
var allSubscribers = await _context.PushSubscribers
|
||||
.Include(s => s.User)
|
||||
.ThenInclude(u => u.Preferences)
|
||||
.ToListAsync();
|
||||
foreach (var s in allSubscribers)
|
||||
{
|
||||
Console.WriteLine($"🔍 Sub: {s.Endpoint}, User: {s.User?.UserName}, NotifyPizza: {s.User?.Preferences?.NotifyPizza}");
|
||||
}
|
||||
int successCount = 0;
|
||||
foreach (var sub in subscribers)
|
||||
{
|
||||
|
||||
304
Aberwyn/Data/RssProcessor.cs
Normal file
304
Aberwyn/Data/RssProcessor.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using Aberwyn.Data;
|
||||
using Aberwyn.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
public class RssProcessor : IRssProcessor
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly ILogger<RssProcessor> _logger;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly HdTorrentsTrackerScraper _trackerScraper;
|
||||
private readonly MovieMetadataService _movieMetadataService;
|
||||
|
||||
public RssProcessor(ApplicationDbContext context, ILogger<RssProcessor> logger,
|
||||
HttpClient httpClient, HdTorrentsTrackerScraper trackerScraper, MovieMetadataService movieMetadataService)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_trackerScraper = trackerScraper;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
}
|
||||
|
||||
public async Task ProcessRssFeeds()
|
||||
{
|
||||
|
||||
var debug = false;
|
||||
|
||||
var oneHourAgo = DateTime.UtcNow.AddHours(-1);
|
||||
if (debug)
|
||||
oneHourAgo = DateTime.UtcNow.AddHours(1);
|
||||
|
||||
var activeFeeds = await _context.RssFeeds
|
||||
.Where(f => f.IsActive && f.LastChecked <= oneHourAgo)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var feed in activeFeeds)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessSingleFeed(feed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing RSS feed {FeedName}", feed.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessSingleFeed(RssFeed feed)
|
||||
{
|
||||
var rssContent = await _httpClient.GetStringAsync(feed.Url);
|
||||
var rssDoc = XDocument.Parse(rssContent);
|
||||
var items = rssDoc.Descendants("item");
|
||||
try
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
try
|
||||
{
|
||||
var torrentItem = ParseRssItem(item, feed);
|
||||
Console.WriteLine($"Trying to save: Title='{torrentItem.Title}', InfoHash='{torrentItem.InfoHash}', MovieName='{torrentItem.MovieName}'");
|
||||
|
||||
// Kolla om den redan finns
|
||||
var exists = await _context.TorrentItems
|
||||
.AnyAsync(t => t.InfoHash == torrentItem.InfoHash ||
|
||||
(t.Title == torrentItem.Title && t.RssSource == feed.Name));
|
||||
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(torrentItem.InfoHash) && !string.IsNullOrEmpty(torrentItem.DownloadKey))
|
||||
{
|
||||
var (seeders, leechers, completed) = await _trackerScraper.ScrapeHdTorrents(
|
||||
torrentItem.InfoHash,
|
||||
torrentItem.DownloadKey);
|
||||
|
||||
torrentItem.Seeders = seeders;
|
||||
torrentItem.Leechers = leechers;
|
||||
torrentItem.Completed = completed;
|
||||
|
||||
Console.WriteLine($"Scraped stats for {torrentItem.Title}: S:{seeders} L:{leechers} C:{completed}");
|
||||
}
|
||||
|
||||
var metadata = await _movieMetadataService.GetMovieAsync(torrentItem.MovieName, torrentItem.Year);
|
||||
if (metadata != null)
|
||||
{
|
||||
torrentItem.Metadata = metadata;
|
||||
var tmdbService = new TmdbService();
|
||||
torrentItem.Metadata.Providers = await tmdbService.GetWatchProvidersByTitleAsync(torrentItem.MovieName, torrentItem.Year);
|
||||
|
||||
}
|
||||
|
||||
_context.TorrentItems.Add(torrentItem);
|
||||
var savedChanges = await _context.SaveChangesAsync();
|
||||
Console.WriteLine($"SaveChanges returned: {savedChanges}");
|
||||
// Kolla auto-download regler
|
||||
if (await ShouldAutoDownload(torrentItem))
|
||||
{
|
||||
await ProcessTorrentDownload(torrentItem);
|
||||
}
|
||||
} else
|
||||
{
|
||||
|
||||
var howLongAgo = DateTime.UtcNow.AddHours(-6);
|
||||
if (torrentItem.PublishDate >= howLongAgo)
|
||||
{
|
||||
|
||||
var existing = await _context.TorrentItems
|
||||
.FirstOrDefaultAsync(t => t.InfoHash == torrentItem.InfoHash
|
||||
|| (t.Title == torrentItem.Title && t.RssSource == feed.Name));
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
var (seeders, leechers, completed) = await _trackerScraper.ScrapeHdTorrents(
|
||||
existing.InfoHash,
|
||||
existing.DownloadKey);
|
||||
|
||||
existing.Seeders = seeders;
|
||||
existing.Leechers = leechers;
|
||||
existing.Completed = completed;
|
||||
|
||||
_context.TorrentItems.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
Console.WriteLine($"Updated stats for {existing.Title}: S:{seeders} L:{leechers} C:{completed}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception itemEx)
|
||||
{
|
||||
Console.WriteLine($"Error processing individual item: {itemEx.Message}");
|
||||
Console.WriteLine($"Stack trace: {itemEx.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
feed.LastChecked = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error in ProcessSingleFeed: {ex.Message}");
|
||||
Console.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
private TorrentItem ParseRssItem(XElement item, RssFeed feed)
|
||||
{
|
||||
var title = item.Element("title")?.Value ?? "Unknown Title";
|
||||
var description = item.Element("description")?.Value ?? ""; // Fix: Default till tom sträng
|
||||
var pubDate = DateTime.TryParse(item.Element("pubDate")?.Value, out var date) ? date : DateTime.UtcNow;
|
||||
var link = item.Element("link")?.Value ?? "";
|
||||
|
||||
var infoHash = ExtractInfoHashFromUrl(link);
|
||||
var downloadKey = ExtractParameterFromUrl(link, "key");
|
||||
var token = ExtractParameterFromUrl(link, "token");
|
||||
var (movieName, year) = ParseMovieNameAndYear(title);
|
||||
|
||||
var magnetLink = "";
|
||||
if (!string.IsNullOrEmpty(infoHash))
|
||||
{
|
||||
magnetLink = $"magnet:?xt=urn:btih:{infoHash}&dn={Uri.EscapeDataString(title)}";
|
||||
}
|
||||
|
||||
return new TorrentItem
|
||||
{
|
||||
Title = title ?? "Unknown Title", // Garanterat inte null
|
||||
Description = description ?? string.Empty, // Garanterat inte null
|
||||
TorrentUrl = string.IsNullOrEmpty(link) ? null : link,
|
||||
MagnetLink = string.IsNullOrEmpty(magnetLink) ? null : magnetLink,
|
||||
InfoHash = string.IsNullOrEmpty(infoHash) ? null : infoHash,
|
||||
PublishDate = pubDate,
|
||||
RssSource = feed.Name ?? "Unknown", // Garanterat inte null
|
||||
Status = TorrentStatus.New,
|
||||
DownloadKey = string.IsNullOrEmpty(downloadKey) ? null : downloadKey,
|
||||
Token = string.IsNullOrEmpty(token) ? null : token,
|
||||
MovieName = string.IsNullOrEmpty(movieName) ? null : movieName,
|
||||
Year = year,
|
||||
Category = DetermineCategory(title),
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private (string movieName, int? year) ParseMovieNameAndYear(string title)
|
||||
{
|
||||
// Exempel titlar:
|
||||
// "Bring It on Fight to the Finish 2009 BluRay 1080p DDP 5.1 x264-hallowed"
|
||||
// "Deadpool & Wolverine 2024 Hybrid 1080p UHD BluRay DD+5.1 Atmos DV HDR10+ x265-HiDt"
|
||||
|
||||
var movieName = "";
|
||||
int? year = null;
|
||||
|
||||
// Leta efter år (4 siffror mellan 1900-2099)
|
||||
var yearMatch = System.Text.RegularExpressions.Regex.Match(title, @"\b(19\d{2}|20\d{2})\b");
|
||||
if (yearMatch.Success && int.TryParse(yearMatch.Groups[1].Value, out var parsedYear))
|
||||
{
|
||||
year = parsedYear;
|
||||
|
||||
// Ta allt före året som filmnamn
|
||||
var yearIndex = yearMatch.Index;
|
||||
movieName = title.Substring(0, yearIndex).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Om inget år hittas, ta första delen före vanliga release-keywords
|
||||
var keywordMatch = System.Text.RegularExpressions.Regex.Match(title,
|
||||
@"\b(BluRay|WEB-DL|WEBRip|HDTV|DVDRIP|REMUX|UHD|1080p|720p|480p|2160p)\b",
|
||||
RegexOptions.IgnoreCase);
|
||||
|
||||
if (keywordMatch.Success)
|
||||
{
|
||||
movieName = title.Substring(0, keywordMatch.Index).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: ta första delen innan sista bindestreck
|
||||
var lastDashIndex = title.LastIndexOf('-');
|
||||
if (lastDashIndex > 0)
|
||||
{
|
||||
movieName = title.Substring(0, lastDashIndex).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
movieName = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rensa bort vanliga suffix från filmnamnet
|
||||
movieName = System.Text.RegularExpressions.Regex.Replace(movieName,
|
||||
@"\b(REMASTERED|REPACK|PROPER|REAL|EXTENDED|DIRECTORS?\.?CUT|UNRATED)\b",
|
||||
"", RegexOptions.IgnoreCase).Trim();
|
||||
|
||||
return (movieName, year);
|
||||
}
|
||||
|
||||
private string ExtractInfoHashFromUrl(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url)) return "";
|
||||
|
||||
var match = System.Text.RegularExpressions.Regex.Match(url, @"hash=([a-fA-F0-9]{40})");
|
||||
return match.Success ? match.Groups[1].Value.ToUpper() : "";
|
||||
}
|
||||
|
||||
private string ExtractParameterFromUrl(string url, string paramName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url)) return "";
|
||||
|
||||
var match = System.Text.RegularExpressions.Regex.Match(url, $@"{paramName}=([^&]+)");
|
||||
return match.Success ? Uri.UnescapeDataString(match.Groups[1].Value) : "";
|
||||
}
|
||||
|
||||
private string DetermineCategory(string title)
|
||||
{
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(title, @"\b(S\d{2}E\d{2}|Season|Episode)\b", RegexOptions.IgnoreCase))
|
||||
return "TV";
|
||||
else
|
||||
return "Movies";
|
||||
}
|
||||
|
||||
private async Task<bool> ShouldAutoDownload(TorrentItem item)
|
||||
{
|
||||
var rules = await _context.DownloadRules.Where(r => r.AutoDownload).ToListAsync();
|
||||
|
||||
return rules.Any(rule =>
|
||||
(string.IsNullOrEmpty(rule.KeywordFilter) ||
|
||||
item.Title.Contains(rule.KeywordFilter, StringComparison.OrdinalIgnoreCase)) &&
|
||||
(string.IsNullOrEmpty(rule.CategoryFilter) ||
|
||||
item.Category == rule.CategoryFilter) &&
|
||||
item.Seeders >= rule.MinSeeders &&
|
||||
(rule.MaxSize == 0 || item.Size <= rule.MaxSize));
|
||||
}
|
||||
|
||||
private async Task ProcessTorrentDownload(TorrentItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.MagnetLink))
|
||||
{
|
||||
_logger.LogInformation("Starting magnet download for {Title}", item.Title);
|
||||
item.Status = TorrentStatus.Downloaded;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(item.TorrentUrl))
|
||||
{
|
||||
var torrentData = await _httpClient.GetByteArrayAsync(item.TorrentUrl);
|
||||
item.Status = TorrentStatus.Downloaded;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error downloading torrent {Title}", item.Title);
|
||||
item.Status = TorrentStatus.Failed;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@ namespace Aberwyn.Data
|
||||
public SetupService(IWebHostEnvironment env)
|
||||
{
|
||||
_env = env;
|
||||
_filePath = Path.Combine(_env.ContentRootPath, "infrastructure", "setup.json");
|
||||
var dataRoot = Path.Combine(Directory.GetCurrentDirectory(), "data"); // /app/data i containern
|
||||
_filePath = Path.Combine(dataRoot, "infrastructure", "setup.json");
|
||||
}
|
||||
|
||||
|
||||
@@ -57,8 +58,10 @@ namespace Aberwyn.Data
|
||||
{
|
||||
public static SetupSettings Load(IHostEnvironment env)
|
||||
{
|
||||
var path = Path.Combine(env.ContentRootPath, "infrastructure", "setup.json");
|
||||
var json = File.ReadAllText(path);
|
||||
var dataRoot = Path.Combine(Directory.GetCurrentDirectory(), "data"); // /app/data i containern
|
||||
var filePath = Path.Combine(dataRoot, "infrastructure", "setup.json");
|
||||
|
||||
var json = File.ReadAllText(filePath);
|
||||
return JsonSerializer.Deserialize<SetupSettings>(json)!;
|
||||
}
|
||||
|
||||
|
||||
41
Aberwyn/Data/TorrentRssService.cs
Normal file
41
Aberwyn/Data/TorrentRssService.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Aberwyn.Data;
|
||||
|
||||
namespace Aberwyn.Services
|
||||
{
|
||||
public class TorrentRssService : BackgroundService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<TorrentRssService> _logger;
|
||||
|
||||
public TorrentRssService(IServiceProvider serviceProvider, ILogger<TorrentRssService> logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
// Vänta lite innan första körningen
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var rssProcessor = scope.ServiceProvider.GetRequiredService<IRssProcessor>();
|
||||
await rssProcessor.ProcessRssFeeds();
|
||||
|
||||
_logger.LogInformation("RSS feeds processed successfully at {Time}", DateTime.UtcNow);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing RSS feeds");
|
||||
}
|
||||
|
||||
// Vänta 10 minuter innan nästa körning
|
||||
await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
213
Aberwyn/Data/TorrentService.cs
Normal file
213
Aberwyn/Data/TorrentService.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using Aberwyn.Data;
|
||||
using BencodeNET.Objects;
|
||||
using BencodeNET.Parsing;
|
||||
using BencodeNET.Torrents;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text;
|
||||
|
||||
public interface ITorrentService
|
||||
{
|
||||
Task<TorrentInfo> ParseTorrentAsync(IFormFile file);
|
||||
Task<TorrentInfo> FetchTrackerStatsAsync(TorrentInfo info);
|
||||
Task<List<TorrentItem>> GetRecentTorrentsAsync(int count);
|
||||
}
|
||||
|
||||
public class TorrentService : ITorrentService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<TorrentService> _logger;
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
// Kända trackers och deras egenskaper
|
||||
private readonly Dictionary<string, TrackerInfo> _knownTrackers = new()
|
||||
{
|
||||
["hdts-announce.ru"] = new TrackerInfo
|
||||
{
|
||||
Name = "HD-Torrents",
|
||||
SupportsScraping = true, // Ändrat till true
|
||||
RequiresAuth = false, // Kan fungera utan auth för scraping
|
||||
IsPrivate = true,
|
||||
Notes = "Privat tracker, scraping kan fungera utan inloggning"
|
||||
}
|
||||
};
|
||||
|
||||
public TorrentService(HttpClient httpClient, ILogger<TorrentService> logger, ApplicationDbContext context)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_context = context;
|
||||
_httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
public async Task<List<TorrentItem>> GetRecentTorrentsAsync(int count)
|
||||
{
|
||||
return await _context.TorrentItems
|
||||
.OrderByDescending(t => t.PublishDate)
|
||||
.Take(count)
|
||||
.ToListAsync();
|
||||
}
|
||||
public async Task<TorrentInfo> ParseTorrentAsync(IFormFile file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
await file.CopyToAsync(stream);
|
||||
stream.Position = 0;
|
||||
|
||||
var parser = new TorrentParser();
|
||||
var torrent = parser.Parse(stream);
|
||||
var infoHash = torrent.GetInfoHashBytes();
|
||||
var announceUrl = torrent.Trackers?.FirstOrDefault()?.FirstOrDefault()?.ToString();
|
||||
|
||||
return new TorrentInfo
|
||||
{
|
||||
FileName = torrent.DisplayName ?? file.FileName,
|
||||
AnnounceUrl = announceUrl,
|
||||
ScrapeUrl = ConvertAnnounceToScrape(announceUrl),
|
||||
InfoHash = UrlEncodeInfoHash(infoHash),
|
||||
InfoHashBytes = infoHash,
|
||||
Size = torrent.TotalSize
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Fel vid parsing av torrent-fil");
|
||||
return new TorrentInfo
|
||||
{
|
||||
FileName = file.FileName,
|
||||
ErrorMessage = $"Kunde inte parsa torrent-filen: {ex.Message}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TorrentInfo> FetchTrackerStatsAsync(TorrentInfo info)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(info.ScrapeUrl))
|
||||
{
|
||||
info.ErrorMessage = "Ingen scrape URL tillgänglig";
|
||||
return info;
|
||||
}
|
||||
|
||||
var url = $"{info.ScrapeUrl}?info_hash={info.InfoHash}";
|
||||
_logger.LogInformation("Scraping tracker: {Url}", url);
|
||||
|
||||
try
|
||||
{
|
||||
var data = await _httpClient.GetByteArrayAsync(url);
|
||||
var parser = new BencodeParser();
|
||||
var bdict = parser.Parse<BDictionary>(data);
|
||||
|
||||
if (bdict.TryGetValue("files", out var filesValue) && filesValue is BDictionary files)
|
||||
{
|
||||
// Använd direkt byte array istället för att konvertera till sträng
|
||||
if (TryGetStatsFromFiles(files, info.InfoHashBytes, info))
|
||||
{
|
||||
info.HasTrackerData = true;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Om det inte fungerar, prova att URL-decode först
|
||||
if (!string.IsNullOrEmpty(info.InfoHash))
|
||||
{
|
||||
try
|
||||
{
|
||||
string decoded = Uri.UnescapeDataString(info.InfoHash);
|
||||
byte[] decodedBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(decoded);
|
||||
|
||||
if (TryGetStatsFromFiles(files, decodedBytes, info))
|
||||
{
|
||||
info.HasTrackerData = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
catch { /* Ignore decode errors */ }
|
||||
}
|
||||
|
||||
info.ErrorMessage = "Info hash hittades inte i tracker-svaret";
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
info.ErrorMessage = $"HTTP fel: {ex.Message}";
|
||||
_logger.LogWarning(ex, "HTTP fel vid tracker scraping");
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
info.ErrorMessage = "Timeout vid anslutning till tracker";
|
||||
_logger.LogWarning("Timeout vid tracker scraping");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
info.ErrorMessage = $"Fel vid parsing: {ex.Message}";
|
||||
_logger.LogError(ex, "Fel vid tracker scraping");
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
private bool ByteArraysEqual(byte[] a, byte[] b)
|
||||
{
|
||||
if (a.Length != b.Length) return false;
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
{
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private bool TryGetStatsFromFiles(BDictionary files, byte[] hashBytes, TorrentInfo info)
|
||||
{
|
||||
// Skapa en BString från byte array
|
||||
var bStringKey = new BString(hashBytes);
|
||||
|
||||
if (files.TryGetValue(bStringKey, out var hashEntry) && hashEntry is BDictionary stats)
|
||||
{
|
||||
info.Seeders = stats.TryGetInt("complete") ?? 0;
|
||||
info.Leechers = stats.TryGetInt("incomplete") ?? 0;
|
||||
info.Completed = stats.TryGetInt("downloaded") ?? 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public async Task<int> GetUnseenTorrentCountAsync(string userId)
|
||||
{
|
||||
// Hämta alla infohashes som användaren redan sett
|
||||
var seenHashes = await _context.UserTorrentSeen
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(x => x.InfoHash)
|
||||
.ToListAsync();
|
||||
|
||||
// Räkna alla torrents som inte finns i seenHashes och som har > 40 seeders
|
||||
var count = await _context.TorrentItems
|
||||
.Where(t => t.InfoHash != null
|
||||
&& !seenHashes.Contains(t.InfoHash)
|
||||
&& t.Seeders > 40)
|
||||
.CountAsync();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private string ConvertAnnounceToScrape(string announceUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(announceUrl))
|
||||
return null;
|
||||
|
||||
return announceUrl.Replace("/announce", "/scrape");
|
||||
}
|
||||
|
||||
private string UrlEncodeInfoHash(byte[] infoHash)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (byte b in infoHash)
|
||||
{
|
||||
sb.AppendFormat("%{0:x2}", b);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class BDictionaryExtensions
|
||||
{
|
||||
public static int? TryGetInt(this BDictionary dict, string key)
|
||||
{
|
||||
return dict.TryGetValue(key, out var value) && value is BNumber num ? (int?)num.Value : null;
|
||||
}
|
||||
}
|
||||
11
Aberwyn/Data/infrastructure/setup.json
Normal file
11
Aberwyn/Data/infrastructure/setup.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"AdminUsername": "admin",
|
||||
"AdminEmail": "admin@localhost",
|
||||
"AdminPassword": "Admin123!",
|
||||
"IsConfigured": true,
|
||||
"DbHost": "192.168.1.108",
|
||||
"DbPort": 3306,
|
||||
"DbName": "lewel_prod",
|
||||
"DbUser": "lewel",
|
||||
"DbPassword": "W542.Hl;)%ta"
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
# Basimage för runtime
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
||||
|
||||
# Installera svenska språkinställningar
|
||||
RUN apt-get update && \
|
||||
apt-get install -y locales && \
|
||||
locale-gen sv_SE.UTF-8
|
||||
|
||||
# Ställ in svenska som standard
|
||||
ENV LANG=sv_SE.UTF-8
|
||||
ENV LANGUAGE=sv_SE:sv
|
||||
ENV LC_ALL=sv_SE.UTF-8
|
||||
@@ -13,18 +16,32 @@ WORKDIR /app
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
VOLUME /app/data
|
||||
|
||||
# Byggimage med SDK
|
||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Kopiera endast .csproj först för att kunna cacha restore
|
||||
COPY ["Aberwyn/Aberwyn.csproj", "Aberwyn/"]
|
||||
WORKDIR /src/Aberwyn
|
||||
|
||||
# Restore beroenden
|
||||
RUN dotnet restore "Aberwyn.csproj"
|
||||
|
||||
# Kopiera övrig kod
|
||||
COPY Aberwyn/. .
|
||||
RUN dotnet build "Aberwyn.csproj" -c Release -o /app/build
|
||||
|
||||
# Bygg utan att köra restore igen
|
||||
RUN dotnet build "Aberwyn.csproj" -c Release -o /app/build --no-restore
|
||||
|
||||
# Publicera utan att köra restore eller build igen
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "Aberwyn.csproj" -c Release -o /app/publish
|
||||
RUN dotnet publish "Aberwyn.csproj" -c Release -o /app/publish --no-restore
|
||||
|
||||
# Slutgiltig image baserad på runtime
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
|
||||
ENTRYPOINT ["dotnet", "Aberwyn.dll"]
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AssignedToNullable : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "AssignedTo",
|
||||
table: "TodoTasks",
|
||||
type: "longtext",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
table: "TodoTasks",
|
||||
keyColumn: "AssignedTo",
|
||||
keyValue: null,
|
||||
column: "AssignedTo",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "AssignedTo",
|
||||
table: "TodoTasks",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddUserPreferencesAndPushSubscriptions : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PushSubscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Endpoint = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
P256DH = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Auth = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PushSubscriptions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PushSubscriptions_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserPreferences",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
NotifyPizza = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
NotifyMenu = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
NotifyBudget = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserPreferences", x => x.UserId);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserPreferences_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PushSubscriptions_UserId",
|
||||
table: "PushSubscriptions",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PushSubscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserPreferences");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddPushSubscriberUserLink : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "UserId",
|
||||
table: "PushSubscribers",
|
||||
type: "varchar(255)",
|
||||
nullable: false,
|
||||
defaultValue: "")
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PushSubscribers_UserId",
|
||||
table: "PushSubscribers",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_PushSubscribers_AspNetUsers_UserId",
|
||||
table: "PushSubscribers",
|
||||
column: "UserId",
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_PushSubscribers_AspNetUsers_UserId",
|
||||
table: "PushSubscribers");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_PushSubscribers_UserId",
|
||||
table: "PushSubscribers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UserId",
|
||||
table: "PushSubscribers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250606100439_AddPushSubscriberUserLink")]
|
||||
partial class AddPushSubscriberUserLink
|
||||
[Migration("20250618203117_AddPaymentStatusToBudgetItem")]
|
||||
partial class AddPaymentStatusToBudgetItem
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -183,6 +183,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BudgetCategoryId");
|
||||
@@ -201,6 +204,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("DefaultCategory")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("DefaultPaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IncludeInSummary")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
@@ -269,9 +275,6 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("CarbType")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@@ -293,6 +296,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<bool>("IsAvailable")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int?>("MealCategoryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@@ -308,9 +314,56 @@ namespace Aberwyn.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MealCategoryId");
|
||||
|
||||
b.ToTable("Meals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("MealCategories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Color = "#f97316",
|
||||
DisplayOrder = 1,
|
||||
Icon = "🍕",
|
||||
IsActive = true,
|
||||
Name = "Pizza",
|
||||
Slug = "pizza"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -358,12 +411,17 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("PizzaOrderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PizzaOrderId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PushSubscribers");
|
||||
@@ -638,7 +696,7 @@ namespace Aberwyn.Migrations
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("BudgetCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -648,8 +706,6 @@ namespace Aberwyn.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("BudgetItemDefinitionId");
|
||||
|
||||
b.Navigation("BudgetCategory");
|
||||
|
||||
b.Navigation("BudgetItemDefinition");
|
||||
});
|
||||
|
||||
@@ -662,14 +718,29 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||
.WithMany("Meals")
|
||||
.HasForeignKey("MealCategoryId");
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||
.WithMany()
|
||||
.HasForeignKey("PizzaOrderId");
|
||||
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PizzaOrder");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
@@ -766,6 +837,11 @@ namespace Aberwyn.Migrations
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Navigation("Meals");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddPaymentStatusToBudgetItem : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PaymentStatus",
|
||||
table: "BudgetItems",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "DefaultPaymentStatus",
|
||||
table: "BudgetItemDefinitions",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PaymentStatus",
|
||||
table: "BudgetItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DefaultPaymentStatus",
|
||||
table: "BudgetItemDefinitions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250606061016_AddUserPreferencesAndPushSubscriptions")]
|
||||
partial class AddUserPreferencesAndPushSubscriptions
|
||||
[Migration("20250624150426_AddIsPublishedToMeals")]
|
||||
partial class AddIsPublishedToMeals
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -183,6 +183,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BudgetCategoryId");
|
||||
@@ -201,6 +204,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("DefaultCategory")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("DefaultPaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IncludeInSummary")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
@@ -269,9 +275,6 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("CarbType")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@@ -293,6 +296,12 @@ namespace Aberwyn.Migrations
|
||||
b.Property<bool>("IsAvailable")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int?>("MealCategoryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@@ -308,9 +317,56 @@ namespace Aberwyn.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MealCategoryId");
|
||||
|
||||
b.ToTable("Meals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("MealCategories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Color = "#f97316",
|
||||
DisplayOrder = 1,
|
||||
Icon = "🍕",
|
||||
IsActive = true,
|
||||
Name = "Pizza",
|
||||
Slug = "pizza"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -358,8 +414,19 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("PizzaOrderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PizzaOrderId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PushSubscribers");
|
||||
});
|
||||
|
||||
@@ -632,7 +699,7 @@ namespace Aberwyn.Migrations
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("BudgetCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -642,8 +709,6 @@ namespace Aberwyn.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("BudgetItemDefinitionId");
|
||||
|
||||
b.Navigation("BudgetCategory");
|
||||
|
||||
b.Navigation("BudgetItemDefinition");
|
||||
});
|
||||
|
||||
@@ -656,6 +721,32 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||
.WithMany("Meals")
|
||||
.HasForeignKey("MealCategoryId");
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||
.WithMany()
|
||||
.HasForeignKey("PizzaOrderId");
|
||||
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PizzaOrder");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
@@ -749,6 +840,11 @@ namespace Aberwyn.Migrations
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Navigation("Meals");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddThumbnailToMeal : Migration
|
||||
public partial class AddIsPublishedToMeals : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<byte[]>(
|
||||
name: "ThumbnailData",
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsPublished",
|
||||
table: "Meals",
|
||||
type: "longblob",
|
||||
nullable: true);
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ThumbnailData",
|
||||
name: "IsPublished",
|
||||
table: "Meals");
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250605062804_AddThumbnailToMeal")]
|
||||
partial class AddThumbnailToMeal
|
||||
[Migration("20250630123620_AddRecipeLab")]
|
||||
partial class AddRecipeLab
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -183,6 +183,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BudgetCategoryId");
|
||||
@@ -201,6 +204,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("DefaultCategory")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("DefaultPaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IncludeInSummary")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
@@ -269,9 +275,6 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("CarbType")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@@ -293,6 +296,12 @@ namespace Aberwyn.Migrations
|
||||
b.Property<bool>("IsAvailable")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int?>("MealCategoryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@@ -308,9 +317,56 @@ namespace Aberwyn.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MealCategoryId");
|
||||
|
||||
b.ToTable("Meals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("MealCategories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Color = "#f97316",
|
||||
DisplayOrder = 1,
|
||||
Icon = "🍕",
|
||||
IsActive = true,
|
||||
Name = "Pizza",
|
||||
Slug = "pizza"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -358,11 +414,124 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("PizzaOrderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PizzaOrderId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PushSubscribers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("BaseMealId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Inspiration")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("Rating")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TestedBy")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BaseMealId");
|
||||
|
||||
b.ToTable("RecipeLabEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Ingredients")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Instructions")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("RecipeLabEntryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ResultNotes")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("VersionLabel")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeLabEntryId");
|
||||
|
||||
b.ToTable("RecipeLabVersions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Auth")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Endpoint")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("P256DH")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PushSubscriptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.TodoTask", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -402,6 +571,25 @@ namespace Aberwyn.Migrations
|
||||
b.ToTable("TodoTasks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<bool>("NotifyBudget")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("NotifyMenu")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("NotifyPizza")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("UserId");
|
||||
|
||||
b.ToTable("UserPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -584,7 +772,7 @@ namespace Aberwyn.Migrations
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("BudgetCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -594,8 +782,6 @@ namespace Aberwyn.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("BudgetItemDefinitionId");
|
||||
|
||||
b.Navigation("BudgetCategory");
|
||||
|
||||
b.Navigation("BudgetItemDefinition");
|
||||
});
|
||||
|
||||
@@ -608,6 +794,74 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||
.WithMany("Meals")
|
||||
.HasForeignKey("MealCategoryId");
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||
.WithMany()
|
||||
.HasForeignKey("PizzaOrderId");
|
||||
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PizzaOrder");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.Meal", "BaseMeal")
|
||||
.WithMany()
|
||||
.HasForeignKey("BaseMealId");
|
||||
|
||||
b.Navigation("BaseMeal");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||
.WithMany("Versions")
|
||||
.HasForeignKey("RecipeLabEntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithOne("Preferences")
|
||||
.HasForeignKey("Aberwyn.Models.UserPreferences", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
@@ -659,6 +913,12 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("Preferences")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
@@ -673,6 +933,16 @@ namespace Aberwyn.Migrations
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Navigation("Meals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.Navigation("Versions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
95
Aberwyn/Migrations/20250630123620_AddRecipeLab.cs
Normal file
95
Aberwyn/Migrations/20250630123620_AddRecipeLab.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddRecipeLab : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RecipeLabEntries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Title = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Category = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Inspiration = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
BaseMealId = table.Column<int>(type: "int", nullable: true),
|
||||
Notes = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Rating = table.Column<int>(type: "int", nullable: true),
|
||||
TestedBy = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Tags = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RecipeLabEntries", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeLabEntries_Meals_BaseMealId",
|
||||
column: x => x.BaseMealId,
|
||||
principalTable: "Meals",
|
||||
principalColumn: "Id");
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RecipeLabVersions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
RecipeLabEntryId = table.Column<int>(type: "int", nullable: false),
|
||||
VersionLabel = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Ingredients = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Instructions = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ResultNotes = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RecipeLabVersions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeLabVersions_RecipeLabEntries_RecipeLabEntryId",
|
||||
column: x => x.RecipeLabEntryId,
|
||||
principalTable: "RecipeLabEntries",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeLabEntries_BaseMealId",
|
||||
table: "RecipeLabEntries",
|
||||
column: "BaseMealId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeLabVersions_RecipeLabEntryId",
|
||||
table: "RecipeLabVersions",
|
||||
column: "RecipeLabEntryId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "RecipeLabVersions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RecipeLabEntries");
|
||||
}
|
||||
}
|
||||
}
|
||||
1026
Aberwyn/Migrations/20250630133813_AddLabIngredientModel.Designer.cs
generated
Normal file
1026
Aberwyn/Migrations/20250630133813_AddLabIngredientModel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
80
Aberwyn/Migrations/20250630133813_AddLabIngredientModel.cs
Normal file
80
Aberwyn/Migrations/20250630133813_AddLabIngredientModel.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddLabIngredientModel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "LabIngredients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
RecipeLabEntryId = table.Column<int>(type: "int", nullable: false),
|
||||
Quantity = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Item = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_LabIngredients", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_LabIngredients_RecipeLabEntries_RecipeLabEntryId",
|
||||
column: x => x.RecipeLabEntryId,
|
||||
principalTable: "RecipeLabEntries",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "LabVersionIngredients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
RecipeLabVersionId = table.Column<int>(type: "int", nullable: false),
|
||||
Quantity = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Item = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_LabVersionIngredients", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_LabVersionIngredients_RecipeLabVersions_RecipeLabVersionId",
|
||||
column: x => x.RecipeLabVersionId,
|
||||
principalTable: "RecipeLabVersions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LabIngredients_RecipeLabEntryId",
|
||||
table: "LabIngredients",
|
||||
column: "RecipeLabEntryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LabVersionIngredients_RecipeLabVersionId",
|
||||
table: "LabVersionIngredients",
|
||||
column: "RecipeLabVersionId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "LabIngredients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "LabVersionIngredients");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250604220420_AssignedToNullable")]
|
||||
partial class AssignedToNullable
|
||||
[Migration("20250630141059_AddRecipeLabModels")]
|
||||
partial class AddRecipeLabModels
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -183,6 +183,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BudgetCategoryId");
|
||||
@@ -201,6 +204,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("DefaultCategory")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("DefaultPaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IncludeInSummary")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
@@ -260,6 +266,54 @@ namespace Aberwyn.Migrations
|
||||
b.ToTable("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Item")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Quantity")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("RecipeLabEntryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeLabEntryId");
|
||||
|
||||
b.ToTable("LabIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Item")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Quantity")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("RecipeLabVersionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeLabVersionId");
|
||||
|
||||
b.ToTable("LabVersionIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -269,9 +323,6 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("CarbType")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@@ -293,6 +344,12 @@ namespace Aberwyn.Migrations
|
||||
b.Property<bool>("IsAvailable")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int?>("MealCategoryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@@ -303,11 +360,61 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("RecipeUrl")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<byte[]>("ThumbnailData")
|
||||
.HasColumnType("longblob");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MealCategoryId");
|
||||
|
||||
b.ToTable("Meals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("MealCategories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Color = "#f97316",
|
||||
DisplayOrder = 1,
|
||||
Icon = "🍕",
|
||||
IsActive = true,
|
||||
Name = "Pizza",
|
||||
Slug = "pizza"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -355,11 +462,121 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("PizzaOrderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PizzaOrderId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PushSubscribers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("BaseMealId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Inspiration")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("Rating")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TestedBy")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BaseMealId");
|
||||
|
||||
b.ToTable("RecipeLabEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Instructions")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("RecipeLabEntryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ResultNotes")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("VersionLabel")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeLabEntryId");
|
||||
|
||||
b.ToTable("RecipeLabVersions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Auth")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Endpoint")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("P256DH")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PushSubscriptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.TodoTask", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -399,6 +616,25 @@ namespace Aberwyn.Migrations
|
||||
b.ToTable("TodoTasks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<bool>("NotifyBudget")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("NotifyMenu")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("NotifyPizza")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("UserId");
|
||||
|
||||
b.ToTable("UserPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -581,7 +817,7 @@ namespace Aberwyn.Migrations
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("BudgetCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -591,8 +827,6 @@ namespace Aberwyn.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("BudgetItemDefinitionId");
|
||||
|
||||
b.Navigation("BudgetCategory");
|
||||
|
||||
b.Navigation("BudgetItemDefinition");
|
||||
});
|
||||
|
||||
@@ -605,6 +839,96 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||
.WithMany("Ingredients")
|
||||
.HasForeignKey("RecipeLabEntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.RecipeLabVersion", "Version")
|
||||
.WithMany("Ingredients")
|
||||
.HasForeignKey("RecipeLabVersionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Version");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||
.WithMany("Meals")
|
||||
.HasForeignKey("MealCategoryId");
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||
.WithMany()
|
||||
.HasForeignKey("PizzaOrderId");
|
||||
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PizzaOrder");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.Meal", "BaseMeal")
|
||||
.WithMany()
|
||||
.HasForeignKey("BaseMealId");
|
||||
|
||||
b.Navigation("BaseMeal");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||
.WithMany("Versions")
|
||||
.HasForeignKey("RecipeLabEntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithOne("Preferences")
|
||||
.HasForeignKey("Aberwyn.Models.UserPreferences", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
@@ -656,6 +980,12 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("Preferences")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
@@ -670,6 +1000,23 @@ namespace Aberwyn.Migrations
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Navigation("Meals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
|
||||
b.Navigation("Versions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
26
Aberwyn/Migrations/20250630141059_AddRecipeLabModels.cs
Normal file
26
Aberwyn/Migrations/20250630141059_AddRecipeLabModels.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddRecipeLabModels : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Ingredients",
|
||||
table: "RecipeLabVersions");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Ingredients",
|
||||
table: "RecipeLabVersions",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
1060
Aberwyn/Migrations/20250707125951_AddMealRating.Designer.cs
generated
Normal file
1060
Aberwyn/Migrations/20250707125951_AddMealRating.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
Aberwyn/Migrations/20250707125951_AddMealRating.cs
Normal file
49
Aberwyn/Migrations/20250707125951_AddMealRating.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddMealRating : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MealRatings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
MealId = table.Column<int>(type: "int", nullable: false),
|
||||
UserId = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Rating = table.Column<int>(type: "int", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MealRatings", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MealRatings_Meals_MealId",
|
||||
column: x => x.MealId,
|
||||
principalTable: "Meals",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MealRatings_MealId",
|
||||
table: "MealRatings",
|
||||
column: "MealId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "MealRatings");
|
||||
}
|
||||
}
|
||||
}
|
||||
1060
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.Designer.cs
generated
Normal file
1060
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.cs
Normal file
19
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddNameToBudgetPeriod : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
1063
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.Designer.cs
generated
Normal file
1063
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.cs
Normal file
26
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddNameToBudgetPeriod2 : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Name",
|
||||
table: "BudgetPeriods",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Name",
|
||||
table: "BudgetPeriods");
|
||||
}
|
||||
}
|
||||
}
|
||||
1063
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.Designer.cs
generated
Normal file
1063
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.cs
Normal file
51
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class MakeYearMonthNullable : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Year",
|
||||
table: "BudgetPeriods",
|
||||
type: "int",
|
||||
nullable: true,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Month",
|
||||
table: "BudgetPeriods",
|
||||
type: "int",
|
||||
nullable: true,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Year",
|
||||
table: "BudgetPeriods",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Month",
|
||||
table: "BudgetPeriods",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
1177
Aberwyn/Migrations/20250805202224_AddTorrentTables.Designer.cs
generated
Normal file
1177
Aberwyn/Migrations/20250805202224_AddTorrentTables.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
105
Aberwyn/Migrations/20250805202224_AddTorrentTables.cs
Normal file
105
Aberwyn/Migrations/20250805202224_AddTorrentTables.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddTorrentTables : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DownloadRules",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
KeywordFilter = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CategoryFilter = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
MinSeeders = table.Column<int>(type: "int", nullable: false),
|
||||
MaxSize = table.Column<long>(type: "bigint", nullable: false),
|
||||
AutoDownload = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DownloadRules", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RssFeeds",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Url = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
LastChecked = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RssFeeds", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TorrentItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Title = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
MagnetLink = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
InfoHash = table.Column<string>(type: "varchar(255)", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
PublishDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Size = table.Column<long>(type: "bigint", nullable: false),
|
||||
Seeders = table.Column<int>(type: "int", nullable: false),
|
||||
Leechers = table.Column<int>(type: "int", nullable: false),
|
||||
Status = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Category = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
RssSource = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
TorrentUrl = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TorrentItems", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TorrentItems_InfoHash",
|
||||
table: "TorrentItems",
|
||||
column: "InfoHash",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DownloadRules");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RssFeeds");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TorrentItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
1195
Aberwyn/Migrations/20250806065232_AddTorrentTablesv2.Designer.cs
generated
Normal file
1195
Aberwyn/Migrations/20250806065232_AddTorrentTablesv2.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
69
Aberwyn/Migrations/20250806065232_AddTorrentTablesv2.cs
Normal file
69
Aberwyn/Migrations/20250806065232_AddTorrentTablesv2.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddTorrentTablesv2 : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Completed",
|
||||
table: "TorrentItems",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DownloadKey",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "MovieName",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Token",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Year",
|
||||
table: "TorrentItems",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Completed",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DownloadKey",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MovieName",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Token",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Year",
|
||||
table: "TorrentItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
1188
Aberwyn/Migrations/20250806072233_MakeTorrentFieldsNullable.Designer.cs
generated
Normal file
1188
Aberwyn/Migrations/20250806072233_MakeTorrentFieldsNullable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
211
Aberwyn/Migrations/20250806072233_MakeTorrentFieldsNullable.cs
Normal file
211
Aberwyn/Migrations/20250806072233_MakeTorrentFieldsNullable.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class MakeTorrentFieldsNullable : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "TorrentUrl",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Token",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MovieName",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MagnetLink",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "InfoHash",
|
||||
table: "TorrentItems",
|
||||
type: "varchar(255)",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(255)")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DownloadKey",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Category",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
table: "TorrentItems",
|
||||
keyColumn: "TorrentUrl",
|
||||
keyValue: null,
|
||||
column: "TorrentUrl",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "TorrentUrl",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "TorrentItems",
|
||||
keyColumn: "Token",
|
||||
keyValue: null,
|
||||
column: "Token",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Token",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "TorrentItems",
|
||||
keyColumn: "MovieName",
|
||||
keyValue: null,
|
||||
column: "MovieName",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MovieName",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "TorrentItems",
|
||||
keyColumn: "MagnetLink",
|
||||
keyValue: null,
|
||||
column: "MagnetLink",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MagnetLink",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "TorrentItems",
|
||||
keyColumn: "InfoHash",
|
||||
keyValue: null,
|
||||
column: "InfoHash",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "InfoHash",
|
||||
table: "TorrentItems",
|
||||
type: "varchar(255)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(255)",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "TorrentItems",
|
||||
keyColumn: "DownloadKey",
|
||||
keyValue: null,
|
||||
column: "DownloadKey",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DownloadKey",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "TorrentItems",
|
||||
keyColumn: "Category",
|
||||
keyValue: null,
|
||||
column: "Category",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Category",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
1237
Aberwyn/Migrations/20250819221425_AddMovieMetadataToTorrentItem.Designer.cs
generated
Normal file
1237
Aberwyn/Migrations/20250819221425_AddMovieMetadataToTorrentItem.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,125 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddMovieMetadataToTorrentItem : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Actors",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Director",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Genre",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_ImdbID",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_ImdbRating",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Plot",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Poster",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Runtime",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Title",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Year",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Actors",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Director",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Genre",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_ImdbID",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_ImdbRating",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Plot",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Poster",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Runtime",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Title",
|
||||
table: "TorrentItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Year",
|
||||
table: "TorrentItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
1240
Aberwyn/Migrations/20250820133435_AddProvidersToMovieMetadata.Designer.cs
generated
Normal file
1240
Aberwyn/Migrations/20250820133435_AddProvidersToMovieMetadata.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddProvidersToMovieMetadata : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata_Providers",
|
||||
table: "TorrentItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata_Providers",
|
||||
table: "TorrentItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
1261
Aberwyn/Migrations/20250824172213_AddUserTorrentSeen.Designer.cs
generated
Normal file
1261
Aberwyn/Migrations/20250824172213_AddUserTorrentSeen.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
Aberwyn/Migrations/20250824172213_AddUserTorrentSeen.cs
Normal file
37
Aberwyn/Migrations/20250824172213_AddUserTorrentSeen.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddUserTorrentSeen : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserTorrentSeen",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
UserId = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
TorrentId = table.Column<int>(type: "int", nullable: false),
|
||||
SeenDate = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserTorrentSeen", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserTorrentSeen");
|
||||
}
|
||||
}
|
||||
}
|
||||
1262
Aberwyn/Migrations/20250824172938_AddUserTorrentSeenv2.Designer.cs
generated
Normal file
1262
Aberwyn/Migrations/20250824172938_AddUserTorrentSeenv2.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
Aberwyn/Migrations/20250824172938_AddUserTorrentSeenv2.cs
Normal file
37
Aberwyn/Migrations/20250824172938_AddUserTorrentSeenv2.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddUserTorrentSeenv2 : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TorrentId",
|
||||
table: "UserTorrentSeen");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "InfoHash",
|
||||
table: "UserTorrentSeen",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InfoHash",
|
||||
table: "UserTorrentSeen");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "TorrentId",
|
||||
table: "UserTorrentSeen",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
1304
Aberwyn/Migrations/20250903130637_AddDoughPlans.Designer.cs
generated
Normal file
1304
Aberwyn/Migrations/20250903130637_AddDoughPlans.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
Aberwyn/Migrations/20250903130637_AddDoughPlans.cs
Normal file
44
Aberwyn/Migrations/20250903130637_AddDoughPlans.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddDoughPlans : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DoughPlans",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
AntalPizzor = table.Column<int>(type: "int", nullable: false),
|
||||
ViktPerPizza = table.Column<double>(type: "double", nullable: false),
|
||||
Mjol = table.Column<double>(type: "double", nullable: false),
|
||||
Vatten = table.Column<double>(type: "double", nullable: false),
|
||||
Olja = table.Column<double>(type: "double", nullable: false),
|
||||
Salt = table.Column<double>(type: "double", nullable: false),
|
||||
Jast = table.Column<double>(type: "double", nullable: false),
|
||||
TotalDeg = table.Column<double>(type: "double", nullable: false),
|
||||
Datum = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Namn = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DoughPlans", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DoughPlans");
|
||||
}
|
||||
}
|
||||
}
|
||||
1349
Aberwyn/Migrations/20250921133610_AddMealWish.Designer.cs
generated
Normal file
1349
Aberwyn/Migrations/20250921133610_AddMealWish.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
Aberwyn/Migrations/20250921133610_AddMealWish.cs
Normal file
53
Aberwyn/Migrations/20250921133610_AddMealWish.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddMealWish : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MealWishes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Recipe = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
LinkedMealId = table.Column<int>(type: "int", nullable: true),
|
||||
RequestedByUserId = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsArchived = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
IsImported = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MealWishes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MealWishes_Meals_LinkedMealId",
|
||||
column: x => x.LinkedMealId,
|
||||
principalTable: "Meals",
|
||||
principalColumn: "Id");
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MealWishes_LinkedMealId",
|
||||
table: "MealWishes",
|
||||
column: "LinkedMealId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "MealWishes");
|
||||
}
|
||||
}
|
||||
}
|
||||
1359
Aberwyn/Migrations/20250921161413_AddMealWishUserRelation.Designer.cs
generated
Normal file
1359
Aberwyn/Migrations/20250921161413_AddMealWishUserRelation.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
56
Aberwyn/Migrations/20250921161413_AddMealWishUserRelation.cs
Normal file
56
Aberwyn/Migrations/20250921161413_AddMealWishUserRelation.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddMealWishUserRelation : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "RequestedByUserId",
|
||||
table: "MealWishes",
|
||||
type: "varchar(255)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MealWishes_RequestedByUserId",
|
||||
table: "MealWishes",
|
||||
column: "RequestedByUserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_MealWishes_AspNetUsers_RequestedByUserId",
|
||||
table: "MealWishes",
|
||||
column: "RequestedByUserId",
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_MealWishes_AspNetUsers_RequestedByUserId",
|
||||
table: "MealWishes");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MealWishes_RequestedByUserId",
|
||||
table: "MealWishes");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "RequestedByUserId",
|
||||
table: "MealWishes",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(255)")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
1362
Aberwyn/Migrations/20250921170530_AddDateToWeeklyMenu.Designer.cs
generated
Normal file
1362
Aberwyn/Migrations/20250921170530_AddDateToWeeklyMenu.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
Aberwyn/Migrations/20250921170530_AddDateToWeeklyMenu.cs
Normal file
27
Aberwyn/Migrations/20250921170530_AddDateToWeeklyMenu.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddDateToWeeklyMenu : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "Date",
|
||||
table: "WeeklyMenu",
|
||||
type: "datetime(6)",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Date",
|
||||
table: "WeeklyMenu");
|
||||
}
|
||||
}
|
||||
}
|
||||
1365
Aberwyn/Migrations/20251027205014_AddTorrentItemTable.Designer.cs
generated
Normal file
1365
Aberwyn/Migrations/20251027205014_AddTorrentItemTable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Aberwyn/Migrations/20251027205014_AddTorrentItemTable.cs
Normal file
26
Aberwyn/Migrations/20251027205014_AddTorrentItemTable.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddTorrentItemTable : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsDownloaded",
|
||||
table: "TorrentItems",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsDownloaded",
|
||||
table: "TorrentItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,6 +181,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BudgetCategoryId");
|
||||
@@ -199,6 +202,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("DefaultCategory")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("DefaultPaymentStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IncludeInSummary")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
@@ -220,13 +226,16 @@ namespace Aberwyn.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Month")
|
||||
b.Property<int?>("Month")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Year")
|
||||
b.Property<int?>("Year")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
@@ -234,6 +243,48 @@ namespace Aberwyn.Migrations
|
||||
b.ToTable("BudgetPeriods");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.DoughPlan", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("AntalPizzor")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("Datum")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<double>("Jast")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<double>("Mjol")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<string>("Namn")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<double>("Olja")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<double>("Salt")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<double>("TotalDeg")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<double>("Vatten")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<double>("ViktPerPizza")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DoughPlans");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -258,6 +309,54 @@ namespace Aberwyn.Migrations
|
||||
b.ToTable("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Item")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Quantity")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("RecipeLabEntryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeLabEntryId");
|
||||
|
||||
b.ToTable("LabIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Item")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Quantity")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("RecipeLabVersionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeLabVersionId");
|
||||
|
||||
b.ToTable("LabVersionIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -267,9 +366,6 @@ namespace Aberwyn.Migrations
|
||||
b.Property<string>("CarbType")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@@ -291,6 +387,12 @@ namespace Aberwyn.Migrations
|
||||
b.Property<bool>("IsAvailable")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int?>("MealCategoryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@@ -306,9 +408,120 @@ namespace Aberwyn.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MealCategoryId");
|
||||
|
||||
b.ToTable("Meals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("MealCategories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Color = "#f97316",
|
||||
DisplayOrder = 1,
|
||||
Icon = "🍕",
|
||||
IsActive = true,
|
||||
Name = "Pizza",
|
||||
Slug = "pizza"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealRating", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("MealId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MealId");
|
||||
|
||||
b.ToTable("MealRatings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealWish", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsArchived")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsImported")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int?>("LinkedMealId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Recipe")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("RequestedByUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LinkedMealId");
|
||||
|
||||
b.HasIndex("RequestedByUserId");
|
||||
|
||||
b.ToTable("MealWishes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -356,17 +569,92 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("PizzaOrderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PizzaOrderId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PushSubscribers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("BaseMealId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Inspiration")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("Rating")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TestedBy")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BaseMealId");
|
||||
|
||||
b.ToTable("RecipeLabEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Instructions")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("RecipeLabEntryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ResultNotes")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("VersionLabel")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeLabEntryId");
|
||||
|
||||
b.ToTable("RecipeLabVersions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -469,6 +757,9 @@ namespace Aberwyn.Migrations
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("DayOfWeek")
|
||||
.HasColumnType("int");
|
||||
|
||||
@@ -489,6 +780,34 @@ namespace Aberwyn.Migrations
|
||||
b.ToTable("WeeklyMenu", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadRule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("AutoDownload")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("CategoryFilter")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("KeywordFilter")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<long>("MaxSize")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("MinSeeders")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DownloadRules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@@ -617,6 +936,128 @@ namespace Aberwyn.Migrations
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RssFeed", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("LastChecked")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RssFeeds");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TorrentItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Completed")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("DownloadKey")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("InfoHash")
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<bool>("IsDownloaded")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("Leechers")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("MagnetLink")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("MovieName")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("PublishDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("RssSource")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Seeders")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TorrentUrl")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("Year")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InfoHash")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("TorrentItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UserTorrentSeen", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("InfoHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("SeenDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("UserTorrentSeen");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.BudgetCategoryDefinition", "Definition")
|
||||
@@ -636,7 +1077,7 @@ namespace Aberwyn.Migrations
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
||||
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("BudgetCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -646,8 +1087,6 @@ namespace Aberwyn.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("BudgetItemDefinitionId");
|
||||
|
||||
b.Navigation("BudgetCategory");
|
||||
|
||||
b.Navigation("BudgetItemDefinition");
|
||||
});
|
||||
|
||||
@@ -660,17 +1099,102 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||
.WithMany("Ingredients")
|
||||
.HasForeignKey("RecipeLabEntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.RecipeLabVersion", "Version")
|
||||
.WithMany("Ingredients")
|
||||
.HasForeignKey("RecipeLabVersionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Version");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||
.WithMany("Meals")
|
||||
.HasForeignKey("MealCategoryId");
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealRating", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.Meal", "Meal")
|
||||
.WithMany()
|
||||
.HasForeignKey("MealId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Meal");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealWish", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.Meal", "LinkedMeal")
|
||||
.WithMany()
|
||||
.HasForeignKey("LinkedMealId");
|
||||
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "RequestedByUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedByUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LinkedMeal");
|
||||
|
||||
b.Navigation("RequestedByUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||
.WithMany()
|
||||
.HasForeignKey("PizzaOrderId");
|
||||
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PizzaOrder");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.Meal", "BaseMeal")
|
||||
.WithMany()
|
||||
.HasForeignKey("BaseMealId");
|
||||
|
||||
b.Navigation("BaseMeal");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||
.WithMany("Versions")
|
||||
.HasForeignKey("RecipeLabEntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||
{
|
||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||
@@ -744,6 +1268,58 @@ namespace Aberwyn.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TorrentItem", b =>
|
||||
{
|
||||
b.OwnsOne("MovieMetadata", "Metadata", b1 =>
|
||||
{
|
||||
b1.Property<int>("TorrentItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b1.Property<string>("Actors")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Director")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Genre")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("ImdbID")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("ImdbRating")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Plot")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Poster")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Providers")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Runtime")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.Property<string>("Year")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b1.HasKey("TorrentItemId");
|
||||
|
||||
b1.ToTable("TorrentItems");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("TorrentItemId");
|
||||
});
|
||||
|
||||
b.Navigation("Metadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("Preferences")
|
||||
@@ -764,6 +1340,23 @@ namespace Aberwyn.Migrations
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||
{
|
||||
b.Navigation("Meals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
|
||||
b.Navigation("Versions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||
{
|
||||
b.Navigation("Ingredients");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ namespace Aberwyn.Models
|
||||
public class BudgetPeriod
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Year { get; set; }
|
||||
public int Month { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public int? Month { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
public List<BudgetCategory> Categories { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -47,14 +49,24 @@ namespace Aberwyn.Models
|
||||
|
||||
[JsonIgnore]
|
||||
[ValidateNever]
|
||||
public BudgetCategory BudgetCategory { get; set; }
|
||||
|
||||
|
||||
public PaymentStatus PaymentStatus { get; set; } = PaymentStatus.None;
|
||||
|
||||
}
|
||||
public enum PaymentStatus
|
||||
{
|
||||
None = 0,
|
||||
Due = 1,
|
||||
Paid = 2
|
||||
}
|
||||
// DTOs/BudgetDto.cs
|
||||
public class BudgetDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Year { get; set; }
|
||||
public int Month { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public int? Month { get; set; }
|
||||
public int Order { get; set; }
|
||||
|
||||
public List<BudgetCategoryDto> Categories { get; set; } = new();
|
||||
@@ -69,7 +81,7 @@ namespace Aberwyn.Models
|
||||
public int Order { get; set; }
|
||||
public int? BudgetCategoryDefinitionId { get; set; }
|
||||
|
||||
|
||||
public int? BudgetPeriodId { get; set; }
|
||||
public int Year { get; set; }
|
||||
public int Month { get; set; }
|
||||
}
|
||||
@@ -84,6 +96,7 @@ namespace Aberwyn.Models
|
||||
|
||||
public bool IsExpense { get; set; }
|
||||
public bool IncludeInSummary { get; set; }
|
||||
public PaymentStatus PaymentStatus { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +104,11 @@ namespace Aberwyn.Models
|
||||
{
|
||||
public List<BudgetItem> BudgetItems { get; set; } = new List<BudgetItem>();
|
||||
}
|
||||
public class PaymentStatusUpdateDto
|
||||
{
|
||||
public int ItemId { get; set; }
|
||||
public PaymentStatus Status { get; set; }
|
||||
}
|
||||
|
||||
public class BudgetItemDefinition
|
||||
{
|
||||
@@ -99,6 +117,8 @@ namespace Aberwyn.Models
|
||||
public string? DefaultCategory { get; set; } // valfritt, kan föreslå "Bilar"
|
||||
public bool IsExpense { get; set; }
|
||||
public bool IncludeInSummary { get; set; }
|
||||
public PaymentStatus? DefaultPaymentStatus { get; set; }
|
||||
|
||||
}
|
||||
public class BudgetCategoryDefinition
|
||||
{
|
||||
|
||||
33
Aberwyn/Models/MediaModels.cs
Normal file
33
Aberwyn/Models/MediaModels.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Aberwyn.Models
|
||||
{
|
||||
public class StreamingService
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } // T.ex. "Netflix", "Plex"
|
||||
}
|
||||
|
||||
public class MediaItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string TmdbId { get; set; } // TMDb ID
|
||||
public string Title { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string PosterPath { get; set; }
|
||||
public string MediaType { get; set; } // "movie" eller "tv"
|
||||
public double Rating { get; set; }
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
public List<MediaItemStreamingService> MediaItemStreamingServices { get; set; } = new();
|
||||
}
|
||||
|
||||
public class MediaItemStreamingService
|
||||
{
|
||||
public int MediaItemId { get; set; }
|
||||
public MediaItem MediaItem { get; set; }
|
||||
|
||||
public int StreamingServiceId { get; set; }
|
||||
public StreamingService StreamingService { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace Aberwyn.Models
|
||||
public List<WeeklyMenu> WeeklyMenus { get; set; } // List of weekly menu entries
|
||||
public int WeekNumber { get; set; } // Week number for the menu
|
||||
public int Year { get; set; } // Year for the menu
|
||||
|
||||
}
|
||||
public class WeeklyMenuDto
|
||||
{
|
||||
@@ -40,9 +41,13 @@ public class WeeklyMenu
|
||||
public int? LunchMealId { get; set; }
|
||||
public int? DinnerMealId { get; set; }
|
||||
public string? Cook { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public int WeekNumber { get; set; }
|
||||
public int Year { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
[NotMapped] public byte[]? BreakfastThumbnail { get; set; }
|
||||
[NotMapped] public byte[]? LunchThumbnail { get; set; }
|
||||
[NotMapped] public byte[]? DinnerThumbnail { get; set; }
|
||||
|
||||
[NotMapped] public string? BreakfastMealName { get; set; }
|
||||
[NotMapped] public string? LunchMealName { get; set; }
|
||||
@@ -56,6 +61,8 @@ public class WeeklyMenu
|
||||
public class RecentMenuEntry
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public int WeekNumber { get; set; } // Lägg till vecka
|
||||
public int Year { get; set; } // Lägg till år
|
||||
public string BreakfastMealName { get; set; }
|
||||
public string LunchMealName { get; set; }
|
||||
public string DinnerMealName { get; set; }
|
||||
@@ -70,7 +77,10 @@ public class WeeklyMenu
|
||||
|
||||
public string? Description { get; set; }
|
||||
public string? ProteinType { get; set; }
|
||||
public string? Category { get; set; }
|
||||
public int? MealCategoryId { get; set; }
|
||||
|
||||
[ForeignKey("MealCategoryId")]
|
||||
public MealCategory? Category { get; set; }
|
||||
public string? CarbType { get; set; }
|
||||
public string? RecipeUrl { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
@@ -78,7 +88,7 @@ public class WeeklyMenu
|
||||
public bool IsAvailable { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public byte[]? ThumbnailData { get; set; }
|
||||
|
||||
public bool IsPublished { get; set; } = false;
|
||||
public byte[]? ImageData { get; set; }
|
||||
public string? ImageMimeType { get; set; }
|
||||
public string? Instructions { get; set; }
|
||||
@@ -125,6 +135,8 @@ public class WeeklyMenu
|
||||
public string? Description { get; set; }
|
||||
public string? ThumbnailData { get; set; }
|
||||
|
||||
public List<IngredientDto> Ingredients { get; set; } = new();
|
||||
|
||||
public static MealListDto FromMeal(Meal meal, bool includeThumbnail = false)
|
||||
{
|
||||
return new MealListDto
|
||||
@@ -134,9 +146,102 @@ public class WeeklyMenu
|
||||
Description = meal.Description,
|
||||
ThumbnailData = includeThumbnail && meal.ThumbnailData != null
|
||||
? Convert.ToBase64String(meal.ThumbnailData)
|
||||
: null
|
||||
: null,
|
||||
Ingredients = meal.Ingredients?.Select(i => new IngredientDto
|
||||
{
|
||||
Item = i.Item
|
||||
}).ToList() ?? new List<IngredientDto>()
|
||||
};
|
||||
}
|
||||
|
||||
public class IngredientDto
|
||||
{
|
||||
public string Item { get; set; } = "";
|
||||
}
|
||||
}
|
||||
|
||||
public class MealCategory
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
public string? Slug { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public string? Color { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public int DisplayOrder { get; set; } = 0;
|
||||
[NotMapped] // krävs om du inte har kolumnen i databasen
|
||||
public int MealCount { get; set; }
|
||||
|
||||
public List<Meal> Meals { get; set; } = new();
|
||||
}
|
||||
public class MealWish
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
public string? Recipe { get; set; }
|
||||
public int? LinkedMealId { get; set; }
|
||||
[ForeignKey("LinkedMealId")]
|
||||
public Meal? LinkedMeal { get; set; }
|
||||
[Required]
|
||||
public string RequestedByUserId { get; set; }
|
||||
[ForeignKey(nameof(RequestedByUserId))]
|
||||
public ApplicationUser? RequestedByUser { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public bool IsArchived { get; set; } = false;
|
||||
public bool IsImported { get; set; } = false;
|
||||
}
|
||||
|
||||
|
||||
public class CreateMealWishDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? Recipe { get; set; }
|
||||
}
|
||||
public class MealWishDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public string? Recipe { get; set; }
|
||||
public bool IsArchived { get; set; }
|
||||
public bool IsImported { get; set; }
|
||||
|
||||
public static MealWishDto FromEntity(MealWish wish)
|
||||
{
|
||||
return new MealWishDto
|
||||
{
|
||||
Id = wish.Id,
|
||||
Name = wish.Name,
|
||||
Recipe = wish.Recipe,
|
||||
IsArchived = wish.IsArchived,
|
||||
IsImported = wish.IsImported
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class MealRating
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int MealId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public int Rating { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public Meal Meal { get; set; }
|
||||
}
|
||||
public class MealRatingDto
|
||||
{
|
||||
public int MealId { get; set; }
|
||||
public int Rating { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -26,5 +26,22 @@ namespace Aberwyn.Models
|
||||
public bool RestaurantIsOpen { get; set; }
|
||||
}
|
||||
|
||||
public class DoughPlan
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int AntalPizzor { get; set; }
|
||||
public double ViktPerPizza { get; set; }
|
||||
|
||||
public double Mjol { get; set; }
|
||||
public double Vatten { get; set; }
|
||||
public double Olja { get; set; }
|
||||
public double Salt { get; set; }
|
||||
public double Jast { get; set; }
|
||||
public double TotalDeg { get; set; }
|
||||
|
||||
public DateTime Datum { get; set; }
|
||||
public string Namn { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
public string Auth { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public virtual ApplicationUser User { get; set; }
|
||||
public int? PizzaOrderId { get; set; }
|
||||
public virtual PizzaOrder PizzaOrder { get; set; }
|
||||
|
||||
}
|
||||
public class PushMessageDto
|
||||
{
|
||||
|
||||
81
Aberwyn/Models/RecipeLab.cs
Normal file
81
Aberwyn/Models/RecipeLab.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Aberwyn.Models
|
||||
{
|
||||
public class RecipeLabEntry
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Title { get; set; }
|
||||
|
||||
public string? Category { get; set; }
|
||||
public string? Inspiration { get; set; }
|
||||
|
||||
public int? BaseMealId { get; set; }
|
||||
|
||||
[ForeignKey("BaseMealId")]
|
||||
public Meal? BaseMeal { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
public int? Rating { get; set; }
|
||||
public string? TestedBy { get; set; }
|
||||
public string? Tags { get; set; } // "vego,snabbt"
|
||||
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||
public List<LabIngredient> Ingredients { get; set; } = new();
|
||||
|
||||
|
||||
public List<RecipeLabVersion> Versions { get; set; } = new();
|
||||
}
|
||||
public class RecipeLabVersion
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int RecipeLabEntryId { get; set; }
|
||||
|
||||
[ForeignKey("RecipeLabEntryId")]
|
||||
public RecipeLabEntry Entry { get; set; }
|
||||
|
||||
public string VersionLabel { get; set; } = "v1";
|
||||
|
||||
public List<LabVersionIngredient> Ingredients { get; set; } = new();
|
||||
|
||||
public string? Instructions { get; set; }
|
||||
public string? ResultNotes { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
|
||||
public class LabIngredient
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int RecipeLabEntryId { get; set; }
|
||||
|
||||
[ForeignKey("RecipeLabEntryId")]
|
||||
public RecipeLabEntry Entry { get; set; }
|
||||
|
||||
public string Quantity { get; set; } = "";
|
||||
public string Item { get; set; } = "";
|
||||
}
|
||||
public class LabVersionIngredient
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int RecipeLabVersionId { get; set; }
|
||||
|
||||
[ForeignKey("RecipeLabVersionId")]
|
||||
public RecipeLabVersion Version { get; set; }
|
||||
|
||||
public string Quantity { get; set; } = "";
|
||||
public string Item { get; set; } = "";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,25 +1,13 @@
|
||||
namespace Aberwyn.Models
|
||||
{
|
||||
public class BudgetReportRequestDto
|
||||
public class BudgetReportViewModel
|
||||
{
|
||||
public List<int> DefinitionIds { get; set; } = new();
|
||||
public int StartYear { get; set; }
|
||||
public int StartMonth { get; set; }
|
||||
public int EndYear { get; set; }
|
||||
public int EndMonth { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public string GroupBy { get; set; } = "month";
|
||||
public string? ItemLabel { get; set; }
|
||||
public string? CategoryLabel { get; set; }
|
||||
public List<int> ItemDefinitionIds { get; set; } = new();
|
||||
public List<int> CategoryDefinitionIds { get; set; } = new();
|
||||
}
|
||||
|
||||
public class BudgetReportResultDto
|
||||
{
|
||||
public int Year { get; set; }
|
||||
public int Month { get; set; }
|
||||
public List<DefinitionSumDto> Definitions { get; set; } = new();
|
||||
}
|
||||
|
||||
public class DefinitionSumDto
|
||||
{
|
||||
public int DefinitionId { get; set; }
|
||||
public string DefinitionName { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
184
Aberwyn/Models/TorrentInfo.cs
Normal file
184
Aberwyn/Models/TorrentInfo.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class TorrentInfo
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public string AnnounceUrl { get; set; }
|
||||
public string ScrapeUrl { get; set; }
|
||||
public string InfoHash { get; set; }
|
||||
public byte[] InfoHashBytes { get; set; }
|
||||
public long Size { get; set; }
|
||||
public int Seeders { get; set; } = 0;
|
||||
public int Leechers { get; set; } = 0;
|
||||
public int Completed { get; set; } = 0;
|
||||
public bool HasTrackerData { get; set; } = false;
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
}
|
||||
public class TrackerInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool SupportsScraping { get; set; }
|
||||
public bool RequiresAuth { get; set; }
|
||||
public bool IsPrivate { get; set; }
|
||||
public string Notes { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentUploadViewModel
|
||||
{
|
||||
public IFormFile TorrentFile { get; set; }
|
||||
public TorrentInfo TorrentInfo { get; set; }
|
||||
public bool ShowResults { get; set; } = false;
|
||||
}
|
||||
|
||||
public class RssFeed
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DateTime LastChecked { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
public class TorrentItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public string? MagnetLink { get; set; }
|
||||
public string? InfoHash { get; set; }
|
||||
public DateTime PublishDate { get; set; }
|
||||
public long Size { get; set; } = 0;
|
||||
public int Seeders { get; set; } = 0;
|
||||
public int Leechers { get; set; } = 0;
|
||||
public int Completed { get; set; } = 0;
|
||||
public TorrentStatus Status { get; set; } = TorrentStatus.New;
|
||||
public string? Category { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[Required]
|
||||
public string RssSource { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string? TorrentUrl { get; set; }
|
||||
public string? MovieName { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public string? DownloadKey { get; set; }
|
||||
public string? Token { get; set; }
|
||||
|
||||
public MovieMetadata? Metadata { get; set; }
|
||||
public bool IsDownloaded { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class MovieMetadata
|
||||
{
|
||||
public string Title { get; set; } = string.Empty; // required
|
||||
public string? Year { get; set; }
|
||||
public string? Poster { get; set; }
|
||||
public string? ImdbRating { get; set; }
|
||||
public string? Genre { get; set; }
|
||||
public string? Plot { get; set; }
|
||||
public string? Director { get; set; }
|
||||
public string? Actors { get; set; }
|
||||
public string? Runtime { get; set; }
|
||||
public string? ImdbID { get; set; }
|
||||
public string? Providers { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentListViewModel
|
||||
{
|
||||
public List<TorrentListItemViewModel> Items { get; set; } = new();
|
||||
public int CurrentPage { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public string CurrentSort { get; set; } = "date";
|
||||
public string CurrentRange { get; set; } = "all";
|
||||
public string CurrentPeriod { get; set; } = "all"; // day/week/month/all
|
||||
}
|
||||
public class TorrentListItemViewModel
|
||||
{
|
||||
public string InfoHash { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string MovieName { get; set; } = string.Empty;
|
||||
public DateTime PublishDate { get; set; }
|
||||
public int Seeders { get; set; }
|
||||
public int Leechers { get; set; }
|
||||
public string? TorrentUrl { get; set; }
|
||||
public MovieMetadata? Metadata { get; set; }
|
||||
public bool IsNew { get; set; } = false;
|
||||
public List<string> AvailableOn { get; set; } = new();
|
||||
public bool IsDownloaded { get; set; }
|
||||
}
|
||||
public class JustWatchResponse
|
||||
{
|
||||
public Data data { get; set; }
|
||||
}
|
||||
|
||||
public class Data
|
||||
{
|
||||
public SearchTitles searchTitles { get; set; }
|
||||
}
|
||||
|
||||
public class SearchTitles
|
||||
{
|
||||
public List<Edge> edges { get; set; }
|
||||
}
|
||||
|
||||
public class Edge
|
||||
{
|
||||
public Node node { get; set; }
|
||||
}
|
||||
|
||||
public class Node
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string title { get; set; }
|
||||
public int? releaseYear { get; set; }
|
||||
public Content content { get; set; }
|
||||
public List<Offer> offers { get; set; }
|
||||
}
|
||||
|
||||
public class Content
|
||||
{
|
||||
public string imdbId { get; set; }
|
||||
}
|
||||
|
||||
public class Offer
|
||||
{
|
||||
public Provider provider { get; set; }
|
||||
public string monetizationType { get; set; } // FLATRATE, RENT, BUY
|
||||
}
|
||||
|
||||
public class Provider
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string clearName { get; set; }
|
||||
}
|
||||
|
||||
public enum TorrentStatus
|
||||
{
|
||||
New,
|
||||
Downloaded,
|
||||
Processing,
|
||||
Completed,
|
||||
Failed,
|
||||
Ignored
|
||||
}
|
||||
public class DownloadRule
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string KeywordFilter { get; set; }
|
||||
public string CategoryFilter { get; set; }
|
||||
public int MinSeeders { get; set; }
|
||||
public long MaxSize { get; set; }
|
||||
public bool AutoDownload { get; set; }
|
||||
}
|
||||
|
||||
public class UserTorrentSeen
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string UserId { get; set; } = null!;
|
||||
public string InfoHash { get; set; } = null!; // unikt för torrent
|
||||
public DateTime SeenDate { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ namespace Aberwyn.Models
|
||||
public List<RecentMenuEntry> RecentEntries { get; set; } = new();
|
||||
public List<WeeklyMenu> WeeklyMenus { get; set; } = new();
|
||||
public List<UserModel> AvailableCooks { get; set; } = new();
|
||||
public List<MealWish> WishList { get; set; } = new();
|
||||
|
||||
public List<WeeklyMenuViewModel> PreviousWeeks { get; set; } = new();
|
||||
public class RecentMenuEntry
|
||||
@@ -37,6 +38,8 @@ namespace Aberwyn.Models
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ using Aberwyn.Services;
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json", optional: false)
|
||||
.AddJsonFile("appsettings.json", optional: true)
|
||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
@@ -25,7 +25,9 @@ builder.Configuration.AddConfiguration(config);
|
||||
|
||||
|
||||
// Läser setup.json eller skapar en ny tom om den inte finns
|
||||
var setupFilePath = Path.Combine("infrastructure", "setup.json");
|
||||
var dataRoot = Path.Combine(Directory.GetCurrentDirectory(), "data");
|
||||
var setupFilePath = Path.Combine(dataRoot, "infrastructure", "setup.json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(setupFilePath)!);
|
||||
|
||||
if (!File.Exists(setupFilePath))
|
||||
{
|
||||
@@ -59,11 +61,24 @@ try
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Fel vid läsning av setup.json: {ex.Message}");
|
||||
Console.WriteLine($"Fel vid läsning av setup.json: {ex.Message}");
|
||||
setup = new SetupSettings { IsConfigured = false };
|
||||
}
|
||||
|
||||
|
||||
builder.Services.AddHttpClient<ITorrentService, TorrentService>();
|
||||
builder.Services.AddScoped<ITorrentService, TorrentService>();
|
||||
builder.Services.AddHttpClient<RssProcessor>();
|
||||
builder.Services.AddScoped<IRssProcessor, RssProcessor>();
|
||||
builder.Services.AddHostedService<TorrentRssService>();
|
||||
|
||||
builder.Services.AddHttpClient<HdTorrentsTrackerScraper>();
|
||||
builder.Services.AddScoped<HdTorrentsTrackerScraper>();
|
||||
builder.Services.AddHttpClient<DelugeClient>();
|
||||
builder.Services.AddHttpClient<MovieMetadataService>();
|
||||
builder.Services.AddScoped<ITorrentService, TorrentService>();
|
||||
builder.Services.AddHttpClient<ITorrentService, TorrentService>();
|
||||
builder.Services.AddScoped<TorrentService>();
|
||||
|
||||
// Add services to the container
|
||||
builder.Services.AddControllersWithViews()
|
||||
@@ -164,7 +179,24 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
options.SupportedUICultures = supportedCultures;
|
||||
});
|
||||
builder.Services.AddSingleton<SetupService>();
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddConsole();
|
||||
builder.Logging.AddDebug();
|
||||
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll", policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin() // Tillåt alla domäner
|
||||
.AllowAnyHeader() // Tillåt alla headers
|
||||
.AllowAnyMethod(); // Tillåt GET, POST, etc.
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Eller om du vill ha mer detaljerad loggning:
|
||||
builder.Logging.SetMinimumLevel(LogLevel.Information);
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseStaticFiles();
|
||||
@@ -191,19 +223,11 @@ app.Use(async (context, next) =>
|
||||
|
||||
|
||||
|
||||
// Configure the HTTP request pipeline
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
|
||||
app.UseRouting();
|
||||
app.UseSession();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseCors("AllowAll");
|
||||
// Routing
|
||||
app.MapControllers();
|
||||
app.MapControllerRoute(
|
||||
@@ -211,6 +235,11 @@ app.MapControllerRoute(
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
app.MapRazorPages();
|
||||
|
||||
app.UseExceptionHandler("/Error");
|
||||
app.UseStatusCodePagesWithReExecute("/Error/{0}");
|
||||
app.UseHsts();
|
||||
|
||||
|
||||
// Init: migrera databas och skapa admin
|
||||
if (setup.IsConfigured)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
}
|
||||
|
||||
.todo-board {
|
||||
padding: 20px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.todo-columns {
|
||||
|
||||
70
Aberwyn/Views/Budget/Elkostnad.cshtml
Normal file
70
Aberwyn/Views/Budget/Elkostnad.cshtml
Normal file
@@ -0,0 +1,70 @@
|
||||
@{
|
||||
ViewData["Title"] = "Elkostnad";
|
||||
}
|
||||
|
||||
<h2>Räkna ut elkostnad</h2>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="mb-2">
|
||||
<label for="spotpris">Månadens spotpris (öre/kWh):</label>
|
||||
<input type="number" id="spotpris" class="form-control" step="0.01" value="52.33" />
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label for="forbrukning">Förbrukning (kWh):</label>
|
||||
<input type="number" id="forbrukning" class="form-control" value="350" />
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary mt-2" onclick="berakna()">Beräkna</button>
|
||||
</div>
|
||||
|
||||
<div id="resultat" class="alert alert-info mt-3" style="display:none;"></div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function berakna() {
|
||||
// Inputs
|
||||
let spotprisOre = parseFloat(document.getElementById("spotpris").value) || 0;
|
||||
let spotprisKr = spotprisOre / 100; // omvandling öre → kr
|
||||
let forbrukning = parseFloat(document.getElementById("forbrukning").value) || 0;
|
||||
|
||||
// Elhandel – fasta tillägg (kr/kWh)
|
||||
const paslag = 0.029;
|
||||
const rorliga = 0.0567;
|
||||
const ursprung = 0.016;
|
||||
|
||||
// Elnät – fasta avgifter (kr/kWh)
|
||||
const eloverforing = 0.29;
|
||||
const energiskatt = 0.439;
|
||||
|
||||
// Fasta avgifter per månad (kr)
|
||||
const elhandelFast = 39.20;
|
||||
const elnatFast = 335.00;
|
||||
const fastSumma = elhandelFast + elnatFast;
|
||||
|
||||
// Rörliga priser
|
||||
let elhandelPerKwh = spotprisKr + paslag + rorliga + ursprung;
|
||||
let elnatPerKwh = eloverforing + energiskatt;
|
||||
let totaltPerKwh = elhandelPerKwh + elnatPerKwh;
|
||||
|
||||
// Kostnader
|
||||
let rorligt = forbrukning * totaltPerKwh;
|
||||
let totalExMoms = rorligt + fastSumma;
|
||||
let moms = totalExMoms * 0.25;
|
||||
let totalInklMoms = totalExMoms + moms;
|
||||
|
||||
// Resultat
|
||||
let resultat = document.getElementById("resultat");
|
||||
resultat.style.display = "block";
|
||||
resultat.innerHTML = `
|
||||
<h4>Resultat</h4>
|
||||
<p>Rörligt (Elhandel + Elnät): ${rorligt.toFixed(2)} kr</p>
|
||||
<p>Fasta avgifter: ${fastSumma.toFixed(2)} kr</p>
|
||||
<p>Moms (25%): ${moms.toFixed(2)} kr</p>
|
||||
<hr />
|
||||
<strong>Total kostnad: ${totalInklMoms.toFixed(2)} kr</strong><br />
|
||||
<small>Snittpris: ${(totalInklMoms / forbrukning).toFixed(2)} kr/kWh</small>
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
}
|
||||
@@ -5,9 +5,13 @@
|
||||
ViewData["Title"] = "Budget";
|
||||
}
|
||||
|
||||
<div ng-app="budgetApp" ng-controller="BudgetController" class="budget-page" ng-init="loadBudget()">
|
||||
<div ng-app="budgetApp" ng-controller="BudgetController">
|
||||
<div class="budget-header" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
|
||||
<div class="month-nav-bar" style="display: flex; align-items: center; gap: 10px; position: relative;">
|
||||
|
||||
<div class="month-nav-bar" ng-if="!budget.name" style="display: flex; align-items: center; gap: 10px; position: relative;">
|
||||
<a href="/budget/list" class="nav-button" style="margin-right: 10px;">
|
||||
Lista
|
||||
</a>
|
||||
<button class="nav-button" ng-click="previousMonth()">←</button>
|
||||
<span class="month-label" ng-click="showMonthPicker = !showMonthPicker" style="cursor: pointer;">
|
||||
{{ selectedMonthName }} {{ selectedYear }}
|
||||
@@ -20,16 +24,24 @@
|
||||
<button class="nav-button" ng-click="applyMonthSelection()">Välj</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="month-nav-bar" ng-if="budget.name">
|
||||
<a href="/budget/list" class="nav-button" style="margin-right: 10px;">
|
||||
Lista
|
||||
</a>
|
||||
<span class="month-label" ng-bind="budget.name"></span>
|
||||
|
||||
</div>
|
||||
<div class="menu-container" ng-class="{ 'open': menuOpen }">
|
||||
<button class="icon-button" ng-click="toggleMenu($event)">
|
||||
<i class="fa fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu" ng-show="menuOpen">
|
||||
<button ng-click="copyPreviousMonthSafe()">Kopiera föregående månad</button>
|
||||
|
||||
<button ng-click="deleteMonth(); menuOpen = false;" class="danger">Ta bort hela månaden</button>
|
||||
<button class="nav-button" ng-click="createNamedBudget()">Ny budget</button>
|
||||
<button ng-click="copyBudget(); menuOpen = false;">Kopiera</button>
|
||||
<button ng-click="deleteBudget(); menuOpen = false;" class="danger">Ta bort</button>
|
||||
<button ng-click="createNewCategory(); menuOpen = false;">Lägg till ny kategori</button>
|
||||
<button ng-click="openImportModule(); menuOpen = false;">Importera text</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,6 +124,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-list">
|
||||
<div ng-repeat="item in cat.items track by item.id">
|
||||
<div drop-placeholder
|
||||
@@ -125,14 +138,22 @@
|
||||
item="item"
|
||||
category="cat"
|
||||
on-item-drop="handleItemDrop(event, data, cat)"
|
||||
ng-class="{ dragging: dragInProgress && draggedItemId === item.id }">
|
||||
ng-class="{
|
||||
dragging: dragInProgress && draggedItemId === item.id,
|
||||
due: item.paymentStatus === 1,
|
||||
paid: item.paymentStatus === 2
|
||||
}"
|
||||
ng-click="handleItemInteraction($event, item)">
|
||||
<i class="fa fa-grip-lines drag-handle"
|
||||
ng-show="cat.editing"
|
||||
style="opacity: 0.5; padding-right: 6px; cursor: grab;"></i>
|
||||
|
||||
<input type="text" ng-model="item.name" ng-if="cat.editing" />
|
||||
<span ng-if="!cat.editing" title="{{ item.definitionName }}">{{ item.name }}</span>
|
||||
<!-- <span ng-if="!cat.editing">#{{ item.definitionName }}</span>-->
|
||||
<input type="text" class="item-label" ng-model="item.name" ng-if="cat.editing" />
|
||||
<span ng-if="!cat.editing" class="item-label" title="{{ item.name }}">{{ item.name }}</span>
|
||||
<!-- debug
|
||||
<span ng-if="!cat.editing" class="item-definition" title="{{ item.definitionName }}">#{{ item.budgetItemDefinitionId }} {{ item.definitionName }}</span>
|
||||
<input type="text" ng-model="item.budgetItemDefinitionId" ng-if="cat.editing" />
|
||||
-->
|
||||
<input type="number" ng-model="item.amount" ng-if="cat.editing" />
|
||||
<span class="amount" ng-if="!cat.editing">{{ item.amount | number:0 }}</span>
|
||||
|
||||
@@ -185,14 +206,63 @@
|
||||
<div>Summa</div>
|
||||
<div class="amount">{{ getCategorySum(cat) | number:0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-data" ng-if="!loading && budget && budget.categories.length === 0">
|
||||
Det finns ingen budgetdata för vald månad ({{ selectedMonth }}/{{ selectedYear }}).
|
||||
<button ng-click="copyPreviousMonthSafe()">[Test] Kopiera föregående</button>
|
||||
<!-- Ingen budget alls -->
|
||||
<div class="no-data" ng-if="!loading && (!budget || !budget.id)">
|
||||
<p>
|
||||
<strong ng-bind="budget && budget.name
|
||||
? 'Budgeten \" ' + budget.name + ' \" finns inte.'
|
||||
: 'Det finns ingen budget för ' + getMonthName(selectedMonth) + ' ' + selectedYear + '.' ">
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<div style="margin-top: 10px;">
|
||||
<!-- Skapa ny budget alltid om budget saknas -->
|
||||
<button ng-click="createEmptyBudget()" style="margin-right: 10px;">
|
||||
Skapa ny budget
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Budget finns men inga kategorier -->
|
||||
<div class="no-data" ng-if="!loading && budget && budget.id && (!budget.categories || budget.categories.length === 0)">
|
||||
<p>
|
||||
Budgeten <strong>{{ budget.name || (getMonthName(selectedMonth) + " " + selectedYear) }}</strong> har inga kategorier än.
|
||||
</p>
|
||||
|
||||
<div style="margin-top: 10px;">
|
||||
<button ng-click="createNewCategory()" style="margin-right: 10px;">
|
||||
Skapa ny kategori
|
||||
</button>
|
||||
<button ng-if="!budget.name" ng-click="copyPreviousMonthSafe()" style="margin-right: 10px;">
|
||||
Kopiera föregående månad
|
||||
</button>
|
||||
<button ng-click="copyBudget()" style="margin-right: 10px;">
|
||||
Kopiera befintlig budget
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal-backdrop" ng-show="showCopyModal">
|
||||
<div class="modal-content">
|
||||
<h3>Kopiera budget</h3>
|
||||
<select ng-model="selectedBudgetToCopy"
|
||||
ng-options="b as formatBudgetName(b) for b in budgetList track by b.id">
|
||||
<option value="">Välj budget</option>
|
||||
</select>
|
||||
|
||||
<div style="margin-top: 10px;">
|
||||
<button ng-click="confirmCopyBudget()">Kopiera</button>
|
||||
<button ng-click="showCopyModal = false">Avbryt</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="add-item-popup" ng-show="addPopupVisible" ng-style="addPopupStyle" ng-class="{ 'above': addPopupAbove }">
|
||||
<label>Typ:</label>
|
||||
<select ng-model="addPopupData.newItemType">
|
||||
@@ -227,10 +297,41 @@
|
||||
<button ng-click="addPopupVisible = false">Avbryt</button>
|
||||
</div>
|
||||
|
||||
<div class="import-module" ng-show="importing" style="position: fixed; top: 10vh; left: 50%; transform: translateX(-50%);
|
||||
background: #1F2C3C; color: white; padding: 24px; border-radius: 8px; z-index: 2000; width: 90%; max-width: 600px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.4);">
|
||||
|
||||
<h3 style="margin-top: 0;">📥 Importera rader till {{ importTargetCategory.name }}</h3>
|
||||
|
||||
<label for="importText">Klistra in rader (en per rad, t.ex. <code>Kallhyra 11315</code>):</label>
|
||||
<textarea id="importText" ng-model="importText" rows="6" style="width: 100%; margin-bottom: 10px;"></textarea>
|
||||
|
||||
<button ng-click="parseImportText()">Förhandsgranska</button>
|
||||
<button ng-click="cancelImport()">Avbryt</button>
|
||||
|
||||
<div class="preview-list" ng-if="importPreview.length > 0" style="margin-top: 16px;">
|
||||
<h4>Förhandsvisning:</h4>
|
||||
<div class="item-row" ng-repeat="item in importPreview">
|
||||
<span class="item-label">{{ item.name }}</span>
|
||||
<span class="amount">{{ item.amount | number:0 }} kr</span>
|
||||
</div>
|
||||
<button ng-click="applyImport()">✔️ Skapa {{ importPreview.length }} rader</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<link rel="stylesheet" href="~/css/budget.css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
||||
<script>
|
||||
window.initialYear = @(ViewBag.Year ?? "null");
|
||||
window.initialMonth = @(ViewBag.Month ?? "null");
|
||||
window.initialName = "@Html.Raw(ViewBag.BudgetName ?? "")";
|
||||
|
||||
|
||||
</script>
|
||||
<script src="~/js/budget.js"></script>
|
||||
<script src="~/js/budget-dragdrop.js"></script>
|
||||
120
Aberwyn/Views/Budget/List.cshtml
Normal file
120
Aberwyn/Views/Budget/List.cshtml
Normal file
@@ -0,0 +1,120 @@
|
||||
@attribute [Authorize(Roles = "Budget")]
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Budgetlista";
|
||||
}
|
||||
|
||||
<div ng-app="budgetApp" ng-controller="BudgetListController" class="budget-page">
|
||||
|
||||
<!-- Toggle -->
|
||||
<div class="details-toggle">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="showDetails"> Visa mer detaljer
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="(year, months) in monthsByYear">
|
||||
<div class="year-header">{{ year }}</div>
|
||||
|
||||
<div class="budget-row">
|
||||
<div class="budget-card" ng-repeat="month in months" ng-click="goToBudget(month)">
|
||||
<!-- Flex-container med namn + bars -->
|
||||
<div class="month-header">
|
||||
<div class="month-name">{{ getMonthName(month) }}</div>
|
||||
<div class="month-bars">
|
||||
<div class="bar income" ng-style="{'height': month.barHeights.income + '%'}" title="Inkomst"></div>
|
||||
<div class="bar expenses" ng-style="{'height': month.barHeights.expenses + '%'}" title="Utgifter"></div>
|
||||
<div class="bar savings" ng-style="{'height': month.barHeights.savings + '%'}" title="Sparande"></div>
|
||||
<!-- <div class="bar leftover" ng-style="{'height': month.barHeights.leftover + '%'}" title="Balans"></div>-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-list" ng-if="showDetails">
|
||||
<div class="item-row" ng-repeat="cat in month.categories">
|
||||
<span class="item-label" style="color: {{ cat.color }}">{{ cat.name }}</span>
|
||||
<span class="amount">{{ getCategorySum(cat) | number:0 }} kr</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="~/css/budget-list.css" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
|
||||
<script>
|
||||
angular.module('budgetApp', [])
|
||||
.controller('BudgetListController', ['$scope', '$http', '$window', function($scope, $http, $window){
|
||||
|
||||
$scope.months = [];
|
||||
$scope.monthsByYear = {};
|
||||
$scope.showDetails = false;
|
||||
const maxBarValue = 100000;
|
||||
const monthNames = ["Januari","Februari","Mars","April","Maj","Juni",
|
||||
"Juli","Augusti","September","Oktober","November","December"];
|
||||
|
||||
$scope.getMonthName = month => month.month ? monthNames[month.month-1] : month.name;
|
||||
$scope.getCategorySum = cat => cat.items ? cat.items.reduce((sum,i)=>sum+i.amount,0) : 0;
|
||||
$scope.goToBudget = month => $window.location.href = '/budget/' + (month.year || month.name) + '/' + (month.month || '');
|
||||
|
||||
$scope.getTotalIncome = period => {
|
||||
return (period.categories || [])
|
||||
.flatMap(c => c.items || [])
|
||||
.filter(i => !i.isExpense && i.includeInSummary)
|
||||
.reduce((sum,i)=>sum+i.amount,0);
|
||||
};
|
||||
|
||||
$scope.getTotalSavings = period => {
|
||||
return (period.categories || [])
|
||||
.flatMap(c => c.items || [])
|
||||
.filter(i => !i.isExpense && i.includeInSummary && i.name.toLowerCase().includes('spara'))
|
||||
.reduce((sum,i)=>sum+i.amount,0);
|
||||
};
|
||||
|
||||
$scope.getTotalExpenses = period => {
|
||||
return (period.categories || [])
|
||||
.flatMap(c => c.items || [])
|
||||
.filter(i => i.isExpense && i.includeInSummary)
|
||||
.reduce((sum,i)=>sum+i.amount,0);
|
||||
};
|
||||
|
||||
$scope.getTotalLeftover = period => {
|
||||
return $scope.getTotalIncome(period) - $scope.getTotalSavings(period) - $scope.getTotalExpenses(period);
|
||||
};
|
||||
|
||||
$http.get('/api/budget/list').then(res => {
|
||||
// Sortera från januari → december per år
|
||||
const sorted = res.data.sort((a,b) => (a.year||0)-(b.year||0) || (a.month||0)-(b.month||0));
|
||||
|
||||
$scope.months = sorted.map(month => {
|
||||
const income = $scope.getTotalIncome(month);
|
||||
const savings = $scope.getTotalSavings(month);
|
||||
const expenses = $scope.getTotalExpenses(month);
|
||||
const leftover = $scope.getTotalLeftover(month);
|
||||
|
||||
month.barHeights = {
|
||||
income: Math.max(income / maxBarValue * 100, 5),
|
||||
savings: Math.max(savings / maxBarValue * 100, 5),
|
||||
expenses: Math.max(expenses / maxBarValue * 100, 5),
|
||||
leftover: Math.max(leftover / maxBarValue * 100, 5)
|
||||
};
|
||||
return month;
|
||||
});
|
||||
|
||||
// Gruppera per år
|
||||
$scope.monthsByYear = $scope.months.reduce((acc, m) => {
|
||||
const year = m.year || 'Övrigt';
|
||||
if (!acc[year]) acc[year] = [];
|
||||
acc[year].push(m);
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
|
||||
|
||||
}]);
|
||||
</script>
|
||||
75
Aberwyn/Views/Error/Error.cshtml
Normal file
75
Aberwyn/Views/Error/Error.cshtml
Normal file
@@ -0,0 +1,75 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
var errorCode = ViewData["ErrorCode"] as int?;
|
||||
var exception = ViewData["Exception"] as Exception;
|
||||
var isDebug = System.Diagnostics.Debugger.IsAttached;
|
||||
}
|
||||
|
||||
<style>
|
||||
.error-page {
|
||||
max-width: 600px;
|
||||
margin: 4rem auto;
|
||||
padding: 2rem;
|
||||
background-color: #1F2C3C;
|
||||
color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.4);
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
}
|
||||
.error-page h1 {
|
||||
font-size: 2.2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #f87171;
|
||||
}
|
||||
.error-page .code {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #facc15;
|
||||
}
|
||||
.error-page pre {
|
||||
background-color: #2E3C4F;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin-top: 1rem;
|
||||
color: #fefefe;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.error-page details summary {
|
||||
cursor: pointer;
|
||||
color: #6a0dad;
|
||||
margin-top: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.error-page p {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="error-page">
|
||||
<h1>🚨 Något gick fel</h1>
|
||||
<a href="/" class="btn-outline">Till startsidan</a>
|
||||
|
||||
@if (errorCode.HasValue)
|
||||
{
|
||||
<div class="code">Statuskod: <strong>@errorCode</strong></div>
|
||||
}
|
||||
|
||||
@if (exception != null)
|
||||
{
|
||||
<h3>Felmeddelande:</h3>
|
||||
<pre>@exception.Message</pre>
|
||||
|
||||
@if (isDebug)
|
||||
{
|
||||
<details>
|
||||
<summary>Visa stacktrace</summary>
|
||||
<pre>@exception.StackTrace</pre>
|
||||
</details>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>Vi kunde tyvärr inte visa sidan. Det kan bero på ett tillfälligt fel.</p>
|
||||
}
|
||||
</div>
|
||||
144
Aberwyn/Views/FoodMenu/Calculator.cshtml
Normal file
144
Aberwyn/Views/FoodMenu/Calculator.cshtml
Normal file
@@ -0,0 +1,144 @@
|
||||
@model Aberwyn.Models.DoughPlan
|
||||
@{
|
||||
var plans = ViewBag.Plans as List<Aberwyn.Models.DoughPlan>;
|
||||
}
|
||||
<div class="card border-info mb-4">
|
||||
<div class="card-header">🍕 Pizzakalkylator</div>
|
||||
<div class="card-body">
|
||||
<form id="calcForm" class="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label>Antal pizzor</label>
|
||||
<input id="antal" type="number" class="form-control" value="8" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Vikt per pizza (g)</label>
|
||||
<input id="vikt" type="number" class="form-control" value="220" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Datum</label>
|
||||
<input id="datum" type="date" class="form-control" value="@DateTime.Today.ToString("yyyy-MM-dd")" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Namn på tillfälle</label>
|
||||
<input id="namn" class="form-control" value="Planering" />
|
||||
</div>
|
||||
</form>
|
||||
<button type="button" class="btn btn-success mt-3" id="saveBtn">💾 Spara</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5>Resultat</h5>
|
||||
<div class="completed-orders-grid">
|
||||
<div class="completed-order-box">
|
||||
<strong>Totalt deg</strong>
|
||||
<span id="totalDeg"></span> g
|
||||
</div>
|
||||
<div class="completed-order-box">
|
||||
<strong>Mjöl</strong>
|
||||
<span id="mjol"></span> g
|
||||
</div>
|
||||
<div class="completed-order-box">
|
||||
<strong>Vatten</strong>
|
||||
<span id="vatten"></span> g
|
||||
</div>
|
||||
<div class="completed-order-box">
|
||||
<strong>Olja</strong>
|
||||
<span id="olja"></span> g
|
||||
</div>
|
||||
<div class="completed-order-box">
|
||||
<strong>Salt</strong>
|
||||
<span id="salt"></span> g
|
||||
</div>
|
||||
<div class="completed-order-box">
|
||||
<strong>Jäst</strong>
|
||||
<span id="jast"></span> g
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (plans?.Any() == true)
|
||||
{
|
||||
<div class="card border-success">
|
||||
<div class="card-header">📋 Sparade planer</div>
|
||||
<div class="card-body">
|
||||
<div class="completed-orders-grid">
|
||||
@foreach (var p in plans)
|
||||
{
|
||||
<div class="completed-order-box">
|
||||
<strong>@p.Namn (@p.Datum.ToString("yyyy-MM-dd"))</strong>
|
||||
<span>🍕 @p.AntalPizzor st × @p.ViktPerPizza:F1 g</span>
|
||||
<hr style="margin:6px 0;" />
|
||||
<span><b>Totalt:</b> @p.TotalDeg:F1 g</span>
|
||||
<span>Mjöl: @p.Mjol:F1 g</span>
|
||||
<span>Vatten: @p.Vatten:F1 g</span>
|
||||
<span>Olja: @p.Olja:F1 g</span>
|
||||
<span>Salt: @p.Salt:F1 g</span>
|
||||
<span>Jäst: @p.Jast:F1 g</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<link rel="stylesheet" href="~/css/pizzacalculator.css" />
|
||||
|
||||
<script>
|
||||
function round1(x) {
|
||||
return Math.round(x * 10) / 10;
|
||||
}
|
||||
|
||||
function calc() {
|
||||
let antal = parseFloat(document.getElementById("antal").value) || 0;
|
||||
let vikt = parseFloat(document.getElementById("vikt").value) || 0;
|
||||
let total = antal * vikt;
|
||||
let mjol = total * (100.0 / 162.0);
|
||||
let vatten = mjol * 0.52;
|
||||
let olja = mjol * 0.075;
|
||||
let salt = mjol * 0.02;
|
||||
let jast = mjol * 0.005;
|
||||
|
||||
document.getElementById("totalDeg").innerText = round1(total);
|
||||
document.getElementById("mjol").innerText = round1(mjol);
|
||||
document.getElementById("vatten").innerText = round1(vatten);
|
||||
document.getElementById("olja").innerText = round1(olja);
|
||||
document.getElementById("salt").innerText = round1(salt);
|
||||
document.getElementById("jast").innerText = round1(jast);
|
||||
|
||||
return { antal, vikt, total, mjol, vatten, olja, salt, jast };
|
||||
}
|
||||
|
||||
["antal","vikt"].forEach(id => {
|
||||
document.getElementById(id).addEventListener("input", calc);
|
||||
});
|
||||
|
||||
calc();
|
||||
|
||||
document.getElementById("saveBtn").addEventListener("click", function () {
|
||||
let values = calc();
|
||||
let data = {
|
||||
AntalPizzor: values.antal,
|
||||
ViktPerPizza: values.vikt,
|
||||
TotalDeg: values.total,
|
||||
Mjol: values.mjol,
|
||||
Vatten: values.vatten,
|
||||
Olja: values.olja,
|
||||
Salt: values.salt,
|
||||
Jast: values.jast,
|
||||
Datum: document.getElementById("datum").value,
|
||||
Namn: document.getElementById("namn").value
|
||||
};
|
||||
|
||||
fetch('@Url.Action("SaveDoughPlan", "FoodMenu")', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
}).then(r => r.json()).then(res => {
|
||||
if (res.success) {
|
||||
location.reload(); // 🔄 Ladda om sidan så listan uppdateras
|
||||
} else {
|
||||
alert("❌ Kunde inte spara");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -3,7 +3,7 @@
|
||||
@{
|
||||
ViewData["Title"] = "Pizza Admin";
|
||||
var availablePizzas = Model.AvailablePizzas as List<Meal> ?? new List<Meal>();
|
||||
var restaurantOpen = ViewBag.RestaurantOpen as bool? ?? true;
|
||||
var restaurantOpen = ViewBag.RestaurantIsOpen as bool? ?? false;
|
||||
}
|
||||
|
||||
<link rel="stylesheet" href="~/css/pizza-admin.css" />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@using Newtonsoft.Json.Serialization
|
||||
@{
|
||||
ViewData["Title"] = "Beställ pizza";
|
||||
var pizzas = ViewBag.Pizzas as List<Meal>;
|
||||
var pizzas = ViewBag.Pizzas as List<MealListDto> ?? new List<MealListDto>();
|
||||
var previousOrder = ViewBag.PreviousOrder as PizzaOrder;
|
||||
var lastId = Context.Session.GetInt32("LastPizzaOrderId");
|
||||
var isCurrentOrder = previousOrder?.Id == lastId;
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
@using System.Globalization
|
||||
@model Aberwyn.Models.WeeklyMenuViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Veckomeny";
|
||||
var days = new[] { "Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag" };
|
||||
}
|
||||
<html lang="sv" ng-app="app">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Veckomeny</title>
|
||||
<link rel="stylesheet" href="~/css/Veckomeny.css" asp-append-version="true" />
|
||||
</head>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
|
||||
<script>
|
||||
window.knownMeals = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(
|
||||
(ViewBag.AvailableMeals as List<Aberwyn.Models.Meal>)?.Select(m => m.Name.Trim()).ToList() ?? new List<string>()));
|
||||
</script>
|
||||
</head>
|
||||
<body x-data="recentHistory()" x-init="init()">
|
||||
|
||||
<div class="weekly-menu-wrapper"
|
||||
x-data="{
|
||||
highlightNew(event) {
|
||||
const input = event.target;
|
||||
const val = input.value?.trim().toLowerCase();
|
||||
input.classList.remove('new-entry', 'existing-entry');
|
||||
<div class="menu-wishlist-wrapper">
|
||||
|
||||
if (!val) return;
|
||||
|
||||
const isKnown = window.knownMeals.some(name => name.toLowerCase() === val);
|
||||
input.classList.add(isKnown ? 'existing-entry' : 'new-entry');
|
||||
}
|
||||
}">
|
||||
<!-- Veckomenyn -->
|
||||
<section class="weekly-editor">
|
||||
<h1>Veckomeny - Vecka @Model.WeekNumber</h1>
|
||||
<div class="week-nav">
|
||||
@@ -46,7 +42,8 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (int i = 0; i < 7; i++) {
|
||||
@for (int i = 0; i < 7; i++)
|
||||
{
|
||||
var dinnerEntry = Model.GetMealEntry(i, "Middag");
|
||||
var lunchEntry = Model.GetMealEntry(i, "Lunch");
|
||||
var breakfastEntry = Model.GetMealEntry(i, "Frukost");
|
||||
@@ -61,8 +58,16 @@
|
||||
<td class="meal-cell">
|
||||
<div class="meal-entry-group">
|
||||
<div class="meal-input-group">
|
||||
<input type="text" name="Meal[@i][Middag]" value="@dinnerEntry?.DinnerMealName" placeholder="Lägg till middag..." list="meals-list" class="meal-input" :tabindex="showExtra ? 0 : -1" x-on:input="highlightNew($event)" />
|
||||
<button type="button" class="delete-btn" title="Rensa middag" onclick="this.previousElementSibling.value='';">
|
||||
<input type="text"
|
||||
name="Meal[@i][Middag]"
|
||||
value="@dinnerEntry?.DinnerMealName"
|
||||
placeholder="Lägg till middag..."
|
||||
list="meals-list"
|
||||
class="meal-input"
|
||||
:tabindex="showExtra ? 0 : -1"
|
||||
x-on:input="highlightFromCurrentWeek()" />
|
||||
<button type="button" class="delete-btn" title="Rensa middag"
|
||||
onclick="this.previousElementSibling.value=''; highlightMeal('');">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -71,7 +76,8 @@
|
||||
<td class="cook-cell">
|
||||
<select name="Cook[@i]" :tabindex="showExtra ? 0 : -1" class="meal-input">
|
||||
<option value="">Välj kock</option>
|
||||
@foreach(var user in Model.AvailableCooks) {
|
||||
@foreach (var user in Model.AvailableCooks)
|
||||
{
|
||||
var selected = Model.WeeklyMenus.FirstOrDefault(m => m.DayOfWeek == i + 1)?.Cook == user.Username;
|
||||
<option value="@user.Username" selected="@selected">@user.Name</option>
|
||||
}
|
||||
@@ -84,15 +90,29 @@
|
||||
<div class="extra-meals">
|
||||
<div class="meal-input-group">
|
||||
<label>Frukost:</label>
|
||||
<input type="text" name="Meal[@i][Frukost]" value="@breakfastEntry?.BreakfastMealName" placeholder="Lägg till frukost..." list="meals-list" class="meal-input" x-on:input="highlightNew($event)" />
|
||||
<button type="button" class="delete-btn" title="Rensa frukost" onclick="this.previousElementSibling.value='';">
|
||||
<input type="text"
|
||||
name="Meal[@i][Frukost]"
|
||||
value="@breakfastEntry?.BreakfastMealName"
|
||||
placeholder="Lägg till frukost..."
|
||||
list="meals-list"
|
||||
class="meal-input"
|
||||
x-on:input="highlightFromCurrentWeek()" />
|
||||
<button type="button" class="delete-btn" title="Rensa frukost"
|
||||
onclick="this.previousElementSibling.value=''; highlightMeal('');">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="meal-input-group">
|
||||
<label>Lunch:</label>
|
||||
<input type="text" name="Meal[@i][Lunch]" value="@lunchEntry?.LunchMealName" placeholder="Lägg till lunch..." list="meals-list" class="meal-input" x-on:input="highlightNew($event)" />
|
||||
<button type="button" class="delete-btn" title="Rensa lunch" onclick="this.previousElementSibling.value='';">
|
||||
<input type="text"
|
||||
name="Meal[@i][Lunch]"
|
||||
value="@lunchEntry?.LunchMealName"
|
||||
placeholder="Lägg till lunch..."
|
||||
list="meals-list"
|
||||
class="meal-input"
|
||||
x-on:input="highlightFromCurrentWeek()" />
|
||||
<button type="button" class="delete-btn" title="Rensa lunch"
|
||||
onclick="this.previousElementSibling.value=''; highlightMeal('');">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -103,51 +123,167 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="save-menu-btn">
|
||||
Spara
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<datalist id="meals-list">
|
||||
@foreach (var meal in (ViewBag.AvailableMeals as List<Aberwyn.Models.Meal>) ?? new List<Aberwyn.Models.Meal>())
|
||||
{
|
||||
<option value="@meal.Name.Trim()"></option>
|
||||
}
|
||||
</datalist>
|
||||
</section>
|
||||
|
||||
<!-- Wishlist -->
|
||||
<aside class="wishlist" ng-controller="WishListCtrl as vm" ng-init="vm.loadWishes()">
|
||||
<h2>Önskemåltider</h2>
|
||||
<div class="wishlist-grid">
|
||||
<div class="wishlist-card" ng-repeat="wish in vm.WishList track by wish.Id">
|
||||
<div class="wishlist-header">
|
||||
<strong>{{wish.Name}}</strong>
|
||||
<span class="wishlist-meta">
|
||||
av {{wish.RequestedByUserName}} • {{wish.CreatedAt}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="wishlist-body" ng-if="wish.Recipe">
|
||||
<p>{{wish.Recipe | limitTo:100}}{{wish.Recipe.length > 100 ? '...' : ''}}</p>
|
||||
</div>
|
||||
<div class="wishlist-actions">
|
||||
<button type="button" ng-click="vm.importWish(wish.Id)" class="btn import">
|
||||
<i class="fa-solid fa-plus"></i> Importera
|
||||
</button>
|
||||
<button type="button" ng-click="vm.archiveWish(wish.Id)" class="btn archive">
|
||||
<i class="fa-solid fa-box-archive"></i> Arkivera
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p ng-if="!vm.WishList.length">Inga önskemål just nu.</p>
|
||||
</aside>
|
||||
|
||||
<div class="add-meal-wrapper">
|
||||
<button type="submit" class="save-menu-btn">Spara veckomeny</button>
|
||||
</div>
|
||||
|
||||
<aside class="recent-history">
|
||||
<h2>Översikt senaste 4 veckor</h2>
|
||||
@{
|
||||
var lastWeeks = Enumerable.Range(1, 4)
|
||||
.Select(i => DateTime.Now.AddDays(-7 * i))
|
||||
.Select(dt => new { Year = dt.Year, Week = ISOWeek.GetWeekOfYear(dt) })
|
||||
.Distinct().ToList();
|
||||
}
|
||||
<!-- Översikt senaste 4 veckor, full width -->
|
||||
<aside class="recent-history-fullwidth">
|
||||
<h2>Översikt senaste 4 veckor (middag)</h2>
|
||||
<div class="recent-table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Vecka</th>
|
||||
@foreach (var d in days) {
|
||||
@foreach (var d in days)
|
||||
{
|
||||
<th>@d</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var w in lastWeeks) {
|
||||
<template x-for="week in weeks" :key="week.weekNumber">
|
||||
<tr>
|
||||
<td>@w.Week</td>
|
||||
@for (int idx = 1; idx <= 7; idx++) {
|
||||
var entry = Model.RecentEntries?.FirstOrDefault(e =>
|
||||
e.Date.Year == w.Year &&
|
||||
ISOWeek.GetWeekOfYear(e.Date) == w.Week &&
|
||||
((int)e.Date.DayOfWeek == (idx % 7)));
|
||||
<td>@(entry?.DinnerMealName ?? "—")</td>
|
||||
}
|
||||
<td x-text="week.weekNumber"></td>
|
||||
<template x-for="day in week.days">
|
||||
<td x-text="day.dinner" :class="day.highlight ? 'highlight' : ''"></td>
|
||||
</template>
|
||||
</tr>
|
||||
}
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</aside>
|
||||
|
||||
<datalist id="meals-list">
|
||||
@foreach (var meal in (List<Meal>)ViewBag.AvailableMeals)
|
||||
{
|
||||
<option value="@meal.Id">@meal.Name</option>
|
||||
}
|
||||
</datalist>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</aside>
|
||||
<script>
|
||||
function recentHistory() {
|
||||
return {
|
||||
weeks: [],
|
||||
init() {
|
||||
fetch('/FoodMenu/GetRecentMenuEntries?weeksBack=4')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const grouped = {};
|
||||
data.forEach(e => {
|
||||
const dt = new Date(e.Date);
|
||||
const weekNo = getISOWeek(dt);
|
||||
const dayIdx = dt.getDay() === 0 ? 6 : dt.getDay() - 1;
|
||||
const key = `W${weekNo}`;
|
||||
if (!grouped[key]) grouped[key] = Array(7).fill().map(() => ({ dinner: '—', highlight: false }));
|
||||
grouped[key][dayIdx] = { dinner: e.DinnerMealName?.trim() || '—', highlight: false };
|
||||
});
|
||||
|
||||
this.weeks = Object.keys(grouped)
|
||||
.sort((a,b) => b.localeCompare(a))
|
||||
.map(weekKey => ({ weekNumber: parseInt(weekKey.replace('W','')), days: grouped[weekKey] }));
|
||||
|
||||
this.highlightFromCurrentWeek();
|
||||
});
|
||||
|
||||
function getISOWeek(date) {
|
||||
const tmpDate = new Date(date.getTime());
|
||||
tmpDate.setHours(0,0,0,0);
|
||||
tmpDate.setDate(tmpDate.getDate() + 4 - (tmpDate.getDay()||7));
|
||||
const yearStart = new Date(tmpDate.getFullYear(),0,1);
|
||||
return Math.ceil((((tmpDate - yearStart) / 86400000) + 1)/7);
|
||||
}
|
||||
},
|
||||
|
||||
highlightMeal(inputValue) {
|
||||
const val = inputValue?.trim().toLowerCase();
|
||||
this.weeks.forEach(week => {
|
||||
week.days.forEach(day => {
|
||||
day.highlight = val && day.dinner?.toLowerCase() === val;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
highlightFromCurrentWeek() {
|
||||
// hämta alla inputs som har något värde
|
||||
const values = Array.from(document.querySelectorAll(".meal-input"))
|
||||
.map(i => i.value?.trim().toLowerCase())
|
||||
.filter(v => v);
|
||||
|
||||
this.weeks.forEach(week => {
|
||||
week.days.forEach(day => {
|
||||
day.highlight = values.includes(day.dinner?.toLowerCase());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
angular.module('app', [])
|
||||
.controller('WishListCtrl', ['$http', function($http){
|
||||
var vm = this;
|
||||
vm.WishList = [];
|
||||
|
||||
vm.loadWishes = function(){
|
||||
$http.get('/api/MealWish/all')
|
||||
.then(function(res){
|
||||
vm.WishList = res.data;
|
||||
}, function(err){
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
vm.archiveWish = function(id){
|
||||
$http.post('/api/MealWish/' + id + '/archive')
|
||||
.then(function(){
|
||||
vm.WishList = vm.WishList.filter(function(w){ return w.Id !== id; });
|
||||
}, function(err){
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
vm.importWish = function(id){
|
||||
$http.post('/api/MealWish/' + id + '/import')
|
||||
.then(function(){
|
||||
vm.WishList = vm.WishList.filter(function(w){ return w.Id !== id; });
|
||||
}, function(err){
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
}]);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user