Compare commits

...

104 Commits

Author SHA1 Message Date
Elias Jansson
d5ae7fedef test
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 15:59:43 +01:00
Elias Jansson
8d7cf86d4d Test 2026-01-24 15:59:39 +01:00
Elias Jansson
81417b2a1c Rättat rätt filer för setup
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 15:41:22 +01:00
Elias Jansson
c0463a8f5b Kanske?
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 15:35:33 +01:00
Elias Jansson
c4f1a1ca81 Nuså kanske
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 15:27:49 +01:00
Elias Jansson
c523552d2f Ny setup fil
Some checks failed
continuous-integration/drone/push Build is failing
2026-01-24 15:24:10 +01:00
Elias Jansson
1e16dc5f18 app data
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 15:07:09 +01:00
Elias Jansson
313c4d6c60 App
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 15:05:11 +01:00
Elias Jansson
0cbe46c93a Ny folder
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 14:59:51 +01:00
Elias Jansson
55a118f8c9 Directory change
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 14:47:37 +01:00
Elias Jansson
056cac794b Dockerfile update med mapp
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 14:42:28 +01:00
Elias Jansson
df2aaa38a3 Docker fix
Some checks failed
continuous-integration/drone/push Build is failing
2026-01-24 14:38:29 +01:00
Elias Jansson
d06dfed0e2 Ny docker för app/data folder
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 14:23:40 +01:00
Elias Jansson
2161ebf1e8 Flytta filen till persistant
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 14:14:26 +01:00
Elias Jansson
42546af4f3 Fix på ny drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 14:05:18 +01:00
Elias Jansson
bd39a45dd2 Version test
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 14:00:21 +01:00
Elias Jansson
cd2c2ac50d Ny drone som inte bygger om på unraid, hämtas automagiskt
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-24 13:49:40 +01:00
Elias Jansson
2bbd67e37d RSS fix
All checks were successful
continuous-integration/drone/push Build is passing
2026-01-23 20:00:16 +01:00
Elias Jansson
a2e14c73df Fixat årsskifte med veckorna
All checks were successful
continuous-integration/drone/push Build is passing
2025-12-26 19:05:07 +01:00
Elias Jansson
c2161bdb91 Budget copy fix 2025-12-14 21:02:42 +01:00
Elias Jansson
49d32b0854 Veckomeny fixes to Date
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-21 21:09:44 +02:00
Elias Jansson
80b0e825b2 Torrent RSS link fix
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-30 23:14:13 +02:00
Elias Jansson
53b4a5e97d Veckomeny fixes
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-28 08:13:33 +02:00
Elias Jansson
335112e044 Rename changes and report
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-27 19:31:54 +02:00
Elias Jansson
9299e29ea6 Wish and veckomeny fixes
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-22 21:22:43 +02:00
Elias Jansson
5a17df917d Budget report!
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-21 14:40:00 +02:00
Elias Jansson
8aed8d16b6 More features
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-12 00:27:35 +02:00
Elias Jansson
64aa9cf716 Budget improvements and list!
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-11 22:42:56 +02:00
Elias Jansson
f63ccc2a38 Metadata
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-29 15:30:11 +02:00
Elias Jansson
a6fd3d720f Torrent fix
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-24 23:28:37 +02:00
Elias Jansson
77f6d2a475 Torrent fixes
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-24 23:23:21 +02:00
Elias Jansson
85f559a607 Torrent v1 done
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-24 21:16:03 +02:00
Elias Jansson
0c2f131fff Torrent changes again!
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-24 01:20:46 +02:00
Elias Jansson
e96696f6be Torrents
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-22 23:54:52 +02:00
Elias Jansson
704e206476 Updates to torrent!
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-20 13:05:22 +02:00
Elias Jansson
6b19f08d6b More torrent and omdb
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-20 00:39:19 +02:00
Elias Jansson
146c557c25 Torrent stuff
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-19 15:56:52 +02:00
elias
fb24ffbf03 Merge branch 'master' of http://192.168.1.9:3000/Tai/Aberwyn
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-03 21:52:25 +02:00
elias
274f98baa4 Test 2025-08-03 21:47:02 +02:00
elias
80ffad6d86 Torrent 2025-08-03 21:46:06 +02:00
Elias Jansson
5b0a8386ad Fixed leftover
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-24 11:18:51 +02:00
Elias Jansson
4b8c54d38d Category fix and movie 0.1
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-24 10:59:33 +02:00
Elias Jansson
cc96802637 Budget tests
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-21 15:16:31 +02:00
Elias Jansson
95811ce3f8 More css and button fixes
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-07 15:47:26 +02:00
Elias Jansson
4a7a2c30c9 Meal CSS fix
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-07 15:33:49 +02:00
Elias Jansson
8ebbb803e8 Meal rating and some fixes to meals
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-07 15:22:12 +02:00
elias
380978959b Lab changes
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-07 09:37:56 +02:00
Elias Jansson
072369fa17 Meal lab v0.1
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-03 22:00:56 +02:00
Elias Jansson
45994a9439 Today menu api
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-25 09:19:16 +02:00
Elias Jansson
3ef872ac8c Recipe publishing
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-24 17:20:08 +02:00
Elias Jansson
051ef625ba Changes Kök to Mat
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-19 12:46:42 +02:00
Elias Jansson
07f6451c5a Drone session persistance fixes
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-19 12:43:53 +02:00
Elias Jansson
979b05f2ca Test 6
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-19 12:32:24 +02:00
Elias Jansson
aefc653e22 Test 5
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 12:28:01 +02:00
Elias Jansson
4f14918b02 Test 4
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 12:26:31 +02:00
Elias Jansson
f8a33123d0 Test 3
Some checks failed
continuous-integration/drone/push Build encountered an error
2025-06-19 12:25:12 +02:00
Elias Jansson
95bb989e07 Test 2
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 12:23:45 +02:00
Elias Jansson
004ac0c696 Test
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 12:21:42 +02:00
Elias Jansson
f0642e4587 More optimization
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 12:20:51 +02:00
Elias Jansson
2504fab3e7 Drone optimization
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 12:18:56 +02:00
Elias Jansson
a4229594bf Another drone optimization
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-19 12:16:58 +02:00
Elias Jansson
55a254457b drone multiline removal
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 11:10:58 +02:00
Elias Jansson
67670cc380 Drone
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 11:09:27 +02:00
Elias Jansson
e26a809122 docker login 192.168.1.9:3000 -u ${GITEA_USERNAME} -p ${GITEA_TOKEN}
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 11:07:46 +02:00
Elias Jansson
b1f20de393 Drone test
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-19 11:01:45 +02:00
Elias Jansson
c84699fd16 Drone test
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-19 10:55:11 +02:00
Elias Jansson
46299cb7f2 Drone fix
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-19 09:33:55 +02:00
Elias Jansson
9f40b3f8a0 Optimizing drone compiling
Some checks failed
continuous-integration/drone/push Build encountered an error
2025-06-19 09:32:38 +02:00
Elias Jansson
6cb2ebf3c8 Mobile fix for paymentstatus
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-18 23:35:22 +02:00
Elias Jansson
871fe3a070 Budget paymentdue
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-18 23:26:41 +02:00
Elias Jansson
22ae26d488 Notifications
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-15 22:32:13 +02:00
Elias Jansson
b785051a89 Notifications
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-15 22:15:31 +02:00
elias
0e58ffb735 Pizza fixes and removed school menu since summer
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-15 20:48:05 +02:00
elias
76656bb6a8 Lots of fixes
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-15 10:49:04 +02:00
Elias Jansson
113cce73ad New manifest with correct color
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-13 15:50:50 +02:00
Elias Jansson
fb62f076a0 Mostly css changes and welcome page thumbnails
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-13 15:46:57 +02:00
Elias Jansson
6a43435950 Layout fixad
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-13 10:41:00 +02:00
Elias Jansson
f71be26ae4 Layout fix
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-12 13:23:58 +02:00
Elias Jansson
d1e4901eee Layout ändringar och meal fixar
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-12 13:00:52 +02:00
Elias Jansson
fc78ec0813 Meal fix
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-11 13:51:45 +02:00
Tai
57bea7b54c Meal updates
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-11 13:06:08 +02:00
Elias Jansson
465f9afc99 Bunch of fixes and session extensions
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-10 18:59:04 +02:00
Elias Jansson
e3eb2dc7cb Budget page improvements
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-09 16:11:59 +02:00
elias
256ce76af1 Pizza notice
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-06 12:57:49 +02:00
elias
b286fed88a Ny meals!
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-05 23:23:36 +02:00
elias
bc77e39c72 Optimized menu
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-05 20:15:11 +02:00
elias
eba00ffbf2 Todo fixes
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-05 00:20:17 +02:00
Elias Jansson
c44fbfdca9 Woohoo working version gooo
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-04 18:01:05 +02:00
Elias Jansson
84c6c45a0b Another attempt
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-04 11:51:08 +02:00
Elias Jansson
83a71a6f1d Setup.json password usage
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-04 09:42:03 +02:00
Elias Jansson
fcd27943bf Drone fix
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-04 09:08:36 +02:00
Elias Jansson
acbb72de3f DRONE
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-03 23:12:15 +02:00
Elias Jansson
c4efcc249b Drone
Some checks failed
continuous-integration/drone/push Build encountered an error
2025-06-03 23:05:53 +02:00
Elias Jansson
4da4a34443 Passwordfix
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-03 22:47:17 +02:00
Elias Jansson
77d3c741b1 Stringbuilder to handle passwords
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-03 22:35:37 +02:00
Elias Jansson
e626daa7bc Gogogo
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-03 22:11:17 +02:00
Elias Jansson
fe8e54b868 Git
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-03 21:57:44 +02:00
Elias Jansson
908bc469c6 Setup!
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-03 21:36:16 +02:00
Elias Jansson
3d6aa2d424 Use old db
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-03 00:26:13 +02:00
Elias Jansson
fd6759e3d0 Gogogo
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-03 00:19:40 +02:00
Elias Jansson
4c60508d6d Fixes!
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-03 00:04:28 +02:00
Elias Jansson
628f25a8be More
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-02 23:52:17 +02:00
Elias Jansson
2e1e0f4670 Clearing up proj file
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-02 23:48:43 +02:00
Elias Jansson
a33ed400f1 Dockerfile fix
Some checks failed
continuous-integration/drone/push Build is failing
2025-06-02 23:40:47 +02:00
160 changed files with 37082 additions and 1523 deletions

View File

@@ -2,45 +2,27 @@ kind: pipeline
type: docker
name: default
volumes:
- name: dockersock
host:
path: /var/run/docker.sock
steps:
- name: build-dotnet
image: mcr.microsoft.com/dotnet/sdk:6.0
- name: docker-build
image: docker:24
volumes:
- name: dockersock
path: /var/run/docker.sock
environment:
LANG: en_US.UTF-8
LC_ALL: en_US.UTF-8
DOTNET_CLI_UI_LANGUAGE: en-US
commands:
- dotnet publish Aberwyn/Aberwyn.csproj -c Release -o out
- name: build-docker
image: plugins/docker
settings:
registry: 192.168.1.9:3000
repo: 192.168.1.9:3000/tai/aberwyn/aberwyn
username:
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 run -d --name='Aberwyn' --net='br0' -e TZ='Europe/Berlin' -p 80:80 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

7
.gitignore vendored
View File

@@ -360,4 +360,9 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
# Setupfil för Aberwyn
infrastructure/setup.json
Aberwyn/Infrastructure/setup.json
/Aberwyn/Data/infrastructure/setup2.json

View File

@@ -11,10 +11,6 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{423CD237-7404-4355-868F-CE5861835258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{423CD237-7404-4355-868F-CE5861835258}.Debug|Any CPU.Build.0 = Debug|Any CPU
{423CD237-7404-4355-868F-CE5861835258}.Release|Any CPU.ActiveCfg = Release|Any CPU
{423CD237-7404-4355-868F-CE5861835258}.Release|Any CPU.Build.0 = Release|Any CPU
{F5586986-B726-4E05-B31B-2E24CA5B2B89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5586986-B726-4E05-B31B-2E24CA5B2B89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5586986-B726-4E05-B31B-2E24CA5B2B89}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@@ -4,9 +4,9 @@ name: default
steps:
- name: build-dotnet
image: mcr.microsoft.com/dotnet/sdk:6.0
image: alpine
commands:
- dotnet publish Aberwyn/Aberwyn.csproj -c Release -o out
- echo "Docker build will handle dotnet publish"
- name: build-docker
image: plugins/docker
@@ -18,6 +18,26 @@ steps:
password:
from_secret: gitea_token
dockerfile: Aberwyn/Dockerfile
context: Aberwyn
context: .
tags:
- latest
insecure: true
- name: notify-result
image: alpine
when:
status:
- success
- failure
commands:
- apk add --no-cache curl
- |
if [ "$DRONE_BUILD_STATUS" = "success" ]; then
curl -X POST http://192.168.1.196:8123/api/webhook/aberwyn_update_success
else
curl -X POST http://192.168.1.196:8123/api/webhook/aberwyn_update_failed
fi
trigger:
branch:
- master

View File

@@ -9,25 +9,16 @@
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Compile Remove="NewFolder\**" />
<Content Remove="NewFolder\**" />
<EmbeddedResource Remove="NewFolder\**" />
<None Remove="NewFolder\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Data\BudgetService.cs" />
</ItemGroup>
<ItemGroup>
<Content Remove="Views\Home\Budget.cshtml" />
<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" />
@@ -35,6 +26,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.36">
<PrivateAssets>all</PrivateAssets>
@@ -52,12 +44,15 @@
<!-- Ö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.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>
<ItemGroup>
<Folder Include="Migrations\" />
<Folder Include="Views\NewFolder\" />
<Folder Include="wwwroot\images\meals\" />
</ItemGroup>

View File

@@ -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>

View File

@@ -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]
@@ -99,7 +99,7 @@ namespace Aberwyn.Areas.Identity.Pages.Account
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: false);
var result = await _signInManager.PasswordSignInAsync(Input.UserName, Input.Password, isPersistent: true, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");

View File

@@ -11,6 +11,7 @@ namespace Aberwyn.Controllers
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
private readonly MenuService _menuService;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly IConfiguration _configuration;
@@ -18,12 +19,14 @@ namespace Aberwyn.Controllers
private readonly ApplicationDbContext _context;
public AdminController(
MenuService menuService,
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IConfiguration configuration,
IHostEnvironment env,
ApplicationDbContext context)
{
_menuService = menuService;
_userManager = userManager;
_roleManager = roleManager;
_configuration = configuration;
@@ -102,54 +105,20 @@ namespace Aberwyn.Controllers
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult ImportMealsFromProd()
public IActionResult ImportMenusFromCustom(string dbHost, int dbPort, string dbName, string dbUser, string dbPassword)
{
var prodService = MenuService.CreateWithConfig(_configuration, _env, useProdDb: true);
var devService = new MenuService(_context); // injicerad context används
var connStr = $"Server={dbHost};Port={dbPort};Database={dbName};Uid={dbUser};Pwd={dbPassword};";
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
var prodMeals = prodService.GetMealsDetailed();
foreach (var meal in prodMeals)
{
// Kopiera utan ID (för att undvika konflikt) och spara
var newMeal = new Meal
{
Name = meal.Name,
Description = meal.Description,
ProteinType = meal.ProteinType,
Category = meal.Category,
CarbType = meal.CarbType,
RecipeUrl = meal.RecipeUrl,
ImageUrl = meal.ImageUrl,
IsAvailable = meal.IsAvailable,
CreatedAt = meal.CreatedAt,
Instructions = meal.Instructions,
ImageData = meal.ImageData,
ImageMimeType = meal.ImageMimeType,
Ingredients = meal.Ingredients.Select(i => new Ingredient
{
Quantity = i.Quantity,
Item = i.Item
}).ToList()
};
devService.SaveOrUpdateMealWithIngredients(newMeal);
}
return Content("Import klar!");
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult ImportMenusFromProd()
{
var prodService = MenuService.CreateWithConfig(_configuration, _env, useProdDb: true);
using var customContext = new ApplicationDbContext(optionsBuilder.Options);
var sourceService = new MenuService(customContext);
var devService = new MenuService(_context);
var allProdMenus = prodService.GetAllWeeklyMenus();
var allMeals = devService.GetMeals();
var sourceMenus = sourceService.GetAllWeeklyMenus();
var devMeals = devService.GetMeals();
foreach (var menu in allProdMenus)
foreach (var menu in sourceMenus)
{
var newMenu = new WeeklyMenu
{
@@ -163,34 +132,99 @@ namespace Aberwyn.Controllers
};
if (!string.IsNullOrEmpty(menu.BreakfastMealName))
newMenu.BreakfastMealId = allMeals.FirstOrDefault(m => m.Name == menu.BreakfastMealName)?.Id;
newMenu.BreakfastMealId = devMeals.FirstOrDefault(m => m.Name == menu.BreakfastMealName)?.Id;
if (!string.IsNullOrEmpty(menu.LunchMealName))
newMenu.LunchMealId = allMeals.FirstOrDefault(m => m.Name == menu.LunchMealName)?.Id;
newMenu.LunchMealId = devMeals.FirstOrDefault(m => m.Name == menu.LunchMealName)?.Id;
if (!string.IsNullOrEmpty(menu.DinnerMealName))
newMenu.DinnerMealId = allMeals.FirstOrDefault(m => m.Name == menu.DinnerMealName)?.Id;
newMenu.DinnerMealId = devMeals.FirstOrDefault(m => m.Name == menu.DinnerMealName)?.Id;
_context.WeeklyMenus.Add(newMenu);
}
_context.SaveChanges();
TempData["Message"] = "Import av veckomenyer klar.";
TempData["Message"] = $"✅ Import av veckomenyer från extern databas klar ({sourceMenus.Count}).";
return RedirectToAction("Index");
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult GenerateThumbnails()
{
var count = _menuService.GenerateMissingThumbnails();
return Ok($"{count} thumbnails skapades.");
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult ImportBudgetFromProd()
public IActionResult ImportMealsFromCustom(string dbHost, int dbPort, string dbName, string dbUser, string dbPassword)
{
// Hämta connection till produktion
using var prodContext = ApplicationDbContextFactory.CreateWithConfig(_configuration, _env, useProdDb: true);
var connStr = $"Server={dbHost};Port={dbPort};Database={dbName};Uid={dbUser};Pwd={dbPassword};";
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr), mySqlOptions => mySqlOptions.CommandTimeout(180));
using var customContext = new ApplicationDbContext(optionsBuilder.Options);
// Importera definitioner först
var prodCategoryDefs = prodContext.BudgetCategoryDefinitions.ToList();
var prodItemDefs = prodContext.BudgetItemDefinitions.ToList();
var customService = new MenuService(customContext);
var devService = new MenuService(_context);
foreach (var def in prodCategoryDefs)
try
{
var importedMeals = customService.GetMealsDetailed(); // Ska inkludera Ingredients
foreach (var meal in importedMeals)
{
var newMeal = new Meal
{
Id = meal.Id, // 👈 Viktigt!
Name = meal.Name,
Description = meal.Description,
ProteinType = meal.ProteinType,
Category = meal.Category,
CarbType = meal.CarbType,
RecipeUrl = meal.RecipeUrl,
ImageUrl = meal.ImageUrl,
IsAvailable = meal.IsAvailable,
CreatedAt = meal.CreatedAt,
Instructions = meal.Instructions,
ImageData = meal.ImageData,
ImageMimeType = meal.ImageMimeType,
Ingredients = meal.Ingredients.Select(i => new Ingredient
{
MealId = meal.Id, // 👈 Koppla till rätt måltid
Quantity = i.Quantity,
Item = i.Item
}).ToList()
};
devService.SaveOrUpdateMealWithIngredients(newMeal);
}
TempData["Message"] = $"✅ {importedMeals.Count} måltider importerade från extern databas.";
}
catch (Exception ex)
{
TempData["Message"] = $"❌ Fel vid import: {ex.Message}";
}
return RedirectToAction("Index");
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult ImportBudgetFromCustom(string dbHost, int dbPort, string dbName, string dbUser, string dbPassword)
{
var connStr = $"Server={dbHost};Port={dbPort};Database={dbName};Uid={dbUser};Pwd={dbPassword};";
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
using var sourceContext = new ApplicationDbContext(optionsBuilder.Options);
var categoryDefs = sourceContext.BudgetCategoryDefinitions.ToList();
var itemDefs = sourceContext.BudgetItemDefinitions.ToList();
foreach (var def in categoryDefs)
{
if (!_context.BudgetCategoryDefinitions.Any(d => d.Name == def.Name))
{
@@ -199,11 +233,10 @@ namespace Aberwyn.Controllers
Name = def.Name,
Color = def.Color ?? "#cccccc"
});
}
}
foreach (var def in prodItemDefs)
foreach (var def in itemDefs)
{
if (!_context.BudgetItemDefinitions.Any(d => d.Name == def.Name))
{
@@ -211,19 +244,17 @@ namespace Aberwyn.Controllers
}
}
_context.SaveChanges(); // Se till att ID:n finns för FK:n nedan
_context.SaveChanges();
// Ladda definitioner i minnet för snabb lookup
var devCategoryDefs = _context.BudgetCategoryDefinitions.ToList();
var devItemDefs = _context.BudgetItemDefinitions.ToList();
// Importera budgetperioder med kategorier och items
var prodPeriods = prodContext.BudgetPeriods
var periods = sourceContext.BudgetPeriods
.Include(p => p.Categories)
.ThenInclude(c => c.Items)
.ToList();
foreach (var period in prodPeriods)
foreach (var period in periods)
{
var exists = _context.BudgetPeriods
.Any(p => p.Year == period.Year && p.Month == period.Month);
@@ -259,11 +290,13 @@ namespace Aberwyn.Controllers
}
_context.SaveChanges();
TempData["Message"] = "✅ Import av budgetdata från produktion är klar.";
TempData["Message"] = $"✅ Import av budgetdata från extern databas klar ({periods.Count} månader).";
return RedirectToAction("Index");
}
//Todo
[HttpGet]
@@ -285,16 +318,31 @@ namespace Aberwyn.Controllers
[HttpPost]
public IActionResult AddTodoTask([FromBody] TodoTask task)
{
if (string.IsNullOrWhiteSpace(task?.Title))
return BadRequest("Titel krävs");
try
{
if (string.IsNullOrWhiteSpace(task?.Title))
return BadRequest("Titel krävs");
task.CreatedAt = DateTime.UtcNow;
_context.TodoTasks.Add(task);
_context.SaveChanges();
task.CreatedAt = DateTime.UtcNow;
return Json(task);
task.Status ??= "ideas";
task.Tags ??= "";
task.AssignedTo ??= null;
task.Description ??= "";
_context.TodoTasks.Add(task);
_context.SaveChanges();
return Json(task);
}
catch (Exception ex)
{
// 👇 Returnera hela stacktracen som JSON för felsökning
return StatusCode(500, ex.ToString());
}
}
[HttpPost]
public IActionResult UpdateTodoTask([FromBody] TodoTask task)
{
@@ -305,18 +353,41 @@ namespace Aberwyn.Controllers
existing.Title = task.Title;
existing.Status = task.Status;
existing.Priority = task.Priority;
existing.Description = task.Description;
existing.Tags = task.Tags;
existing.AssignedTo = task.AssignedTo;
existing.IsArchived = task.IsArchived;
_context.SaveChanges();
return Ok();
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult TestDbConnection(string dbHost, int dbPort, string dbName, string dbUser, string dbPassword)
{
var connStr = $"Server={dbHost};Port={dbPort};Database={dbName};Uid={dbUser};Pwd={dbPassword};";
try
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
using var context = new ApplicationDbContext(builder.Options);
context.Database.OpenConnection();
context.Database.CloseConnection();
return Json(new { success = true, message = "✅ Anslutning lyckades!" });
}
catch (Exception ex)
{
return Json(new { success = false, message = $"❌ Anslutning misslyckades: {ex.Message}" });
}
}
}
public class AdminUserViewModel
public class AdminUserViewModel
{
public string UserId { get; set; }
public string Email { get; set; }

View File

@@ -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)
{
_context.BudgetPeriods.Add(newPeriod);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetBudget), new { year = newPeriod.Year, month = newPeriod.Month }, 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 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}");
}
}
}
}

View File

@@ -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 });
}
}
}
}

View 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");
}
}
}

View File

@@ -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,25 +21,29 @@ 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();
ViewBag.RestaurantIsOpen = GetRestaurantStatus();
int? lastId = HttpContext.Session.GetInt32("LastPizzaOrderId");
if (lastId.HasValue)
@@ -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,10 +159,14 @@ 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")
.OrderBy(m => m.Name)
.ToList();
.Where(m => m.MealCategoryId == pizzaCategory?.Id)
.OrderBy(m => m.Name)
.ToList();
ViewBag.RestaurantIsOpen = GetRestaurantStatus();
@@ -167,27 +178,70 @@ namespace Aberwyn.Controllers
[HttpPost]
[Authorize(Roles = "Chef")]
public IActionResult UpdatePizzaOrder(int id, string status, string ingredientsJson)
public async Task<IActionResult> UpdatePizzaOrder(int id, string status, string ingredientsJson)
{
var order = _context.PizzaOrders.FirstOrDefault(p => p.Id == id);
if (order != null)
var order = await _context.PizzaOrders.FirstOrDefaultAsync(p => p.Id == id);
if (order == null)
return RedirectToAction("PizzaAdmin");
order.Status = status;
order.IngredientsJson = ingredientsJson;
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)
{
order.Status = status;
order.IngredientsJson = ingredientsJson;
_context.SaveChanges();
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 });
}
}
}

View File

@@ -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();

View File

@@ -2,6 +2,8 @@
using Aberwyn.Models;
using Aberwyn.Data;
using Microsoft.AspNetCore.Authorization;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp;
namespace Aberwyn.Controllers
{
@@ -10,28 +12,48 @@ namespace Aberwyn.Controllers
private readonly IConfiguration _configuration;
private readonly IHostEnvironment _env;
private readonly MenuService _menuService;
public MealController(IConfiguration configuration, IHostEnvironment env)
public MealController(MenuService menuService, IConfiguration configuration, IHostEnvironment env)
{
_menuService = menuService;
_configuration = configuration;
_env = env;
}
[HttpGet]
public IActionResult View(int id, bool edit = false)
[HttpGet("/meal")]
public IActionResult Index()
{
var service = _menuService;
var meal = service.GetMealById(id);
return View("Index");
}
[HttpGet]
public IActionResult View(int? id, bool edit = false)
{
Meal meal;
ViewData["IsEditing"] = edit; // → här
if (meal == null)
if (id.HasValue)
{
return NotFound();
meal = _menuService.GetMealById(id.Value);
if (meal == null)
return NotFound();
}
else
{
meal = new Meal
{
Name = "",
Description = "",
Ingredients = new List<Ingredient>(),
IsAvailable = true,
CreatedAt = DateTime.Now
};
}
ViewBag.Categories = _menuService.GetMealCategories()
.Where(c => c.IsActive)
.OrderBy(c => c.Name)
.ToList();
ViewData["IsEditing"] = edit;
return View("View", meal);
}
[HttpGet]
[Route("Meal/Tooltip/{id}")]
public IActionResult Tooltip(int id)
@@ -69,6 +91,15 @@ namespace Aberwyn.Controllers
return StatusCode(500, $"<pre>{ex.Message}</pre>");
}
}
[HttpGet("Meal/Thumbnail/{id}")]
public IActionResult Thumbnail(int id)
{
var meal = _menuService.GetMealById(id);
if (meal == null || meal.ThumbnailData == null)
return NotFound();
return File(meal.ThumbnailData, "image/webp"); // eller image/jpeg om du använder det
}
@@ -92,6 +123,8 @@ namespace Aberwyn.Controllers
ImageFile.CopyTo(ms);
meal.ImageData = ms.ToArray();
meal.ImageMimeType = ImageFile.ContentType;
meal.ThumbnailData = GenerateThumbnail(ImageFile);
}
else
{
@@ -110,8 +143,21 @@ namespace Aberwyn.Controllers
}
private byte[] GenerateThumbnail(IFormFile file)
{
using var image = SixLabors.ImageSharp.Image.Load(file.OpenReadStream());
image.Mutate(x => x.Resize(new ResizeOptions
{
Mode = ResizeMode.Max,
Size = new Size(300, 300) // eller vad du vill för korten
}));
using var ms = new MemoryStream();
image.SaveAsWebp(ms); // kräver ImageSharp.Webp-paketet
return ms.ToArray();
}
[HttpPost]
@@ -121,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 });
}
}
}

View File

@@ -1,9 +1,12 @@
// 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;
using System.Text.Json;
namespace Aberwyn.Controllers
{
@@ -25,15 +28,61 @@ 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()
{
var meals = _menuService.GetMealsDetailed(); // Hämtar med ImageData
var mealDtos = meals.Select(MealDto.FromMeal).ToList();
var meals = _menuService.GetMealsSlim(true);
var mealDtos = meals.Select(m => MealDto.FromMeal(m, includeThumbnail: true)).ToList(); // 👈 fix
return Ok(mealDtos);
}
[HttpGet("getWeeklyMenu")]
public IActionResult GetWeeklyMenu(int weekNumber, int year)
{
var menuDtos = _menuService.GetWeeklyMenuDto(weekNumber, year);
Console.WriteLine("Hämtar meals: " + menuDtos);
return Ok(menuDtos);
}
[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")]
public IActionResult SaveMenu([FromBody] MenuViewModel weeklyMenu)
{
@@ -55,5 +104,48 @@ 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")
{
var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3M2Q5ODIyYzU4ZWI0MjM4OWEyMGQ2MWQ2MWVhOWYzYyIsImlhdCI6MTc0OTE1MzY1MCwiZXhwIjoyMDY0NTEzNjUwfQ.8C_dKm7P1BbFVJKc_wT76YnQqiZxkP9EzrsLbfD0Ml8";
var client = new HttpClient();
client.BaseAddress = new Uri("http://192.168.1.196:8123/");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var res = await client.GetAsync($"/api/states/{sensor}");
if (!res.IsSuccessStatusCode)
return StatusCode((int)res.StatusCode, $"Kunde inte hämta data för {sensor}");
var json = await res.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
var attr = doc.RootElement.GetProperty("attributes");
if (!attr.TryGetProperty("calendar", out var calendar))
return NotFound("Ingen kalender hittades i attributen");
if (!calendar.TryGetProperty(week.ToString(), out var weekData))
return NotFound("Ingen skolmat för vecka " + week);
var result = new List<object>();
foreach (var day in weekData.EnumerateArray())
{
var weekday = day.GetProperty("weekday").GetString();
var date = day.GetProperty("date").GetString();
var courses = day.GetProperty("courses").EnumerateArray().Select(c => c.GetString()).ToList();
result.Add(new { weekday, date, courses });
}
return Ok(result);
}
#endregion
}
}

View 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();
}
}
}

View 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);
}
}

View File

@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Mvc;
namespace Aberwyn.Controllers
{
public class MovieController : Controller
{
[HttpGet("/movie/search")]
public IActionResult Search()
{
return View();
}
}
}

View File

@@ -2,6 +2,7 @@
using Aberwyn.Data;
using Aberwyn.Models;
using System.Linq;
using Aberwyn.Services;
namespace Aberwyn.Controllers
{
@@ -9,11 +10,14 @@ namespace Aberwyn.Controllers
{
private readonly MenuService _menuService;
private readonly ApplicationDbContext _context;
private readonly PizzaNotificationService _pizzaNotifier;
public PizzaController(MenuService menuService, ApplicationDbContext context)
public PizzaController(PizzaNotificationService pizzaNotifier,MenuService menuService, ApplicationDbContext context)
{
_menuService = menuService;
_context = context;
_pizzaNotifier = pizzaNotifier;
}
[HttpGet]
@@ -50,9 +54,13 @@ namespace Aberwyn.Controllers
_context.PizzaOrders.Add(order);
_context.SaveChanges();
_pizzaNotifier.NotifyPizzaSubscribersAsync(pizzaName, customerName);
TempData["Success"] = "Beställningen har lagts!";
return RedirectToAction("Order");
}
}
}

View File

@@ -3,6 +3,9 @@ using Aberwyn.Models;
using Lib.Net.Http.WebPush;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Aberwyn.Services;
namespace Aberwyn.Controllers
{
@@ -12,41 +15,95 @@ namespace Aberwyn.Controllers
{
private readonly ApplicationDbContext _context;
private readonly PushNotificationService _notificationService;
private readonly UserManager<ApplicationUser> _userManager;
private readonly PizzaNotificationService _pizzaNotifier;
public PushController(ApplicationDbContext context, PushNotificationService notificationService)
public PushController(ApplicationDbContext context,
PushNotificationService notificationService,
UserManager<ApplicationUser> userManager,
PizzaNotificationService pizzaNotifier)
{
_context = context;
_notificationService = notificationService;
_userManager = userManager;
_pizzaNotifier = pizzaNotifier;
}
[HttpPost("notify-pizza")]
public async Task<IActionResult> NotifyPizza()
{
var count = await _pizzaNotifier.NotifyPizzaSubscribersAsync("Capricciosa", User.Identity.Name);
[HttpPost("subscribe")]
public async Task<IActionResult> Subscribe([FromBody] PushSubscription subscription)
return Ok(new { message = $"Skickade pizzanotiser till {count} användare." });
}
[HttpPost("subscribe-user")]
public async Task<IActionResult> SubscribeUser([FromBody] PushSubscription subscription)
{
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"]
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)
{
@@ -62,7 +119,12 @@ namespace Aberwyn.Controllers
return BadRequest("Titel och meddelande krävs.");
}
var subscribers = await _context.PushSubscribers.ToListAsync();
var subscribers = await _context.PushSubscribers
.Include(s => s.User)
.ThenInclude(u => u.Preferences)
.Where(s => s.User != null && s.User.Preferences.NotifyPizza)
.ToListAsync();
var payload = $"{{\"title\":\"{message.Title}\",\"body\":\"{message.Body}\"}}";
int successCount = 0;
@@ -82,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; }
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}
}

View 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" });
}
}
}

View File

@@ -0,0 +1,78 @@
using Microsoft.AspNetCore.Mvc;
using MySql.Data.MySqlClient;
namespace Aberwyn.Controllers
{
[ApiController]
[Route("api/setup")]
public class SetupApiController : ControllerBase
{
[HttpPost("testdb")]
public IActionResult TestDbConnection([FromBody] DbTestRequest request)
{
try
{
var builder = new MySqlConnectionStringBuilder
{
Server = request.Host,
Port = uint.Parse(request.Port),
UserID = request.User,
Password = request.Pass,
Database = "information_schema"
};
using (var conn = new MySqlConnection(builder.ConnectionString))
{
conn.Open();
// Kontrollera om databasen redan finns
var checkCmd = new MySqlCommand("SELECT SCHEMA_NAME FROM SCHEMATA WHERE SCHEMA_NAME = @dbName", conn);
checkCmd.Parameters.AddWithValue("@dbName", request.Db);
var exists = checkCmd.ExecuteScalar();
if (exists != null)
{
return Ok(new
{
success = true,
message = "Anslutning OK och databasen finns redan."
});
}
// Testa skapa en temporär databas
var testDbName = $"testcheck_{Guid.NewGuid():N}".Substring(0, 12);
var createCmd = new MySqlCommand($"CREATE DATABASE `{testDbName}`", conn);
createCmd.ExecuteNonQuery();
var dropCmd = new MySqlCommand($"DROP DATABASE `{testDbName}`", conn);
dropCmd.ExecuteNonQuery();
return Ok(new
{
success = true,
message = "Anslutning OK. Databasen finns inte, men CREATE DATABASE är tillåten."
});
}
}
catch (Exception ex)
{
return Ok(new
{
success = false,
message = ex.Message
});
}
}
public class DbTestRequest
{
public string Host { get; set; }
public string Port { get; set; }
public string Db { get; set; }
public string User { get; set; }
public string Pass { get; set; }
}
}
}

View File

@@ -0,0 +1,173 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using MySql.Data.MySqlClient;
using System.Text.Json;
using Aberwyn.Data;
using Aberwyn.Models;
namespace Aberwyn.Controllers
{
[Route("setup")]
public class SetupController : Controller
{
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)
{
ViewBag.IsSetupMode = true;
base.OnActionExecuting(context);
}
[HttpGet]
public IActionResult Index() => View(new SetupSettings());
[Authorize(Roles = "Admin")]
[HttpPost("reset")]
public IActionResult Reset()
{
var path = _filePath;
var resetSettings = new SetupSettings
{
IsConfigured = false,
DbHost = "",
DbPort = 3306,
DbName = "",
DbUser = "",
DbPassword = "",
AdminUsername = "admin",
AdminEmail = "admin@localhost",
AdminPassword = "Admin123!"
};
var json = JsonSerializer.Serialize(resetSettings, new JsonSerializerOptions { WriteIndented = true });
System.IO.File.WriteAllText(path, json);
return RedirectToAction("Index");
}
[HttpPost("")]
public async Task<IActionResult> Setup([FromBody] SetupSettings model)
{
if (!ModelState.IsValid)
{
var allErrors = ModelState
.Where(e => e.Value.Errors.Count > 0)
.Select(e => new { Field = e.Key, Errors = e.Value.Errors.Select(x => x.ErrorMessage) });
return BadRequest(new { error = "Modellen är ogiltig", details = allErrors });
}
try
{
_logger.LogDebug("Start setup write");
// Bygg connection string säkert
var baseConnBuilder = new MySqlConnectionStringBuilder
{
Server = model.DbHost,
Port = (uint)model.DbPort,
UserID = model.DbUser,
Password = model.DbPassword,
Database = "information_schema"
};
// Kontrollera om databasen redan finns
using (var conn = new MySqlConnection(baseConnBuilder.ConnectionString))
{
conn.Open();
var cmd = new MySqlCommand("SELECT SCHEMA_NAME FROM SCHEMATA WHERE SCHEMA_NAME = @dbName", conn);
cmd.Parameters.AddWithValue("@dbName", model.DbName);
var exists = cmd.ExecuteScalar();
if (exists == null)
{
try
{
var createCmd = new MySqlCommand($"CREATE DATABASE `{model.DbName}`", conn);
createCmd.ExecuteNonQuery();
}
catch (Exception ex)
{
_logger.LogError(ex, "Kunde inte skapa databasen.");
return BadRequest(new { error = "Databasen finns inte och kunde inte skapas.", details = ex.Message });
}
}
}
// Bygg EF-connection
var efConnBuilder = new MySqlConnectionStringBuilder
{
Server = model.DbHost,
Port = (uint)model.DbPort,
UserID = model.DbUser,
Password = model.DbPassword,
Database = model.DbName
};
var tempProvider = SetupService.BuildTemporaryServices(efConnBuilder.ConnectionString);
using var scope = tempProvider.CreateScope();
// Skapa databastabeller
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await db.Database.MigrateAsync();
// Sätt konfig-flagga tidigt
model.IsConfigured = true;
_logger.LogDebug("Filepath" + _filePath);
// Spara setup.json
var json = JsonSerializer.Serialize(model, new JsonSerializerOptions { WriteIndented = true });
System.IO.File.WriteAllText(_filePath, json);
// Roller och admin
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
string[] roles = { "Admin", "Chef", "Budget" };
foreach (var role in roles)
{
if (!await roleManager.RoleExistsAsync(role))
await roleManager.CreateAsync(new IdentityRole(role));
}
var existingUser = await userManager.FindByNameAsync(model.AdminUsername);
if (existingUser == null)
{
var adminUser = new ApplicationUser
{
UserName = model.AdminUsername,
Email = model.AdminEmail,
EmailConfirmed = true
};
var result = await userManager.CreateAsync(adminUser, model.AdminPassword);
if (!result.Succeeded)
return BadRequest(new { error = "Kunde inte skapa administratör", details = result.Errors });
await userManager.AddToRoleAsync(adminUser, "Admin");
}
return Ok(new { message = "Installation slutförd!" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Fel vid installation.");
return BadRequest(new { error = "Fel vid installation", details = ex.Message });
}
}
public IActionResult SetupComplete() => View();
}
}

View File

@@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Aberwyn.Models;
using System.Threading.Tasks;
using Aberwyn.Data;
namespace Aberwyn.Controllers
{
[Authorize]
public class UserController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _context;
public UserController(UserManager<ApplicationUser> userManager, ApplicationDbContext context)
{
_userManager = userManager;
_context = context;
}
[HttpGet]
public async Task<IActionResult> Profile()
{
var user = await _userManager.GetUserAsync(User);
var prefs = await _context.UserPreferences.FindAsync(user.Id) ?? new UserPreferences();
var model = new UserProfileViewModel
{
Name = user.UserName,
Email = user.Email,
NotifyPizza = prefs.NotifyPizza,
NotifyMenu = prefs.NotifyMenu,
NotifyBudget = prefs.NotifyBudget
};
return View(model);
}
[HttpPost]
public async Task<IActionResult> SaveProfile(UserProfileViewModel model)
{
var user = await _userManager.GetUserAsync(User);
var prefs = await _context.UserPreferences.FindAsync(user.Id);
if (prefs == null)
{
prefs = new UserPreferences { UserId = user.Id };
_context.UserPreferences.Add(prefs);
}
prefs.NotifyPizza = model.NotifyPizza;
prefs.NotifyMenu = model.NotifyMenu;
prefs.NotifyBudget = model.NotifyBudget;
await _context.SaveChangesAsync();
return RedirectToAction("Profile");
}
}
}

View File

@@ -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; }
@@ -31,6 +55,20 @@ namespace Aberwyn.Data
public DbSet<Meal> Meals { get; set; }
public DbSet<WeeklyMenu> WeeklyMenus { get; set; }
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; }
}
}

View File

@@ -1,8 +1,11 @@
using Microsoft.EntityFrameworkCore;
using Aberwyn.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using System.Text.Json;
using static Aberwyn.Data.SetupService;
namespace Aberwyn.Data
{
@@ -10,47 +13,51 @@ namespace Aberwyn.Data
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var basePath = Directory.GetCurrentDirectory();
var config = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile("appsettings.Development.json", optional: true)
.AddEnvironmentVariables()
.Build();
var setup = LoadSetup();
var connectionString = config.GetConnectionString("DefaultConnection");
File.WriteAllText("connection-log.txt", $"Connection string: {connectionString}");
Console.WriteLine($"Anslutningssträng: {connectionString}");
if (string.IsNullOrEmpty(connectionString))
var csBuilder = new MySqlConnector.MySqlConnectionStringBuilder
{
throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
}
Server = setup.DbHost,
Port = (uint)setup.DbPort,
Database = setup.DbName,
UserID = setup.DbUser,
Password = setup.DbPassword,
AllowUserVariables = true
};
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseMySql(
connectionString,
new MySqlServerVersion(new Version(8, 0, 36)));
optionsBuilder.UseMySql(csBuilder.ConnectionString, new MySqlServerVersion(new Version(8, 0, 36)));
return new ApplicationDbContext(optionsBuilder.Options);
}
public static ApplicationDbContext CreateWithConfig(IConfiguration config, IHostEnvironment env, bool useProdDb = false)
public static ApplicationDbContext CreateWithConfig(IHostEnvironment env, bool useProdDb = false)
{
var connectionString = useProdDb
? config.GetConnectionString("ProdConnection") // <--- FIX HÄR
: config.GetConnectionString("DefaultConnection");
if (string.IsNullOrWhiteSpace(connectionString))
throw new InvalidOperationException("Connection string saknas.");
var setup = SetupLoader.Load(env);
var connStr = SetupLoader.GetConnectionString(setup);
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 36)));
optionsBuilder.UseMySql(connStr, new MySqlServerVersion(new Version(8, 0, 36)));
return new ApplicationDbContext(optionsBuilder.Options);
}
private static SetupSettings LoadSetup()
{
var basePath = Directory.GetCurrentDirectory();
var setupPath = Path.Combine(basePath, "infrastructure", "setup.json");
if (!File.Exists(setupPath))
throw new FileNotFoundException("setup.json saknas i infrastructure-mappen.");
var json = File.ReadAllText(setupPath);
var setup = JsonSerializer.Deserialize<SetupSettings>(json);
if (setup == null || !setup.IsConfigured)
throw new InvalidOperationException("setup.json är inte korrekt konfigurerad.");
return setup;
}
}
}

View File

@@ -1,148 +0,0 @@
using MySql.Data.MySqlClient;
using System.Collections.Generic;
using Aberwyn.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; // Add this namespace
namespace Aberwyn.Data
{
public class BudgetService
{
private readonly IConfiguration _configuration;
private readonly IHostEnvironment _env; // Add this field
public BudgetService(IConfiguration configuration, IHostEnvironment env) // Update constructor
{
_configuration = configuration;
_env = env; // Initialize the environment field
}
public MySqlConnection GetConnection()
{
var connectionString = _env.IsDevelopment() // Use the injected environment variable
? _configuration.GetConnectionString("DefaultConnection")
: _configuration.GetConnectionString("ProductionConnection");
return new MySqlConnection(connectionString);
}
public bool UpdateBudgetItem(BudgetItem item)
{
using (var connection = GetConnection())
{
connection.Open();
string query = @"
UPDATE tblBudgetItems
SET Name = @name, Amount = @amount
WHERE idtblBudgetItems = @id";
using (var cmd = new MySqlCommand(query, connection))
{
cmd.Parameters.AddWithValue("@name", item.Name);
cmd.Parameters.AddWithValue("@amount", item.Amount);
cmd.Parameters.AddWithValue("@id", item.ID);
return cmd.ExecuteNonQuery() > 0; // Returns true if one or more rows are updated
}
}
}
public List<BudgetItem> GetBudgetItems(int month, int year)
{
var budgetItems = new List<BudgetItem>();
using (var connection = GetConnection())
{
connection.Open();
string query = @"
SELECT
b.idtblBudgetItems AS id,
b.Name AS item_name,
b.Amount AS amount,
c1.Name AS category,
b.Month,
b.Year,
b.Description AS description
FROM tblBudgetItems b
LEFT JOIN tblCategories c1 ON b.Category = c1.idtblCategories
WHERE b.Month = @month AND b.Year = @year";
using (var cmd = new MySqlCommand(query, connection))
{
cmd.Parameters.AddWithValue("@month", month);
cmd.Parameters.AddWithValue("@year", year);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
budgetItems.Add(new BudgetItem
{
ID = reader.GetInt32("id"),
Name = reader.GetString("item_name"), // Updated alias
Amount = reader.GetDecimal("amount"),
Category = reader.GetString("category"),
Month = reader.GetInt32("Month"),
Year = reader.GetInt32("Year"),
Description = reader.IsDBNull(reader.GetOrdinal("description"))
? null
: reader.GetString("description")
});
}
}
}
}
return budgetItems;
}
public bool AddBudgetItem(BudgetItem item)
{
using (var connection = GetConnection())
{
connection.Open();
string query = @"
INSERT INTO tblBudgetItems (Name, Amount, Category, Month, Year)
VALUES (@name, @amount, @category, @month, @year)";
using (var cmd = new MySqlCommand(query, connection))
{
cmd.Parameters.AddWithValue("@name", item.Name);
cmd.Parameters.AddWithValue("@amount", item.Amount);
cmd.Parameters.AddWithValue("@category", item.Category);
cmd.Parameters.AddWithValue("@month", item.Month);
cmd.Parameters.AddWithValue("@year", item.Year);
return cmd.ExecuteNonQuery() > 0; // Returns true if a row was inserted
}
}
}*/
// New method to fetch all categories
public List<string> GetCategories()
{
var categories = new List<string>();
using (var connection = GetConnection())
{
connection.Open();
string query = "SELECT Name FROM tblCategories"; // Adjust based on your table structure
using (var cmd = new MySqlCommand(query, connection))
{
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
categories.Add(reader.GetString("Name"));
}
}
}
}
return categories;
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Aberwyn.Data
{
public interface IRssProcessor
{
Task ProcessRssFeeds();
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Aberwyn.Models;
@@ -6,10 +7,16 @@ namespace Aberwyn.Data
{
public static class IdentityDataInitializer
{
public static async Task SeedData(IServiceProvider serviceProvider)
public static async Task<IdentityResult> SeedData(IServiceProvider serviceProvider, SetupSettings? setup = null)
{
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var config = serviceProvider.GetService<IConfiguration>();
if (setup == null && config != null)
{
setup = config.GetSection("SetupSettings").Get<SetupSettings>() ?? new SetupSettings();
}
string[] roles = { "Admin", "Chef", "Budget" };
@@ -19,26 +26,38 @@ namespace Aberwyn.Data
await roleManager.CreateAsync(new IdentityRole(role));
}
string adminUsername = "admin";
string adminEmail = "admin@localhost";
string password = "Admin123!";
if (await userManager.FindByEmailAsync(adminEmail) == null)
var existingUser = await userManager.FindByEmailAsync(setup.AdminEmail);
if (existingUser == null)
{
var user = new ApplicationUser
{
UserName = adminUsername,
Email = adminEmail,
UserName = setup.AdminUsername,
Email = setup.AdminEmail,
EmailConfirmed = true
};
var result = await userManager.CreateAsync(user, password);
var result = await userManager.CreateAsync(user, setup.AdminPassword);
if (result.Succeeded)
await userManager.AddToRoleAsync(user, "Admin");
return result;
}
else
{
var token = await userManager.GeneratePasswordResetTokenAsync(existingUser);
var result = await userManager.ResetPasswordAsync(existingUser, token, setup.AdminPassword);
if (result.Succeeded)
{
await userManager.AddToRoleAsync(user, "Admin");
var rolesForUser = await userManager.GetRolesAsync(existingUser);
if (!rolesForUser.Contains("Admin"))
await userManager.AddToRoleAsync(existingUser, "Admin");
}
return result;
}
}
}
}

View File

@@ -1,8 +1,11 @@
// Nya versionen av MenuService med Entity Framework
using Aberwyn.Models;
using Microsoft.EntityFrameworkCore;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Processing;
using System.Globalization;
using static Aberwyn.Data.SetupService;
namespace Aberwyn.Data
{
@@ -16,20 +19,47 @@ public class MenuService
_context = context;
}
// Detta är en alternativ konstruktör används manuellt vid t.ex. import
public static MenuService CreateWithConfig(IConfiguration config, IHostEnvironment env, bool useProdDb = false)
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
public static MenuService CreateWithSetup(IHostEnvironment env)
{
var setup = SetupLoader.Load(env);
var connStr = SetupLoader.GetConnectionString(setup);
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
var context = new ApplicationDbContext(builder.Options);
return new MenuService(context);
}
public List<WeeklyMenuDto> GetWeeklyMenuDto(int weekNumber, int year)
{
var query = from wm in _context.WeeklyMenus
where wm.WeekNumber == weekNumber && wm.Year == year
join mDinner in _context.Meals on wm.DinnerMealId equals mDinner.Id into dinnerJoin
from mDinner in dinnerJoin.DefaultIfEmpty()
join mLunch in _context.Meals on wm.LunchMealId equals mLunch.Id into lunchJoin
from mLunch in lunchJoin.DefaultIfEmpty()
join mBreakfast in _context.Meals on wm.BreakfastMealId equals mBreakfast.Id into breakfastJoin
from mBreakfast in breakfastJoin.DefaultIfEmpty()
select new WeeklyMenuDto
{
Id = wm.Id,
DayOfWeek = wm.DayOfWeek,
WeekNumber = wm.WeekNumber,
Year = wm.Year,
BreakfastMealId = wm.BreakfastMealId,
LunchMealId = wm.LunchMealId,
DinnerMealId = wm.DinnerMealId,
BreakfastMealName = mBreakfast.Name,
LunchMealName = mLunch.Name,
DinnerMealName = mDinner.Name,
DinnerMealThumbnail = mDinner.ThumbnailData
};
return query.ToList();
}
var connStr = useProdDb
? config.GetConnectionString("ProdConnection")
: config.GetConnectionString("DefaultConnection");
builder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
var context = new ApplicationDbContext(builder.Options);
return new MenuService(context);
}
public void UpdateWeeklyMenu(MenuViewModel model)
{
var existing = _context.WeeklyMenus
@@ -47,7 +77,30 @@ public class MenuService
}
public void SaveMeal2(Meal meal)
{
if (string.IsNullOrWhiteSpace(meal?.Name)) return;
meal.Name = meal.Name.Trim();
meal.CreatedAt = meal.CreatedAt == default ? DateTime.Now : meal.CreatedAt;
var existing = _context.Meals
.AsNoTracking()
.FirstOrDefault(m => m.Id == meal.Id);
if (existing == null)
{
// Nytt objekt försök behålla ID:t från prod
_context.Entry(meal).State = EntityState.Added;
}
else
{
// Befintlig uppdatera
_context.Meals.Update(meal);
}
_context.SaveChanges();
}
public void SaveMeal(Meal meal)
{
@@ -57,15 +110,90 @@ public class MenuService
meal.CreatedAt = meal.CreatedAt == default ? DateTime.Now : meal.CreatedAt;
if (meal.Id == 0)
{
// Ny måltid
_context.Meals.Add(meal);
}
else
_context.Meals.Update(meal);
{
// Uppdatera existerande utan tracking-krockar
var existing = _context.Meals
.Include(m => m.Ingredients)
.FirstOrDefault(m => m.Id == meal.Id);
if (existing != null)
{
_context.Entry(existing).CurrentValues.SetValues(meal);
// OBS: Ingredienser hanteras separat
}
}
_context.SaveChanges();
}
public List<WeeklyMenu> GetAllWeeklyMenus()
public int GenerateMissingThumbnails()
{
var updatedCount = 0;
var meals = _context.Meals
.Where(m => m.ImageData != null && m.ThumbnailData == null)
.ToList();
foreach (var meal in meals)
{
using var ms = new MemoryStream(meal.ImageData);
using var image = Image.Load(ms);
image.Mutate(x => x.Resize(new ResizeOptions
{
Mode = ResizeMode.Max,
Size = new Size(300, 300)
}));
using var outStream = new MemoryStream();
var encoder = new WebpEncoder
{
Quality = 75
};
image.Save(outStream, encoder);
meal.ThumbnailData = outStream.ToArray();
updatedCount++;
}
_context.SaveChanges();
return updatedCount;
}
public List<Meal> GetMealsSlim(bool includeThumbnail = false)
{
if (includeThumbnail)
{
return _context.Meals
.Select(m => new Meal
{
Id = m.Id,
Name = m.Name,
Description = m.Description,
ThumbnailData = m.ThumbnailData
})
.ToList();
}
else
{
return _context.Meals
.Select(m => new Meal
{
Id = m.Id,
Name = m.Name,
Description = m.Description
})
.ToList();
}
}
public List<WeeklyMenu> GetAllWeeklyMenus()
{
var menus = _context.WeeklyMenus.ToList();
var allMeals = _context.Meals.ToDictionary(m => m.Id, m => m.Name);
@@ -100,10 +228,17 @@ public List<WeeklyMenu> GetAllWeeklyMenus()
{
var existing = _context.Ingredients.Where(i => i.MealId == mealId);
_context.Ingredients.RemoveRange(existing);
foreach (var ing in ingredients)
{
ing.MealId = mealId;
}
_context.Ingredients.AddRange(ingredients);
_context.SaveChanges();
}
public List<Meal> GetMeals()
{
return _context.Meals.ToList();
@@ -112,10 +247,12 @@ public List<WeeklyMenu> GetAllWeeklyMenus()
public List<Meal> GetMealsDetailed()
{
return _context.Meals
.Include(m => m.Ingredients) // 🧠 detta behövs!
.OrderByDescending(m => m.CreatedAt)
.ToList();
}
public Meal GetMealById(int id)
{
var meal = _context.Meals
@@ -141,6 +278,8 @@ public List<WeeklyMenu> GetAllWeeklyMenus()
public void SaveOrUpdateMealWithIngredients(Meal meal)
{
var isNew = meal.Id == 0;
SaveMeal(meal);
if (meal.Ingredients != null && meal.Ingredients.Count > 0)
@@ -149,41 +288,123 @@ public List<WeeklyMenu> GetAllWeeklyMenus()
}
}
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();
}
public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
{
var menus = _context.WeeklyMenus
.Where(m => m.WeekNumber == weekNumber && m.Year == year)
.ToList();
public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
{
var menus = _context.WeeklyMenus
.Where(m => m.WeekNumber == weekNumber && m.Year == year)
.ToList();
var mealIds = menus
.SelectMany(w => new int?[] { w.BreakfastMealId, w.LunchMealId, w.DinnerMealId })
.Where(id => id.HasValue)
.Select(id => id.Value)
.Distinct()
.ToList();
var allMeals = _context.Meals
.Where(m => mealIds.Contains(m.Id))
.Select(m => new {
m.Id,
m.Name,
m.ThumbnailData // Vi tar med detta även om det bara används för middag
})
.ToList()
.ToDictionary(m => m.Id, m => m);
var allMeals = _context.Meals.ToDictionary(m => m.Id, m => m.Name);
foreach (var wm in menus)
{
wm.BreakfastMealName = wm.BreakfastMealId.HasValue && allMeals.TryGetValue(wm.BreakfastMealId.Value, out var breakfast)
? breakfast
: null;
if (wm.BreakfastMealId.HasValue && allMeals.TryGetValue(wm.BreakfastMealId.Value, out var breakfast))
wm.BreakfastMealName = breakfast.Name;
wm.LunchMealName = wm.LunchMealId.HasValue && allMeals.TryGetValue(wm.LunchMealId.Value, out var lunch)
? lunch
: null;
if (wm.LunchMealId.HasValue && allMeals.TryGetValue(wm.LunchMealId.Value, out var lunch))
wm.LunchMealName = lunch.Name;
wm.DinnerMealName = wm.DinnerMealId.HasValue && allMeals.TryGetValue(wm.DinnerMealId.Value, out var dinner)
? dinner
: null;
if (wm.DinnerMealId.HasValue && allMeals.TryGetValue(wm.DinnerMealId.Value, out var dinner))
{
wm.DinnerMealName = dinner.Name;
}
}
return menus;
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)
{
var results = new List<WeeklyMenu>();
@@ -214,5 +435,128 @@ public List<WeeklyMenu> GetAllWeeklyMenus()
}
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
}
}

View 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; } = "";
}
}

View File

@@ -0,0 +1,76 @@
using Aberwyn.Data;
using Microsoft.EntityFrameworkCore;
using WebPush;
namespace Aberwyn.Services
{
public class PizzaNotificationService
{
private readonly ApplicationDbContext _context;
private readonly PushNotificationService _push;
public PizzaNotificationService(ApplicationDbContext context, PushNotificationService push)
{
Console.WriteLine("🍕 PizzaNotificationService constructor körs!");
_context = context;
_push = push;
}
public async Task<int> NotifyPizzaSubscribersAsync(string pizzaName = null, string userName = null)
{
var title = "Ny pizzabeställning 🍕";
var body = "En pizza har precis beställts!";
if (!string.IsNullOrWhiteSpace(pizzaName))
body = $"Pizzan '{pizzaName}' har precis beställts!";
if (!string.IsNullOrWhiteSpace(userName))
body += $" (av {userName})";
var payload = $@"{{""title"":""{title}"",""body"":""{body}""}}";
var subscribers = await _context.PushSubscribers
.Include(s => s.User)
.ThenInclude(u => u.Preferences)
.Where(s => s.User != null &&
s.User.Preferences != null &&
s.User.Preferences.NotifyPizza &&
!string.IsNullOrEmpty(s.Endpoint) &&
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)
{
try
{
_push.SendNotification(sub.Endpoint, sub.P256DH, sub.Auth, payload);
successCount++;
}
catch (WebPushException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Gone || ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
Console.WriteLine($"🗑️ Ogiltig prenumeration tas bort: {sub.Endpoint}");
_context.PushSubscribers.Remove(sub);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
Console.WriteLine($"❌ Misslyckades att skicka till {sub.Endpoint}: {ex.Message}");
}
}
return successCount;
}
}
}

View 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();
}
}
}
}

View File

@@ -0,0 +1,76 @@
using Aberwyn.Models;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using System.IO;
using System.Text.Json;
using Microsoft.Extensions.Hosting;
namespace Aberwyn.Data
{
// SetupService.cs
public class SetupService
{
private readonly IWebHostEnvironment _env;
private readonly string _filePath;
public SetupService(IWebHostEnvironment env)
{
_env = env;
var dataRoot = Path.Combine(Directory.GetCurrentDirectory(), "data"); // /app/data i containern
_filePath = Path.Combine(dataRoot, "infrastructure", "setup.json");
}
public SetupSettings GetSetup()
{
if (!File.Exists(_filePath))
return new SetupSettings { IsConfigured = false };
var json = File.ReadAllText(_filePath);
return JsonSerializer.Deserialize<SetupSettings>(json) ?? new SetupSettings { IsConfigured = false };
}
internal static IServiceProvider BuildTemporaryServices(string connectionString)
{
var services = new ServiceCollection();
// Konfigurera EF + Identity
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Lägg till en tom konfiguration för att undvika null
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
// Valfritt: Lägg till loggning om något kräver det
services.AddLogging();
return services.BuildServiceProvider();
}
public static class SetupLoader
{
public static SetupSettings Load(IHostEnvironment env)
{
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)!;
}
public static string GetConnectionString(SetupSettings setup)
{
return $"server={setup.DbHost};port={setup.DbPort};database={setup.DbName};user={setup.DbUser};password={setup.DbPassword}";
}
}
}
}

View 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);
}
}
}
}

View 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;
}
}

View 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"
}

View File

@@ -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,17 +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
COPY ["Aberwyn.csproj", "."]
# 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"
COPY . .
RUN dotnet build "Aberwyn.csproj" -c Release -o /app/build
# Kopiera övrig kod
COPY Aberwyn/. .
# 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"]

View File

@@ -2,22 +2,22 @@ version: '3.8'
services:
aberwyn-app:
image: aberwyn:latest
build:
context: ../
dockerfile: Dockerfile
image: 192.168.1.9:3000/tai/aberwyn/aberwyn:latest
container_name: aberwyn-app-prod
ports:
- "8080:80"
environment:
ASPNETCORE_ENVIRONMENT: Production
DB_HOST: aberwyn-mysql-prod
DB_NAME: aberwyn_prod
DB_USER: aberwyn
DB_PASSWORD: 3edc4RFV
depends_on:
- mysql
- aberwyn-mysql-prod
networks:
- aberwyn-net
mysql:
aberwyn-mysql-prod:
image: mysql:8
container_name: aberwyn-mysql-prod
restart: always

View 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"
}

View File

@@ -0,0 +1,848 @@
// <auto-generated />
using System;
using Aberwyn.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Aberwyn.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250618203117_AddPaymentStatusToBudgetItem")]
partial class AddPaymentStatusToBudgetItem
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.36")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.AppSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("AppSettings");
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BudgetCategoryDefinitionId")
.HasColumnType("int");
b.Property<int>("BudgetPeriodId")
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryDefinitionId");
b.HasIndex("BudgetPeriodId");
b.ToTable("BudgetCategories");
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategoryDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("BudgetCategoryDefinitions");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<int>("BudgetCategoryId")
.HasColumnType("int");
b.Property<int?>("BudgetItemDefinitionId")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("PaymentStatus")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryId");
b.HasIndex("BudgetItemDefinitionId");
b.ToTable("BudgetItems");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItemDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("DefaultCategory")
.HasColumnType("longtext");
b.Property<int?>("DefaultPaymentStatus")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("BudgetItemDefinitions");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Month")
.HasColumnType("int");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Item")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("MealId")
.HasColumnType("int");
b.Property<string>("Quantity")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("MealId");
b.ToTable("Ingredients");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CarbType")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<byte[]>("ImageData")
.HasColumnType("longblob");
b.Property<string>("ImageMimeType")
.HasColumnType("longtext");
b.Property<string>("ImageUrl")
.HasColumnType("longtext");
b.Property<string>("Instructions")
.HasColumnType("longtext");
b.Property<bool>("IsAvailable")
.HasColumnType("tinyint(1)");
b.Property<int?>("MealCategoryId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProteinType")
.HasColumnType("longtext");
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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CustomerName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("IngredientsJson")
.HasColumnType("longtext");
b.Property<DateTime>("OrderedAt")
.HasColumnType("datetime(6)");
b.Property<string>("PizzaName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PizzaOrders");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", 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<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.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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AssignedTo")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("IsArchived")
.HasColumnType("tinyint(1)");
b.Property<int>("Priority")
.HasColumnType("int");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BreakfastMealId")
.HasColumnType("int");
b.Property<string>("Cook")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int>("DayOfWeek")
.HasColumnType("int");
b.Property<int?>("DinnerMealId")
.HasColumnType("int");
b.Property<int?>("LunchMealId")
.HasColumnType("int");
b.Property<int>("WeekNumber")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("WeeklyMenu", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategoryDefinition", "Definition")
.WithMany()
.HasForeignKey("BudgetCategoryDefinitionId");
b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
.WithMany("Categories")
.HasForeignKey("BudgetPeriodId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BudgetPeriod");
b.Navigation("Definition");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategory", null)
.WithMany("Items")
.HasForeignKey("BudgetCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.BudgetItemDefinition", "BudgetItemDefinition")
.WithMany()
.HasForeignKey("BudgetItemDefinitionId");
b.Navigation("BudgetItemDefinition");
});
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
{
b.HasOne("Aberwyn.Models.Meal", null)
.WithMany("Ingredients")
.HasForeignKey("MealId")
.OnDelete(DeleteBehavior.Cascade)
.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")
.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)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Navigation("Preferences")
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Navigation("Items");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Navigation("Categories");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.Navigation("Ingredients");
});
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
{
b.Navigation("Meals");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -0,0 +1,851 @@
// <auto-generated />
using System;
using Aberwyn.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Aberwyn.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250624150426_AddIsPublishedToMeals")]
partial class AddIsPublishedToMeals
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.36")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.AppSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("AppSettings");
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BudgetCategoryDefinitionId")
.HasColumnType("int");
b.Property<int>("BudgetPeriodId")
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryDefinitionId");
b.HasIndex("BudgetPeriodId");
b.ToTable("BudgetCategories");
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategoryDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("BudgetCategoryDefinitions");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<int>("BudgetCategoryId")
.HasColumnType("int");
b.Property<int?>("BudgetItemDefinitionId")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("PaymentStatus")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryId");
b.HasIndex("BudgetItemDefinitionId");
b.ToTable("BudgetItems");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItemDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("DefaultCategory")
.HasColumnType("longtext");
b.Property<int?>("DefaultPaymentStatus")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("BudgetItemDefinitions");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Month")
.HasColumnType("int");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Item")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("MealId")
.HasColumnType("int");
b.Property<string>("Quantity")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("MealId");
b.ToTable("Ingredients");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CarbType")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<byte[]>("ImageData")
.HasColumnType("longblob");
b.Property<string>("ImageMimeType")
.HasColumnType("longtext");
b.Property<string>("ImageUrl")
.HasColumnType("longtext");
b.Property<string>("Instructions")
.HasColumnType("longtext");
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");
b.Property<string>("ProteinType")
.HasColumnType("longtext");
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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CustomerName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("IngredientsJson")
.HasColumnType("longtext");
b.Property<DateTime>("OrderedAt")
.HasColumnType("datetime(6)");
b.Property<string>("PizzaName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PizzaOrders");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", 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<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.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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AssignedTo")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("IsArchived")
.HasColumnType("tinyint(1)");
b.Property<int>("Priority")
.HasColumnType("int");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BreakfastMealId")
.HasColumnType("int");
b.Property<string>("Cook")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int>("DayOfWeek")
.HasColumnType("int");
b.Property<int?>("DinnerMealId")
.HasColumnType("int");
b.Property<int?>("LunchMealId")
.HasColumnType("int");
b.Property<int>("WeekNumber")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("WeeklyMenu", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategoryDefinition", "Definition")
.WithMany()
.HasForeignKey("BudgetCategoryDefinitionId");
b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
.WithMany("Categories")
.HasForeignKey("BudgetPeriodId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BudgetPeriod");
b.Navigation("Definition");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategory", null)
.WithMany("Items")
.HasForeignKey("BudgetCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.BudgetItemDefinition", "BudgetItemDefinition")
.WithMany()
.HasForeignKey("BudgetItemDefinitionId");
b.Navigation("BudgetItemDefinition");
});
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
{
b.HasOne("Aberwyn.Models.Meal", null)
.WithMany("Ingredients")
.HasForeignKey("MealId")
.OnDelete(DeleteBehavior.Cascade)
.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")
.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)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Navigation("Preferences")
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Navigation("Items");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Navigation("Categories");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.Navigation("Ingredients");
});
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
{
b.Navigation("Meals");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Aberwyn.Migrations
{
public partial class AddIsPublishedToMeals : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsPublished",
table: "Meals",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsPublished",
table: "Meals");
}
}
}

View File

@@ -0,0 +1,949 @@
// <auto-generated />
using System;
using Aberwyn.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Aberwyn.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250630123620_AddRecipeLab")]
partial class AddRecipeLab
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.36")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.AppSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("AppSettings");
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BudgetCategoryDefinitionId")
.HasColumnType("int");
b.Property<int>("BudgetPeriodId")
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryDefinitionId");
b.HasIndex("BudgetPeriodId");
b.ToTable("BudgetCategories");
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategoryDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("BudgetCategoryDefinitions");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<int>("BudgetCategoryId")
.HasColumnType("int");
b.Property<int?>("BudgetItemDefinitionId")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("PaymentStatus")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryId");
b.HasIndex("BudgetItemDefinitionId");
b.ToTable("BudgetItems");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItemDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("DefaultCategory")
.HasColumnType("longtext");
b.Property<int?>("DefaultPaymentStatus")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("BudgetItemDefinitions");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Month")
.HasColumnType("int");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Item")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("MealId")
.HasColumnType("int");
b.Property<string>("Quantity")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("MealId");
b.ToTable("Ingredients");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CarbType")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<byte[]>("ImageData")
.HasColumnType("longblob");
b.Property<string>("ImageMimeType")
.HasColumnType("longtext");
b.Property<string>("ImageUrl")
.HasColumnType("longtext");
b.Property<string>("Instructions")
.HasColumnType("longtext");
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");
b.Property<string>("ProteinType")
.HasColumnType("longtext");
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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CustomerName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("IngredientsJson")
.HasColumnType("longtext");
b.Property<DateTime>("OrderedAt")
.HasColumnType("datetime(6)");
b.Property<string>("PizzaName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PizzaOrders");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", 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<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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AssignedTo")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("IsArchived")
.HasColumnType("tinyint(1)");
b.Property<int>("Priority")
.HasColumnType("int");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
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")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BreakfastMealId")
.HasColumnType("int");
b.Property<string>("Cook")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int>("DayOfWeek")
.HasColumnType("int");
b.Property<int?>("DinnerMealId")
.HasColumnType("int");
b.Property<int?>("LunchMealId")
.HasColumnType("int");
b.Property<int>("WeekNumber")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("WeeklyMenu", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategoryDefinition", "Definition")
.WithMany()
.HasForeignKey("BudgetCategoryDefinitionId");
b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
.WithMany("Categories")
.HasForeignKey("BudgetPeriodId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BudgetPeriod");
b.Navigation("Definition");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategory", null)
.WithMany("Items")
.HasForeignKey("BudgetCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.BudgetItemDefinition", "BudgetItemDefinition")
.WithMany()
.HasForeignKey("BudgetItemDefinitionId");
b.Navigation("BudgetItemDefinition");
});
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
{
b.HasOne("Aberwyn.Models.Meal", null)
.WithMany("Ingredients")
.HasForeignKey("MealId")
.OnDelete(DeleteBehavior.Cascade)
.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)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Navigation("Preferences")
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Navigation("Items");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Navigation("Categories");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
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
}
}
}

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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)
{
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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);
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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);
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

View 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");
}
}
}

View File

@@ -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");
@@ -301,11 +403,125 @@ 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.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")
@@ -353,11 +569,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")
@@ -365,7 +691,6 @@ namespace Aberwyn.Migrations
.HasColumnType("int");
b.Property<string>("AssignedTo")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
@@ -398,6 +723,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")
@@ -413,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");
@@ -433,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")
@@ -561,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")
@@ -580,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)
@@ -590,8 +1087,6 @@ namespace Aberwyn.Migrations
.WithMany()
.HasForeignKey("BudgetItemDefinitionId");
b.Navigation("BudgetCategory");
b.Navigation("BudgetItemDefinition");
});
@@ -604,6 +1099,124 @@ 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")
.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)
@@ -655,6 +1268,64 @@ 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")
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Navigation("Items");
@@ -669,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
}
}

View File

@@ -13,9 +13,9 @@
public string Title { get; set; }
public string Description { get; set; }
public int Priority { get; set; }
public string Status { get; set; } // e.g., "ideas", "doing", "done"
public string Status { get; set; } = "ideas";
public DateTime CreatedAt { get; set; }
public string AssignedTo { get; set; } // Ex: namn eller användarnamn
public string? AssignedTo { get; set; }
public bool IsArchived { get; set; }
public string Tags { get; set; } // Komma-separerad t.ex. "frontend,bug"
}

View File

@@ -4,6 +4,8 @@
public class ApplicationUser : IdentityUser
{
public virtual UserPreferences Preferences { get; set; }
// Lägg till egna fält om du vill, t.ex. public string DisplayName { get; set; }
}
}

View File

@@ -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
{

View 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; }
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Aberwyn.Data;
using Aberwyn.Models;
@@ -11,7 +12,27 @@ 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
{
public int Id { get; set; }
public int DayOfWeek { get; set; }
public int WeekNumber { get; set; }
public int Year { get; set; }
public int? BreakfastMealId { get; set; }
public int? LunchMealId { get; set; }
public int? DinnerMealId { get; set; }
public string? BreakfastMealName { get; set; }
public string? LunchMealName { get; set; }
public string? DinnerMealName { get; set; }
public byte[]? DinnerMealThumbnail { get; set; }
}
public class WeeklyMenu
{
public int Id { get; set; }
@@ -20,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; }
@@ -36,30 +61,40 @@ 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; }
}
public class Meal
{
public int Id { get; set; }
public string Name { get; set; } // Behåll som obligatorisk
public class Meal
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string? Description { get; set; }
public string? ProteinType { get; set; }
public string? Category { get; set; }
public string? CarbType { get; set; }
public string? RecipeUrl { get; set; }
public string? ImageUrl { get; set; }
public bool IsAvailable { get; set; }
public DateTime CreatedAt { get; set; }
public string Name { get; set; } // Behåll som obligatorisk
public byte[]? ImageData { get; set; } // 👈 Viktigt!
public string? ImageMimeType { get; set; } // 👈 Viktigt!
public string? Instructions { get; set; } // 👈 Viktigt!
public string? Description { get; set; }
public string? ProteinType { get; set; }
public int? MealCategoryId { get; set; }
public List<Ingredient> Ingredients { get; set; } = new();
}
[ForeignKey("MealCategoryId")]
public MealCategory? Category { get; set; }
public string? CarbType { get; set; }
public string? RecipeUrl { get; set; }
public string? ImageUrl { get; set; }
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; }
public List<Ingredient> Ingredients { get; set; } = new();
}
public class Ingredient
{
@@ -79,20 +114,134 @@ public class Meal
public string? ImageData { get; set; } // base64
public string? ImageMimeType { get; set; }
public static MealDto FromMeal(Meal meal)
public static MealListDto FromMeal(Meal meal, bool includeThumbnail = false)
{
return new MealDto
return new MealListDto
{
Id = meal.Id,
Name = meal.Name,
Category = meal.Category,
IsAvailable = meal.IsAvailable,
ImageUrl = meal.ImageUrl,
ImageMimeType = meal.ImageMimeType,
ImageData = meal.ImageData != null ? Convert.ToBase64String(meal.ImageData) : null
Description = meal.Description,
ThumbnailData = includeThumbnail && meal.ThumbnailData != null
? Convert.ToBase64String(meal.ThumbnailData)
: null
};
}
}
public class MealListDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
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
{
Id = meal.Id,
Name = meal.Name,
Description = meal.Description,
ThumbnailData = includeThumbnail && meal.ThumbnailData != null
? Convert.ToBase64String(meal.ThumbnailData)
: 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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -6,6 +6,11 @@
public string Endpoint { get; set; }
public string P256DH { get; set; }
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
{

View 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; } = "";
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,17 @@
namespace Aberwyn.Models
{
public class SetupSettings
{
public string AdminUsername { get; set; } = "admin";
public string AdminEmail { get; set; } = "admin@localhost";
public string AdminPassword { get; set; } = "Admin123!";
public bool IsConfigured { get; set; }
public string DbHost { get; set; }
public int DbPort { get; set; }
public string DbName { get; set; }
public string DbUser { get; set; }
public string DbPassword { get; set; }
}
}

View 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;
}

View File

@@ -1,10 +0,0 @@
namespace Aberwyn.Models
{
public class User
{
public int UserID { get; set; }
public string Username { get; set; }
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,47 @@
using System.ComponentModel.DataAnnotations;
namespace Aberwyn.Models
{
public class UserModel
{
public int UserID { get; set; }
public string Username { get; set; }
public string Name { get; set; }
}
public class UserPreferences
{
[Key]
public string UserId { get; set; }
public bool NotifyPizza { get; set; }
public bool NotifyMenu { get; set; }
public bool NotifyBudget { get; set; }
public virtual ApplicationUser User { get; set; }
}
public class StoredPushSubscription
{
[Key]
public int Id { get; set; }
public string UserId { get; set; }
public string Endpoint { get; set; }
public string P256DH { get; set; }
public string Auth { get; set; }
public virtual ApplicationUser User { get; set; }
}
public class UserProfileViewModel
{
public string Name { get; set; }
public string Email { get; set; }
public bool NotifyPizza { get; set; }
public bool NotifyMenu { get; set; }
public bool NotifyBudget { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More