diff --git a/LemonBot.sln b/LemonBot.sln deleted file mode 100644 index c92bdaf..0000000 --- a/LemonBot.sln +++ /dev/null @@ -1,416 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.0.11018.127 d18.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F47298C6-FC9C-4B30-88D6-3DDAAD1B7B65}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LemonBot", "src\LemonBot\LemonBot.csproj", "{C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LemonBot.Commands", "src\LemonBot.Commands\LemonBot.Commands.csproj", "{0017D050-5521-4C55-B1CD-2DDB228CEC42}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LemonBot.Clients", "src\LemonBot.Clients\LemonBot.Clients.csproj", "{D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "KITT", "KITT", "{53F76412-0B22-4C62-8519-834A82747AD4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Web.Models", "src\KITT.Web.Models\KITT.Web.Models.csproj", "{A9018405-BC9E-41FD-955A-5A3B4AED27A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Core", "src\KITT.Core\KITT.Core.csproj", "{66F99049-7BCF-47D4-81FF-8E0254FE82EE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{B32B1475-75B8-41DD-BB71-D6106152C656}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Core.Test", "tests\KITT.Core.Test\KITT.Core.Test.csproj", "{AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Web.App.UI", "src\KITT.Web.App.UI\KITT.Web.App.UI.csproj", "{2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Telegram.Messages", "src\KITT.Telegram.Messages\KITT.Telegram.Messages.csproj", "{6E87C525-EB68-4DAD-9F63-39E426D01668}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Telegram.Functions", "src\KITT.Telegram.Functions\KITT.Telegram.Functions.csproj", "{A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Telegram.Functions.Test", "tests\KITT.Telegram.Functions.Test\KITT.Telegram.Functions.Test.csproj", "{92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Cms.Web.Api", "src\KITT.Cms.Web.Api\KITT.Cms.Web.Api.csproj", "{406A74C4-AF3B-43C0-9D54-111B7CE24EBF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Cms.Web.Models", "src\KITT.Cms.Web.Models\KITT.Cms.Web.Models.csproj", "{7E0161FF-5AB4-451E-A95A-45D15BFC8C29}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Web.Shared", "src\KITT.Web.Shared\KITT.Web.Shared.csproj", "{A701C3B3-E70B-4386-BBD4-39B9376966EE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.AppHost", "src\KITT.AppHost\KITT.AppHost.csproj", "{7C417964-199A-40D7-99AD-77A356F6B8D0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.ServiceDefaults", "src\KITT.ServiceDefaults\KITT.ServiceDefaults.csproj", "{F1D748E1-2241-446F-88D0-CEFCCCEB7A93}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Web.App", "src\KITT.Web.App\KITT.Web.App\KITT.Web.App.csproj", "{4CBD2E55-3636-445A-93BB-84D549D64501}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Web.App.Client", "src\KITT.Web.App\KITT.Web.App.Client\KITT.Web.App.Client.csproj", "{C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cms", "Cms", "{90F26E81-71A2-4438-ACD9-64D3AC5636AF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KITT.Cms.Web.App", "src\KITT.Cms.Web.App\KITT.Cms.Web.App.csproj", "{E51D737B-4337-47BD-84AF-97086FDDA158}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Proposals", "Proposals", "{5C9DDC07-E0A9-48AA-8D75-0188C65B2D5E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KITT.Proposals.Web.Api", "src\KITT.Proposals.Web.Api\KITT.Proposals.Web.Api.csproj", "{CA7C2776-D362-4AD6-BA88-2C0B38D71C42}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KITT.Proposals.Web.Models", "src\KITT.Proposals.Web.Models\KITT.Proposals.Web.Models.csproj", "{B7D3A829-1BFD-455B-8E7B-074DC16EE85D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KITT.Proposals.Web.App", "src\KITT.Proposals.Web.App\KITT.Proposals.Web.App.csproj", "{B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KITT.Cms.Web.Api.Test", "tests\KITT.Cms.Web.Api.Test\KITT.Cms.Web.Api.Test.csproj", "{A29853FF-53A7-499F-B309-7154A51D9908}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KITT.Web.Testing", "tests\KITT.Web.Testing\KITT.Web.Testing.csproj", "{AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Support", "Support", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KITT.Support.Seeder", "src\KITT.Support.Seeder\KITT.Support.Seeder.csproj", "{92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KITT.Services", "src\KITT.Services\KITT.Services.csproj", "{C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Debug|x64.ActiveCfg = Debug|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Debug|x64.Build.0 = Debug|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Debug|x86.ActiveCfg = Debug|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Debug|x86.Build.0 = Debug|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Release|Any CPU.Build.0 = Release|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Release|x64.ActiveCfg = Release|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Release|x64.Build.0 = Release|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Release|x86.ActiveCfg = Release|Any CPU - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC}.Release|x86.Build.0 = Release|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Debug|x64.ActiveCfg = Debug|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Debug|x64.Build.0 = Debug|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Debug|x86.ActiveCfg = Debug|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Debug|x86.Build.0 = Debug|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Release|Any CPU.Build.0 = Release|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Release|x64.ActiveCfg = Release|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Release|x64.Build.0 = Release|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Release|x86.ActiveCfg = Release|Any CPU - {0017D050-5521-4C55-B1CD-2DDB228CEC42}.Release|x86.Build.0 = Release|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Debug|x64.ActiveCfg = Debug|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Debug|x64.Build.0 = Debug|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Debug|x86.ActiveCfg = Debug|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Debug|x86.Build.0 = Debug|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Release|Any CPU.Build.0 = Release|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Release|x64.ActiveCfg = Release|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Release|x64.Build.0 = Release|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Release|x86.ActiveCfg = Release|Any CPU - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1}.Release|x86.Build.0 = Release|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Debug|x64.ActiveCfg = Debug|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Debug|x64.Build.0 = Debug|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Debug|x86.ActiveCfg = Debug|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Debug|x86.Build.0 = Debug|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Release|Any CPU.Build.0 = Release|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Release|x64.ActiveCfg = Release|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Release|x64.Build.0 = Release|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Release|x86.ActiveCfg = Release|Any CPU - {A9018405-BC9E-41FD-955A-5A3B4AED27A4}.Release|x86.Build.0 = Release|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Debug|x64.ActiveCfg = Debug|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Debug|x64.Build.0 = Debug|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Debug|x86.ActiveCfg = Debug|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Debug|x86.Build.0 = Debug|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Release|Any CPU.Build.0 = Release|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Release|x64.ActiveCfg = Release|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Release|x64.Build.0 = Release|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Release|x86.ActiveCfg = Release|Any CPU - {66F99049-7BCF-47D4-81FF-8E0254FE82EE}.Release|x86.Build.0 = Release|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Debug|x64.ActiveCfg = Debug|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Debug|x64.Build.0 = Debug|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Debug|x86.ActiveCfg = Debug|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Debug|x86.Build.0 = Debug|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Release|Any CPU.Build.0 = Release|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Release|x64.ActiveCfg = Release|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Release|x64.Build.0 = Release|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Release|x86.ActiveCfg = Release|Any CPU - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235}.Release|x86.Build.0 = Release|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Debug|x64.ActiveCfg = Debug|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Debug|x64.Build.0 = Debug|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Debug|x86.ActiveCfg = Debug|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Debug|x86.Build.0 = Debug|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Release|Any CPU.Build.0 = Release|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Release|x64.ActiveCfg = Release|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Release|x64.Build.0 = Release|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Release|x86.ActiveCfg = Release|Any CPU - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE}.Release|x86.Build.0 = Release|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Debug|x64.ActiveCfg = Debug|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Debug|x64.Build.0 = Debug|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Debug|x86.ActiveCfg = Debug|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Debug|x86.Build.0 = Debug|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Release|Any CPU.Build.0 = Release|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Release|x64.ActiveCfg = Release|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Release|x64.Build.0 = Release|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Release|x86.ActiveCfg = Release|Any CPU - {6E87C525-EB68-4DAD-9F63-39E426D01668}.Release|x86.Build.0 = Release|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Debug|x64.ActiveCfg = Debug|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Debug|x64.Build.0 = Debug|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Debug|x86.ActiveCfg = Debug|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Debug|x86.Build.0 = Debug|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Release|Any CPU.Build.0 = Release|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Release|x64.ActiveCfg = Release|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Release|x64.Build.0 = Release|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Release|x86.ActiveCfg = Release|Any CPU - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4}.Release|x86.Build.0 = Release|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Debug|x64.ActiveCfg = Debug|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Debug|x64.Build.0 = Debug|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Debug|x86.ActiveCfg = Debug|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Debug|x86.Build.0 = Debug|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Release|Any CPU.Build.0 = Release|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Release|x64.ActiveCfg = Release|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Release|x64.Build.0 = Release|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Release|x86.ActiveCfg = Release|Any CPU - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE}.Release|x86.Build.0 = Release|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Debug|x64.ActiveCfg = Debug|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Debug|x64.Build.0 = Debug|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Debug|x86.ActiveCfg = Debug|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Debug|x86.Build.0 = Debug|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Release|Any CPU.Build.0 = Release|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Release|x64.ActiveCfg = Release|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Release|x64.Build.0 = Release|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Release|x86.ActiveCfg = Release|Any CPU - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF}.Release|x86.Build.0 = Release|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Debug|x64.ActiveCfg = Debug|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Debug|x64.Build.0 = Debug|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Debug|x86.ActiveCfg = Debug|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Debug|x86.Build.0 = Debug|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Release|Any CPU.Build.0 = Release|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Release|x64.ActiveCfg = Release|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Release|x64.Build.0 = Release|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Release|x86.ActiveCfg = Release|Any CPU - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29}.Release|x86.Build.0 = Release|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Debug|x64.ActiveCfg = Debug|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Debug|x64.Build.0 = Debug|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Debug|x86.ActiveCfg = Debug|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Debug|x86.Build.0 = Debug|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Release|Any CPU.Build.0 = Release|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Release|x64.ActiveCfg = Release|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Release|x64.Build.0 = Release|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Release|x86.ActiveCfg = Release|Any CPU - {A701C3B3-E70B-4386-BBD4-39B9376966EE}.Release|x86.Build.0 = Release|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Debug|x64.ActiveCfg = Debug|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Debug|x64.Build.0 = Debug|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Debug|x86.ActiveCfg = Debug|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Debug|x86.Build.0 = Debug|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Release|Any CPU.Build.0 = Release|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Release|x64.ActiveCfg = Release|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Release|x64.Build.0 = Release|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Release|x86.ActiveCfg = Release|Any CPU - {7C417964-199A-40D7-99AD-77A356F6B8D0}.Release|x86.Build.0 = Release|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Debug|x64.ActiveCfg = Debug|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Debug|x64.Build.0 = Debug|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Debug|x86.ActiveCfg = Debug|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Debug|x86.Build.0 = Debug|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Release|Any CPU.Build.0 = Release|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Release|x64.ActiveCfg = Release|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Release|x64.Build.0 = Release|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Release|x86.ActiveCfg = Release|Any CPU - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93}.Release|x86.Build.0 = Release|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Debug|x64.ActiveCfg = Debug|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Debug|x64.Build.0 = Debug|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Debug|x86.ActiveCfg = Debug|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Debug|x86.Build.0 = Debug|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Release|Any CPU.Build.0 = Release|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Release|x64.ActiveCfg = Release|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Release|x64.Build.0 = Release|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Release|x86.ActiveCfg = Release|Any CPU - {4CBD2E55-3636-445A-93BB-84D549D64501}.Release|x86.Build.0 = Release|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Debug|x64.ActiveCfg = Debug|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Debug|x64.Build.0 = Debug|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Debug|x86.ActiveCfg = Debug|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Debug|x86.Build.0 = Debug|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Release|Any CPU.Build.0 = Release|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Release|x64.ActiveCfg = Release|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Release|x64.Build.0 = Release|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Release|x86.ActiveCfg = Release|Any CPU - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4}.Release|x86.Build.0 = Release|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Debug|x64.ActiveCfg = Debug|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Debug|x64.Build.0 = Debug|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Debug|x86.ActiveCfg = Debug|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Debug|x86.Build.0 = Debug|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Release|Any CPU.Build.0 = Release|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Release|x64.ActiveCfg = Release|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Release|x64.Build.0 = Release|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Release|x86.ActiveCfg = Release|Any CPU - {E51D737B-4337-47BD-84AF-97086FDDA158}.Release|x86.Build.0 = Release|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Debug|x64.ActiveCfg = Debug|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Debug|x64.Build.0 = Debug|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Debug|x86.ActiveCfg = Debug|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Debug|x86.Build.0 = Debug|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Release|Any CPU.Build.0 = Release|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Release|x64.ActiveCfg = Release|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Release|x64.Build.0 = Release|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Release|x86.ActiveCfg = Release|Any CPU - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42}.Release|x86.Build.0 = Release|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Debug|x64.ActiveCfg = Debug|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Debug|x64.Build.0 = Debug|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Debug|x86.ActiveCfg = Debug|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Debug|x86.Build.0 = Debug|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Release|Any CPU.Build.0 = Release|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Release|x64.ActiveCfg = Release|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Release|x64.Build.0 = Release|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Release|x86.ActiveCfg = Release|Any CPU - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D}.Release|x86.Build.0 = Release|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Debug|x64.ActiveCfg = Debug|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Debug|x64.Build.0 = Debug|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Debug|x86.ActiveCfg = Debug|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Debug|x86.Build.0 = Debug|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Release|Any CPU.Build.0 = Release|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Release|x64.ActiveCfg = Release|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Release|x64.Build.0 = Release|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Release|x86.ActiveCfg = Release|Any CPU - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD}.Release|x86.Build.0 = Release|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Debug|x64.ActiveCfg = Debug|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Debug|x64.Build.0 = Debug|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Debug|x86.ActiveCfg = Debug|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Debug|x86.Build.0 = Debug|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Release|Any CPU.Build.0 = Release|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Release|x64.ActiveCfg = Release|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Release|x64.Build.0 = Release|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Release|x86.ActiveCfg = Release|Any CPU - {A29853FF-53A7-499F-B309-7154A51D9908}.Release|x86.Build.0 = Release|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Debug|x64.ActiveCfg = Debug|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Debug|x64.Build.0 = Debug|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Debug|x86.ActiveCfg = Debug|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Debug|x86.Build.0 = Debug|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Release|Any CPU.Build.0 = Release|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Release|x64.ActiveCfg = Release|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Release|x64.Build.0 = Release|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Release|x86.ActiveCfg = Release|Any CPU - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9}.Release|x86.Build.0 = Release|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Debug|x64.ActiveCfg = Debug|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Debug|x64.Build.0 = Debug|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Debug|x86.ActiveCfg = Debug|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Debug|x86.Build.0 = Debug|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Release|Any CPU.Build.0 = Release|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Release|x64.ActiveCfg = Release|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Release|x64.Build.0 = Release|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Release|x86.ActiveCfg = Release|Any CPU - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98}.Release|x86.Build.0 = Release|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Debug|x64.ActiveCfg = Debug|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Debug|x64.Build.0 = Debug|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Debug|x86.ActiveCfg = Debug|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Debug|x86.Build.0 = Debug|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Release|Any CPU.Build.0 = Release|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Release|x64.ActiveCfg = Release|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Release|x64.Build.0 = Release|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Release|x86.ActiveCfg = Release|Any CPU - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C56ABDA1-E83F-4C97-B3C2-3A248526E4FC} = {F47298C6-FC9C-4B30-88D6-3DDAAD1B7B65} - {0017D050-5521-4C55-B1CD-2DDB228CEC42} = {F47298C6-FC9C-4B30-88D6-3DDAAD1B7B65} - {D2F9C1BB-F01E-459E-8C7E-F225F83A47B1} = {F47298C6-FC9C-4B30-88D6-3DDAAD1B7B65} - {53F76412-0B22-4C62-8519-834A82747AD4} = {F47298C6-FC9C-4B30-88D6-3DDAAD1B7B65} - {A9018405-BC9E-41FD-955A-5A3B4AED27A4} = {53F76412-0B22-4C62-8519-834A82747AD4} - {66F99049-7BCF-47D4-81FF-8E0254FE82EE} = {53F76412-0B22-4C62-8519-834A82747AD4} - {AB7A75D3-84BA-4AFD-BC49-D0C7545FB235} = {B32B1475-75B8-41DD-BB71-D6106152C656} - {2B84BEBB-A29E-4BDE-893F-BBCCE979A0BE} = {53F76412-0B22-4C62-8519-834A82747AD4} - {6E87C525-EB68-4DAD-9F63-39E426D01668} = {53F76412-0B22-4C62-8519-834A82747AD4} - {A6A4722A-4AA2-4B9C-A2FB-A7C171E03DF4} = {53F76412-0B22-4C62-8519-834A82747AD4} - {92122C69-6CEC-4FEA-BAC5-AC7F8197C6FE} = {B32B1475-75B8-41DD-BB71-D6106152C656} - {406A74C4-AF3B-43C0-9D54-111B7CE24EBF} = {90F26E81-71A2-4438-ACD9-64D3AC5636AF} - {7E0161FF-5AB4-451E-A95A-45D15BFC8C29} = {90F26E81-71A2-4438-ACD9-64D3AC5636AF} - {A701C3B3-E70B-4386-BBD4-39B9376966EE} = {53F76412-0B22-4C62-8519-834A82747AD4} - {7C417964-199A-40D7-99AD-77A356F6B8D0} = {F47298C6-FC9C-4B30-88D6-3DDAAD1B7B65} - {F1D748E1-2241-446F-88D0-CEFCCCEB7A93} = {F47298C6-FC9C-4B30-88D6-3DDAAD1B7B65} - {4CBD2E55-3636-445A-93BB-84D549D64501} = {53F76412-0B22-4C62-8519-834A82747AD4} - {C4ED93D6-3B6F-4EF3-84D4-3B76FE7875F4} = {53F76412-0B22-4C62-8519-834A82747AD4} - {90F26E81-71A2-4438-ACD9-64D3AC5636AF} = {53F76412-0B22-4C62-8519-834A82747AD4} - {E51D737B-4337-47BD-84AF-97086FDDA158} = {90F26E81-71A2-4438-ACD9-64D3AC5636AF} - {5C9DDC07-E0A9-48AA-8D75-0188C65B2D5E} = {53F76412-0B22-4C62-8519-834A82747AD4} - {CA7C2776-D362-4AD6-BA88-2C0B38D71C42} = {5C9DDC07-E0A9-48AA-8D75-0188C65B2D5E} - {B7D3A829-1BFD-455B-8E7B-074DC16EE85D} = {5C9DDC07-E0A9-48AA-8D75-0188C65B2D5E} - {B5656CFD-8ED7-4CC8-9AE4-CD0BE14B98AD} = {5C9DDC07-E0A9-48AA-8D75-0188C65B2D5E} - {A29853FF-53A7-499F-B309-7154A51D9908} = {B32B1475-75B8-41DD-BB71-D6106152C656} - {AA34DAE8-59AE-4500-8D60-6CA2B899DCD9} = {B32B1475-75B8-41DD-BB71-D6106152C656} - {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {53F76412-0B22-4C62-8519-834A82747AD4} - {92B53BDC-3A3B-D5E5-70AE-56E877F1BB98} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {C4747EBF-4249-4D6A-9DD0-73C35E0C9AA1} = {F47298C6-FC9C-4B30-88D6-3DDAAD1B7B65} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {08491B30-AD3E-4AEA-AAB9-40A83A96F393} - EndGlobalSection -EndGlobal diff --git a/LemonBot.slnx b/LemonBot.slnx new file mode 100644 index 0000000..2d99318 --- /dev/null +++ b/LemonBot.slnx @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspire.config.json b/aspire.config.json new file mode 100644 index 0000000..ff9018f --- /dev/null +++ b/aspire.config.json @@ -0,0 +1,5 @@ +{ + "appHost": { + "path": "src/KITT.AppHost/KITT.AppHost.csproj" + } +} \ No newline at end of file diff --git a/src/KITT.AppHost/DistributedApplicationBuilderExtensions.cs b/src/KITT.AppHost/DistributedApplicationBuilderExtensions.cs index 173940e..fb04a42 100644 --- a/src/KITT.AppHost/DistributedApplicationBuilderExtensions.cs +++ b/src/KITT.AppHost/DistributedApplicationBuilderExtensions.cs @@ -5,22 +5,28 @@ namespace KITT.AppHost; public static class DistributedApplicationBuilderExtensions { - public static IResourceBuilder AddKittDatabase(this IDistributedApplicationBuilder builder) + extension(IDistributedApplicationBuilder builder) { - ArgumentNullException.ThrowIfNull(builder); - - if (!builder.ExecutionContext.IsPublishMode) + public IResourceBuilder AddKittDatabase() { - var kittSql = builder.AddSqlServer(ServiceNames.Sql) - .WithContainerName("sqlserver-local") - .WithLifetime(ContainerLifetime.Persistent) - .WithDataVolume("kitt-data"); + ArgumentNullException.ThrowIfNull(builder); - return kittSql.AddDatabase(ServiceNames.Database, databaseName: "KITT"); - } - else - { - return builder.AddConnectionString(ServiceNames.Database); + if (!builder.ExecutionContext.IsPublishMode) + { + var kittSql = builder.AddSqlServer(ServiceNames.Sql) + .WithDataVolume("kitt-data") + .WithDbGate(containerBuilder => + { + containerBuilder.WithExplicitStart(); + }); + + return kittSql.AddDatabase(ServiceNames.Database, databaseName: "KITT"); + } + else + { + return builder.AddConnectionString(ServiceNames.Database); + } } } + } diff --git a/src/KITT.AppHost/KITT.AppHost.csproj b/src/KITT.AppHost/KITT.AppHost.csproj index 5967448..6faa106 100644 --- a/src/KITT.AppHost/KITT.AppHost.csproj +++ b/src/KITT.AppHost/KITT.AppHost.csproj @@ -1,22 +1,21 @@ - - - + Exe - net9.0 + net10.0 enable enable 9088a778-e43f-44ea-8c00-27313c124862 - - - - - - + + + + + + + diff --git a/src/KITT.AppHost/Program.cs b/src/KITT.AppHost/Program.cs index 0cea868..0115372 100644 --- a/src/KITT.AppHost/Program.cs +++ b/src/KITT.AppHost/Program.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Hosting; using KITT.Services; using KITT.AppHost; +using Aspire.Hosting.Azure; var builder = DistributedApplication.CreateBuilder(args); @@ -22,10 +23,37 @@ var kittDb = builder.AddKittDatabase(); #endregion +#region Azure Storage Account +var azureStorage = builder.AddAzureStorage(ServiceNames.DataStorageAccount) + .ConfigureInfrastructure(infra => + { + var storageAccountResource = infra.GetProvisionableResources() + .OfType() + .SingleOrDefault(); + + if (storageAccountResource is not null) + { + storageAccountResource.Kind = Azure.Provisioning.Storage.StorageKind.StorageV2; + storageAccountResource.Sku = new() + { + Name = Azure.Provisioning.Storage.StorageSkuName.StandardLrs + }; + } + }) + .RunAsEmulator(azurite => + { + azurite.WithDataVolume("kittdatastorage-data"); + }); + +var settingsTables = azureStorage.AddTables(ServiceNames.SettingsTables); +#endregion + #region CMS var cmsApi = builder.AddProject(ServiceNames.CmsApi) .WithReference(kittDb) .WaitFor(kittDb) + .WithReference(settingsTables) + .WaitFor(settingsTables) .WithEnvironment("Identity__TenantId", tenantId) .WithEnvironment("Identity__Cms__AppId", cmsApiAppId) .PublishAsAzureContainerApp((_, app) => diff --git a/src/KITT.Cms.Settings/ConnectedChannelsRepository.cs b/src/KITT.Cms.Settings/ConnectedChannelsRepository.cs new file mode 100644 index 0000000..d6adf7a --- /dev/null +++ b/src/KITT.Cms.Settings/ConnectedChannelsRepository.cs @@ -0,0 +1,54 @@ +using Azure.Data.Tables; +using KITT.Cms.Settings.Models; + +namespace KITT.Cms.Settings; + +public class ConnectedChannelsRepository : IConnectedChannelsRepository +{ + private readonly TableClient _tableClient; + + internal const string ConnectedChannelsTableName = "SettingsChannels"; + + public ConnectedChannelsRepository(TableServiceClient tableServiceClient) + { + ArgumentNullException.ThrowIfNull(tableServiceClient); + _tableClient = CreateTableIfNotExist(tableServiceClient) ?? throw new ArgumentNullException("tableClient"); + } + + private static TableClient? CreateTableIfNotExist(TableServiceClient tableServiceClient) + { + var tableClient = tableServiceClient.GetTableClient(ConnectedChannelsTableName); + tableClient.CreateIfNotExists(); + + return tableClient; + } + + public async Task GetConnectedChannelsAsync(string userId) + { + var channelsQuery = _tableClient.QueryAsync(c => c.PartitionKey == userId); + var channels = new List(); + + await foreach (var channel in channelsQuery) + { + channels.Add(channel); + } + + return channels.OrderBy(c => c.Name).ToArray(); + } + + public Task GetConnectedChannelAsync(Guid channelId, string userId) + { + var channels = _tableClient.Query(c => c.PartitionKey == userId && c.RowKey == channelId.ToString()); + return Task.FromResult(channels.SingleOrDefault()); + } + + public async Task SaveAsync(ConnectedChannel channel) + { + await _tableClient.UpsertEntityAsync(channel, TableUpdateMode.Replace); + } + + public async Task DeleteAsync(Guid channelId, string userId) + { + await _tableClient.DeleteEntityAsync(userId, channelId.ToString()); + } +} diff --git a/src/KITT.Cms.Settings/IConnectedChannelsRepository.cs b/src/KITT.Cms.Settings/IConnectedChannelsRepository.cs new file mode 100644 index 0000000..7de2c22 --- /dev/null +++ b/src/KITT.Cms.Settings/IConnectedChannelsRepository.cs @@ -0,0 +1,14 @@ +using KITT.Cms.Settings.Models; + +namespace KITT.Cms.Settings; + +public interface IConnectedChannelsRepository +{ + Task GetConnectedChannelsAsync(string userId); + + Task GetConnectedChannelAsync(Guid channelId, string userId); + + Task SaveAsync(ConnectedChannel channel); + + Task DeleteAsync(Guid channelId, string userId); +} diff --git a/src/KITT.Cms.Settings/KITT.Cms.Settings.csproj b/src/KITT.Cms.Settings/KITT.Cms.Settings.csproj new file mode 100644 index 0000000..d005c22 --- /dev/null +++ b/src/KITT.Cms.Settings/KITT.Cms.Settings.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/src/KITT.Cms.Settings/Models/ChannelType.cs b/src/KITT.Cms.Settings/Models/ChannelType.cs new file mode 100644 index 0000000..5a68353 --- /dev/null +++ b/src/KITT.Cms.Settings/Models/ChannelType.cs @@ -0,0 +1,7 @@ +namespace KITT.Cms.Settings.Models; + +public enum ChannelType +{ + Twitch, + YouTube +} diff --git a/src/KITT.Cms.Settings/Models/ConnectedChannel.cs b/src/KITT.Cms.Settings/Models/ConnectedChannel.cs new file mode 100644 index 0000000..a9a9b7c --- /dev/null +++ b/src/KITT.Cms.Settings/Models/ConnectedChannel.cs @@ -0,0 +1,21 @@ +using Azure; +using Azure.Data.Tables; + +namespace KITT.Cms.Settings.Models; + +public class ConnectedChannel : ITableEntity +{ + public required string PartitionKey { get; set; } = string.Empty; + + public required string RowKey { get; set; } = string.Empty; + + public DateTimeOffset? Timestamp { get; set; } + + public ETag ETag { get; set; } + + public required string Name { get; set; } = string.Empty; + + public required string Url { get; set; } = string.Empty; + + public required ChannelType Type { get; set; } +} diff --git a/src/KITT.Cms.Web.Api/Endpoints/Services/ChannelsEndpointsServices.cs b/src/KITT.Cms.Web.Api/Endpoints/Services/ChannelsEndpointsServices.cs new file mode 100644 index 0000000..e94bbd7 --- /dev/null +++ b/src/KITT.Cms.Web.Api/Endpoints/Services/ChannelsEndpointsServices.cs @@ -0,0 +1,79 @@ +using KITT.Cms.Settings; +using KITT.Cms.Settings.Models; +using KITT.Cms.Web.Models.Settings; + +namespace KITT.Cms.Web.Api.Endpoints.Services; + +public class ChannelsEndpointsServices(IConnectedChannelsRepository connectedChannelsRepository) +{ + public async Task GetChannelsAsync(string userId) + { + var channels = await connectedChannelsRepository.GetConnectedChannelsAsync(userId); + return channels.Select(c => new ChannelModel + { + Id = Guid.Parse(c.RowKey), + Name = c.Name, + Url = c.Url, + Type = c.Type + }).ToArray(); + } + + public async Task GetChannelAsync(Guid channelId, string userId) + { + var channel = await connectedChannelsRepository.GetConnectedChannelAsync(channelId, userId); + if (channel is null) + { + return null; + } + + return new ChannelModel + { + Id = Guid.Parse(channel.RowKey), + Name = channel.Name, + Url = channel.Url, + Type = channel.Type + }; + } + + public async Task CreateChannelAsync(ChannelModel model, string userId) + { + model.Id = Guid.NewGuid(); + + var channelEntity = MapToConnectedChannel(model, userId); + await connectedChannelsRepository.SaveAsync(channelEntity); + + return model; + } + + public async Task DeleteChannelAsync(Guid channelId, string userId) + { + await connectedChannelsRepository.DeleteAsync(channelId, userId); + } + + public async Task UpdateChannelAsync(Guid channelId, ChannelModel model, string userId) + { + var connectedChannel = await connectedChannelsRepository.GetConnectedChannelAsync(channelId, userId); + if (connectedChannel is null) + { + throw new ArgumentOutOfRangeException(nameof(channelId)); + } + + connectedChannel.Name = model.Name; + connectedChannel.Url = model.Url; + connectedChannel.Type = model.Type; + + await connectedChannelsRepository.SaveAsync(connectedChannel); + } + + private static ConnectedChannel MapToConnectedChannel(ChannelModel model, string userId) + { + return new() + { + RowKey = model.Id.ToString(), + PartitionKey = userId, + Name = model.Name, + Url = model.Url, + Type = model.Type + }; + } +} diff --git a/src/KITT.Cms.Web.Api/Endpoints/Services/SettingsEndpointsServices.cs b/src/KITT.Cms.Web.Api/Endpoints/Services/SettingsEndpointsServices.cs deleted file mode 100644 index f9076b4..0000000 --- a/src/KITT.Cms.Web.Api/Endpoints/Services/SettingsEndpointsServices.cs +++ /dev/null @@ -1,53 +0,0 @@ -using KITT.Cms.Web.Models.Settings; -using KITT.Core.Commands; -using KITT.Core.ReadModels; -using Microsoft.EntityFrameworkCore; - -namespace KITT.Cms.Web.Api.Endpoints.Services; - -public class SettingsEndpointsServices -{ - public IDatabase Database { get; } - public ISettingsCommands Commands { get; } - - public SettingsEndpointsServices(IDatabase database, ISettingsCommands commands) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - Commands = commands ?? throw new ArgumentNullException(nameof(commands)); - } - - public async Task GetAllSettingsAsync(string userId) - { - var settings = await Database.Settings - .ByUserId(userId) - .OrderBy(s => s.TwitchChannel) - .Select(s => new SettingsListModel.SettingsDescriptor - { - Id = s.Id, - TwitchChannel = s.TwitchChannel - }).ToArrayAsync(); - - var model = new SettingsListModel { Items = settings }; - return model; - } - - public Task CreateNewSettingsAsync(CreateNewSettingsModel model, string userId) - { - return Commands.CreateNewSettingsAsync(userId, model.TwitchChannel); - } - - public async Task GetSettingsDetailAsync(Guid settingsId) - { - var settings = await Database.Settings.SingleOrDefaultAsync(s => s.Id == settingsId); - if (settings is null) - { - return null; - } - - return new SettingsDetailModel - { - Id = settings.Id, - TwitchChannel = settings.TwitchChannel - }; - } -} diff --git a/src/KITT.Cms.Web.Api/Endpoints/Services/StreamingsEndpointsServices.cs b/src/KITT.Cms.Web.Api/Endpoints/Services/StreamingsEndpointsServices.cs index 3c02c7d..0138e5e 100644 --- a/src/KITT.Cms.Web.Api/Endpoints/Services/StreamingsEndpointsServices.cs +++ b/src/KITT.Cms.Web.Api/Endpoints/Services/StreamingsEndpointsServices.cs @@ -5,17 +5,11 @@ namespace KITT.Cms.Web.Api.Endpoints.Services; -public class StreamingsEndpointsServices +public class StreamingsEndpointsServices(IDatabase database, IStreamingCommands commands) { - public IDatabase Database { get; } + public IDatabase Database { get; } = database ?? throw new ArgumentNullException(nameof(database)); - public IStreamingCommands Commands { get; } - - public StreamingsEndpointsServices(IDatabase database, IStreamingCommands commands) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - Commands = commands ?? throw new ArgumentNullException(nameof(commands)); - } + public IStreamingCommands Commands { get; } = commands ?? throw new ArgumentNullException(nameof(commands)); public async Task GetAllStreamingsAsync(string userId, int page, int size, StreamingQueryModel.SortDirection sort, string? query) { @@ -40,8 +34,8 @@ public async Task GetAllStreamingsAsync(string userId, int ScheduledOn = s.ScheduleDate, StartingTime = s.StartingTime, Title = s.Title, - HostingChannelUrl = s.HostingChannelUrl, - YouTubeVideoUrl = s.YouTubeVideoUrl + HostingChannelUrl = s.TwitchUrl ?? string.Empty, + YouTubeVideoUrl = s.YouTubeUrl }).Skip(skip).Take(size).ToArrayAsync(); var model = new StreamingsListModel { TotalItems = streamingsQuery.Count(), Items = streamings }; @@ -59,28 +53,27 @@ public async Task GetAllStreamingsAsync(string userId, int return new() { Id = streaming.Id, + TwitchChannel = streaming.TwitchChannel, + YouTubeChannel = streaming.YouTubeChannel, ScheduleDate = streaming.ScheduleDate, EndingTime = streaming.EndingTime, - HostingChannelUrl = streaming.HostingChannelUrl, + TwitchUrl = streaming.TwitchUrl ?? string.Empty, StartingTime = streaming.StartingTime, StreamingAbstract = streaming.Abstract, Title = streaming.Title, - YoutubeVideoUrl = streaming.YouTubeVideoUrl, - Slug = streaming.Slug + YouTubeUrl = streaming.YouTubeUrl ?? string.Empty, + Slug = streaming.Slug, + Seo = new Models.SeoData + { + Title = streaming.Seo?.Title ?? string.Empty, + Description = streaming.Seo?.Description ?? string.Empty, + Keywords = streaming.Seo?.Keywords ?? string.Empty, + } }; } public Task ImportStreamingAsync(ImportStreamingModel model, string userId) { - var settings = Database.Settings - .ByUserId(userId) - .FirstOrDefault(); - - if (settings is null) - { - throw new InvalidOperationException("No settings configured"); - } - var seo = new Core.Models.Content.SeoData { Title = model.Seo.Title, @@ -90,29 +83,21 @@ public Task ImportStreamingAsync(ImportStreamingModel model, string userId return Commands.ImportStreamingAsync( userId, - settings.TwitchChannel, + model.TwitchChannel, + model.YouTubeChannel, model.Title, model.Slug, - DateOnly.FromDateTime(model.ScheduleDate), - TimeOnly.FromTimeSpan(model.StartingTime), - TimeOnly.FromTimeSpan(model.EndingTime), - model.HostingChannelUrl, + model.ScheduleDate, + model.StartingTime, + model.EndingTime, + model.TwitchUrl, model.StreamingAbstract, - model.YoutubeVideoUrl, + model.YouTubeUrl, seo); } public Task ScheduleStreamingAsync(ScheduleStreamingModel model, string userId) { - var settings = Database.Settings - .ByUserId(userId) - .FirstOrDefault(); - - if (settings is null) - { - throw new InvalidOperationException("No settings configured"); - } - var seo = new Core.Models.Content.SeoData { Title = model.Seo.Title, @@ -122,13 +107,15 @@ public Task ScheduleStreamingAsync(ScheduleStreamingModel model, string us return Commands.ScheduleStreamingAsync( userId, - settings.TwitchChannel, + model.TwitchChannel, + model.YouTubeChannel, model.Title, model.Slug, model.ScheduleDate, model.StartingTime, model.EndingTime, - model.HostingChannelUrl, + model.TwitchUrl, + model.YouTubeUrl, model.StreamingAbstract, seo); } @@ -144,13 +131,15 @@ public Task UpdateStreamingAsync(Guid streamingId, StreamingDetailModel model) return Commands.UpdateStreamingAsync( streamingId, + model.TwitchChannel, + model.YouTubeChannel, model.Title, model.ScheduleDate, model.StartingTime, model.EndingTime, - model.HostingChannelUrl, + model.TwitchUrl, model.StreamingAbstract, - model.YoutubeVideoUrl, + model.YouTubeUrl, seo); } diff --git a/src/KITT.Cms.Web.Api/Endpoints/SettingsEndpoints.cs b/src/KITT.Cms.Web.Api/Endpoints/SettingsEndpoints.cs index dc049ef..2414a69 100644 --- a/src/KITT.Cms.Web.Api/Endpoints/SettingsEndpoints.cs +++ b/src/KITT.Cms.Web.Api/Endpoints/SettingsEndpoints.cs @@ -9,61 +9,101 @@ namespace KITT.Cms.Web.Api.Endpoints; public static class SettingsEndpoints { - public static IEndpointRouteBuilder MapSettingsEndpoints(this IEndpointRouteBuilder builder) + extension(IEndpointRouteBuilder builder) { - var settingsGroup = builder - .MapGroup("api/settings") - .RequireAuthorization() - .WithParameterValidation(); - - settingsGroup - .MapGet("", GetAllSettings) - .WithName(nameof(GetAllSettings)) - .WithOpenApi(); - - settingsGroup - .MapGet("{id}", GetSettingsDetails) - .WithName(nameof(GetSettingsDetails)) - .WithOpenApi(); - - settingsGroup - .MapPost("", CreateNewSettings) - .WithName(nameof(CreateNewSettings)) - .WithOpenApi(); - - return builder; + public IEndpointRouteBuilder MapSettingsEndpoints() + { + var settingsGroup = builder + .MapGroup("api/settings") + .RequireAuthorization(); + + settingsGroup + .MapGet("channels", GetChannels) + .WithName(nameof(GetChannels)); + + settingsGroup + .MapPost("channels", CreateChannel) + .WithName(nameof(CreateChannel)); + + settingsGroup + .MapGet("channels/{id:guid}", GetChannelDetail) + .WithName(nameof(GetChannelDetail)); + + settingsGroup + .MapPut("channels/{id:guid}", UpdateChannel) + .WithName(nameof(UpdateChannel)); + + settingsGroup + .MapDelete("channels/{id:guid}", DeleteChannel) + .WithName(nameof(DeleteChannel)); + + return builder; + } } - private static async Task> GetAllSettings( - SettingsEndpointsServices services, - ClaimsPrincipal user) + private static async Task> GetChannels( + ChannelsEndpointsServices services, + ClaimsPrincipal user) { - var model = await services.GetAllSettingsAsync(user.GetUserId()); - return TypedResults.Ok(model); + var userId = user.GetUserId(); + var channels = await services.GetChannelsAsync(userId); + + return TypedResults.Ok(channels); + } + + private static async Task, BadRequest, ValidationProblem>> CreateChannel( + ChannelsEndpointsServices services, + ClaimsPrincipal user, + [FromBody] ChannelModel model) + { + var userId = user.GetUserId(); + var createdChannel = await services.CreateChannelAsync(model, userId); + + return TypedResults.CreatedAtRoute(createdChannel, nameof(GetChannelDetail), new { id = createdChannel.Id }); } - private static async Task, NotFound>> GetSettingsDetails( - SettingsEndpointsServices services, + private static async Task, NotFound>> GetChannelDetail( + ChannelsEndpointsServices services, + ClaimsPrincipal user, Guid id) { - var settings = await services.GetSettingsDetailAsync(id); - if (settings is null) + var userId = user.GetUserId(); + var channel = await services.GetChannelAsync(id, userId); + if (channel is null) { return TypedResults.NotFound(); } - return TypedResults.Ok(settings); + return TypedResults.Ok(channel); } - private static async Task, BadRequest, ValidationProblem>> CreateNewSettings( - SettingsEndpointsServices services, + private static async Task> UpdateChannel( + ChannelsEndpointsServices services, ClaimsPrincipal user, - [FromBody] CreateNewSettingsModel model) + Guid id, + [FromBody] ChannelModel model) { - var settingsId = await services.CreateNewSettingsAsync(model, user.GetUserId()); - return TypedResults.CreatedAtRoute( - model, - nameof(GetSettingsDetails), - new { id = settingsId }); + var userId = user.GetUserId(); + + try + { + await services.UpdateChannelAsync(id, model, userId); + return TypedResults.NoContent(); + } + catch (ArgumentOutOfRangeException) + { + return TypedResults.NotFound(); + } + } + + private static async Task DeleteChannel( + ChannelsEndpointsServices services, + ClaimsPrincipal user, + Guid id) + { + var userId = user.GetUserId(); + await services.DeleteChannelAsync(id, userId); + + return TypedResults.NoContent(); } } diff --git a/src/KITT.Cms.Web.Api/Endpoints/StreamingsEndpoints.cs b/src/KITT.Cms.Web.Api/Endpoints/StreamingsEndpoints.cs index 98a384e..326fac3 100644 --- a/src/KITT.Cms.Web.Api/Endpoints/StreamingsEndpoints.cs +++ b/src/KITT.Cms.Web.Api/Endpoints/StreamingsEndpoints.cs @@ -9,49 +9,44 @@ namespace KITT.Cms.Web.Api.Endpoints; public static class StreamingsEndpoints { - public static IEndpointRouteBuilder MapStreamingsEndpoints(this IEndpointRouteBuilder builder) + extension(IEndpointRouteBuilder builder) { - var streamingsGroup = builder - .MapGroup("api/streamings") - .RequireAuthorization() - .WithParameterValidation(); - - streamingsGroup - .MapGet("", GetAllStreamings) - .WithName(nameof(GetAllStreamings)) - .WithOpenApi(); - - streamingsGroup - .MapGet("{id:guid}", GetStreamingDetail) - .WithName(nameof(GetStreamingDetail)) - .WithOpenApi(); - - streamingsGroup - .MapPost("", ScheduleStreaming) - .WithName(nameof(ScheduleStreaming)) - .WithOpenApi(); - - streamingsGroup - .MapPost("import", ImportStreaming) - .WithName(nameof(ImportStreaming)) - .WithOpenApi(); - - streamingsGroup - .MapPut("{id:guid}", UpdateStreaming) - .WithName(nameof(UpdateStreaming)) - .WithOpenApi(); - - streamingsGroup - .MapDelete("{id:guid}", DeleteStreaming) - .WithName(nameof(DeleteStreaming)) - .WithOpenApi(); - - streamingsGroup - .MapGet("stats", GetStreamingStats) - .WithName(nameof(GetStreamingStats)) - .WithOpenApi(); - - return builder; + public IEndpointRouteBuilder MapStreamingsEndpoints() + { + var streamingsGroup = builder + .MapGroup("api/streamings") + .RequireAuthorization(); + + streamingsGroup + .MapGet("", GetAllStreamings) + .WithName(nameof(GetAllStreamings)); + + streamingsGroup + .MapGet("{id:guid}", GetStreamingDetail) + .WithName(nameof(GetStreamingDetail)); + + streamingsGroup + .MapPost("", ScheduleStreaming) + .WithName(nameof(ScheduleStreaming)); + + streamingsGroup + .MapPost("import", ImportStreaming) + .WithName(nameof(ImportStreaming)); + + streamingsGroup + .MapPut("{id:guid}", UpdateStreaming) + .WithName(nameof(UpdateStreaming)); + + streamingsGroup + .MapDelete("{id:guid}", DeleteStreaming) + .WithName(nameof(DeleteStreaming)); + + streamingsGroup + .MapGet("stats", GetStreamingStats) + .WithName(nameof(GetStreamingStats)); + + return builder; + } } private static async Task> GetAllStreamings( diff --git a/src/KITT.Cms.Web.Api/KITT.Cms.Web.Api.csproj b/src/KITT.Cms.Web.Api/KITT.Cms.Web.Api.csproj index bc05eb5..dfd424a 100644 --- a/src/KITT.Cms.Web.Api/KITT.Cms.Web.Api.csproj +++ b/src/KITT.Cms.Web.Api/KITT.Cms.Web.Api.csproj @@ -1,18 +1,19 @@  - net9.0 + net10.0 enable enable - - - + + + + diff --git a/src/KITT.Cms.Web.Api/Program.cs b/src/KITT.Cms.Web.Api/Program.cs index 956723c..5ec12fe 100644 --- a/src/KITT.Cms.Web.Api/Program.cs +++ b/src/KITT.Cms.Web.Api/Program.cs @@ -6,12 +6,17 @@ using KITT.Telegram.Messages; using Microsoft.AspNetCore.Authentication.JwtBearer; using KITT.Services; +using KITT.Cms.Settings; var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); builder.AddSqlServerDbContext(ServiceNames.Database); +builder.AddAzureTableServiceClient(ServiceNames.SettingsTables); + +builder.Services.AddValidation(); + builder.Services.AddAuthentication() .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { @@ -25,9 +30,11 @@ builder.Services.AddKittCore(); +builder.Services.AddSingleton(); + builder.Services .AddScoped() - .AddScoped(); + .AddScoped(); builder.Services.AddProblemDetails(); @@ -65,7 +72,3 @@ .MapSettingsEndpoints(); app.Run(); - -#region Testing workaround -public partial class Program { } // This is a workaround for testing purposes, to allow the test project to reference the Program class. -#endregion \ No newline at end of file diff --git a/src/KITT.Cms.Web.App/Clients/ClientsServiceCollectionExtensions.cs b/src/KITT.Cms.Web.App/Clients/ClientsServiceCollectionExtensions.cs index 36a6f68..8421fbe 100644 --- a/src/KITT.Cms.Web.App/Clients/ClientsServiceCollectionExtensions.cs +++ b/src/KITT.Cms.Web.App/Clients/ClientsServiceCollectionExtensions.cs @@ -5,13 +5,14 @@ namespace KITT.Cms.Web.App.Clients; public static class ClientsServiceCollectionExtensions { - public static IServiceCollection AddCmsClients( - this IServiceCollection services, - string apiBaseUrl) + extension(IServiceCollection services) { - services - .AddHttpClient(client => client.BaseAddress = new(apiBaseUrl)); + public IServiceCollection AddCmsClients(string apiBaseUrl) + { + services.AddHttpClient(client => client.BaseAddress = new(apiBaseUrl)); + services.AddHttpClient(client => client.BaseAddress = new(apiBaseUrl)); - return services; + return services; + } } } diff --git a/src/KITT.Cms.Web.App/Clients/Http/ConnectedChannelsHttpClient.cs b/src/KITT.Cms.Web.App/Clients/Http/ConnectedChannelsHttpClient.cs new file mode 100644 index 0000000..1a6b9a4 --- /dev/null +++ b/src/KITT.Cms.Web.App/Clients/Http/ConnectedChannelsHttpClient.cs @@ -0,0 +1,36 @@ +using KITT.Cms.Web.Models.Settings; +using System.Net.Http.Json; + +namespace KITT.Cms.Web.App.Clients.Http; + +public class ConnectedChannelsHttpClient(HttpClient httpClient) : IConnectedChannelsClient +{ + public string ApiResource { get; } = "/api/cms/settings/channels"; + + public async Task CreateNewConnectedChannelAsync(ChannelModel model) + { + var response = await httpClient.PostAsJsonAsync(ApiResource, model); + response.EnsureSuccessStatusCode(); + + var createdChannel = await response.Content.ReadFromJsonAsync(); + return createdChannel ?? throw new InvalidOperationException("Invalid channel"); + } + + public async Task DeleteConnectedChannelAsync(ChannelModel model) + { + var response = await httpClient.DeleteAsync($"{ApiResource}/{model.Id}"); + response.EnsureSuccessStatusCode(); + } + + public async Task GetConnectedChannelsAsync() + { + var channels = await httpClient.GetFromJsonAsync(ApiResource); + return channels ?? []; + } + + public async Task UpdateConnectedChannelAsync(ChannelModel model) + { + var response = await httpClient.PutAsJsonAsync($"{ApiResource}/{model.Id}", model); + response.EnsureSuccessStatusCode(); + } +} diff --git a/src/KITT.Cms.Web.App/Clients/Http/StreamingsHttpClient.cs b/src/KITT.Cms.Web.App/Clients/Http/StreamingsHttpClient.cs index 20600c5..987770c 100644 --- a/src/KITT.Cms.Web.App/Clients/Http/StreamingsHttpClient.cs +++ b/src/KITT.Cms.Web.App/Clients/Http/StreamingsHttpClient.cs @@ -3,20 +3,13 @@ namespace KITT.Cms.Web.App.Clients.Http; -public class StreamingsHttpClient : IStreamingsClient +public class StreamingsHttpClient(HttpClient httpClient) : IStreamingsClient { - public HttpClient Client { get; } - public string ApiResource { get; } = "/api/cms/streamings"; - public StreamingsHttpClient(HttpClient client) - { - Client = client ?? throw new ArgumentNullException(nameof(client)); - } - public async Task ScheduleStreamingAsync(ScheduleStreamingModel model) { - var response = await Client.PostAsJsonAsync(ApiResource, model); + var response = await httpClient.PostAsJsonAsync(ApiResource, model); if (!response.IsSuccessStatusCode) { throw new ApplicationException("Error scheduling streaming"); @@ -32,7 +25,7 @@ public async Task GetAllStreamingsAsync(StreamingQueryModel url = $"{url}?{queryString}"; } - var model = await Client.GetFromJsonAsync(url); + var model = await httpClient.GetFromJsonAsync(url); return model ?? new StreamingsListModel(); } @@ -40,7 +33,7 @@ public async Task GetAllStreamingsAsync(StreamingQueryModel { try { - var model = await Client.GetFromJsonAsync($"{ApiResource}/{streamingId}"); + var model = await httpClient.GetFromJsonAsync($"{ApiResource}/{streamingId}"); return model; } catch @@ -51,7 +44,7 @@ public async Task GetAllStreamingsAsync(StreamingQueryModel public async Task UpdateStreamingAsync(StreamingDetailModel model) { - var response = await Client.PutAsJsonAsync($"{ApiResource}/{model.Id}", model); + var response = await httpClient.PutAsJsonAsync($"{ApiResource}/{model.Id}", model); if (!response.IsSuccessStatusCode) { throw new ApplicationException("Error updating streaming"); @@ -60,7 +53,7 @@ public async Task UpdateStreamingAsync(StreamingDetailModel model) public async Task DeleteStreamingAsync(Guid streamingId) { - var response = await Client.DeleteAsync($"{ApiResource}/{streamingId}"); + var response = await httpClient.DeleteAsync($"{ApiResource}/{streamingId}"); if (!response.IsSuccessStatusCode) { throw new ApplicationException("Error deleting streaming"); @@ -69,7 +62,7 @@ public async Task DeleteStreamingAsync(Guid streamingId) public async Task ImportStreamingAsync(ImportStreamingModel model) { - var response = await Client.PostAsJsonAsync($"{ApiResource}/import", model); + var response = await httpClient.PostAsJsonAsync($"{ApiResource}/import", model); if (!response.IsSuccessStatusCode) { throw new ApplicationException("Error importing streaming"); @@ -78,7 +71,7 @@ public async Task ImportStreamingAsync(ImportStreamingModel model) public async Task GetStreamingStatsAsync() { - var streamingStats = await Client.GetFromJsonAsync($"{ApiResource}/stats"); + var streamingStats = await httpClient.GetFromJsonAsync($"{ApiResource}/stats"); return streamingStats ?? new(0, 0); } } diff --git a/src/KITT.Cms.Web.App/Clients/IConnectedChannelsClient.cs b/src/KITT.Cms.Web.App/Clients/IConnectedChannelsClient.cs new file mode 100644 index 0000000..4c2f030 --- /dev/null +++ b/src/KITT.Cms.Web.App/Clients/IConnectedChannelsClient.cs @@ -0,0 +1,14 @@ +using KITT.Cms.Web.Models.Settings; + +namespace KITT.Cms.Web.App.Clients; + +public interface IConnectedChannelsClient +{ + Task GetConnectedChannelsAsync(); + + Task CreateNewConnectedChannelAsync(ChannelModel model); + + Task UpdateConnectedChannelAsync(ChannelModel model); + + Task DeleteConnectedChannelAsync(ChannelModel model); +} diff --git a/src/KITT.Cms.Web.App/Components/ChannelFormPanel.razor b/src/KITT.Cms.Web.App/Components/ChannelFormPanel.razor new file mode 100644 index 0000000..e693635 --- /dev/null +++ b/src/KITT.Cms.Web.App/Components/ChannelFormPanel.razor @@ -0,0 +1,55 @@ +@implements IDialogContentComponent + +@using KITT.Cms.Settings.Models + + + + + + + + + + + + + + + + + + + + + @localizer[nameof(Resources.Components.ChannelFormPanel.SaveButtonText)] + + + @localizer[nameof(Resources.Components.ChannelFormPanel.CloseButtonText)] + + + diff --git a/src/KITT.Cms.Web.App/Components/ChannelFormPanel.razor.cs b/src/KITT.Cms.Web.App/Components/ChannelFormPanel.razor.cs new file mode 100644 index 0000000..ff315b9 --- /dev/null +++ b/src/KITT.Cms.Web.App/Components/ChannelFormPanel.razor.cs @@ -0,0 +1,73 @@ +using KITT.Cms.Settings.Models; +using KITT.Cms.Web.Models.Settings; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.Extensions.Localization; +using Microsoft.FluentUI.AspNetCore.Components; + +namespace KITT.Cms.Web.App.Components; + +public partial class ChannelFormPanel( + IToastService toastService, + IStringLocalizer localizer) : IDialogContentComponent +{ + [Parameter] + public ViewModel Content { get; set; } = new(); + + [CascadingParameter] + FluentDialog Dialog { get; set; } = default!; + + private EditContext context = default!; + + private bool saving = false; + + private readonly ChannelType[] channelTypes = Enum.GetValues(); + + private string UrlPlaceholder => Content.Model.Type switch + { + ChannelType.Twitch => "https://www.twitch.tv/channelName", + ChannelType.YouTube => "https://www.youtube.com/@channelName", + _ => string.Empty + }; + + protected override void OnInitialized() + { + context = new(Content.Model); + } + + private async Task SaveAsync() + { + saving = true; + + try + { + if (!context.Validate()) + { + return; + } + + await Content.OnChannelSave.InvokeAsync(Content.Model); + + toastService.ShowSuccess(localizer[nameof(Resources.Components.ChannelFormPanel.ChannelSavedSuccessMessage)]); + + await Dialog.CloseAsync(DialogResult.Ok(true)); + } + catch + { + toastService.ShowError(localizer[nameof(Resources.Components.ChannelFormPanel.ChannelSavedErrorMessage)]); + } + finally + { + saving = false; + } + } + + private async Task CloseAsync() => await Dialog.CloseAsync(DialogResult.Cancel()); + + public class ViewModel + { + public ChannelModel Model { get; set; } = new(); + + public EventCallback OnChannelSave { get; set; } + } +} diff --git a/src/KITT.Cms.Web.App/Components/ContentForm.razor.cs b/src/KITT.Cms.Web.App/Components/ContentForm.razor.cs index 32c1329..b0c4293 100644 --- a/src/KITT.Cms.Web.App/Components/ContentForm.razor.cs +++ b/src/KITT.Cms.Web.App/Components/ContentForm.razor.cs @@ -53,6 +53,7 @@ private async Task SubmitAsync() finally { saving = false; + editContext?.MarkAsUnmodified(); } } diff --git a/src/KITT.Cms.Web.App/Components/StreamingForm.razor b/src/KITT.Cms.Web.App/Components/StreamingForm.razor new file mode 100644 index 0000000..7148c23 --- /dev/null +++ b/src/KITT.Cms.Web.App/Components/StreamingForm.razor @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/KITT.Cms.Web.App/Components/StreamingForm.razor.cs b/src/KITT.Cms.Web.App/Components/StreamingForm.razor.cs new file mode 100644 index 0000000..63e2a33 --- /dev/null +++ b/src/KITT.Cms.Web.App/Components/StreamingForm.razor.cs @@ -0,0 +1,68 @@ +using KITT.Cms.Web.App.Clients; +using KITT.Cms.Web.Models; +using KITT.Cms.Web.Models.Settings; +using Microsoft.AspNetCore.Components; +using System.ComponentModel.DataAnnotations; + +namespace KITT.Cms.Web.App.Components; + +public partial class StreamingForm( + IConnectedChannelsClient channelsClient) +{ + [Parameter] + public ViewModel Model { get; set; } = new(); + + [Parameter] + public EventCallback OnSave { get; set; } + + [Parameter] + public EventCallback OnCancel { get; set; } + + private ChannelModel?[] availableTwitchChannels = []; + + private ChannelModel?[] availableYouTubeChannels = []; + + protected override async Task OnInitializedAsync() + { + var channels = await channelsClient.GetConnectedChannelsAsync(); + + availableTwitchChannels = [null, ..channels.Where(c => c.Type is Settings.Models.ChannelType.Twitch).ToArray()]; + availableYouTubeChannels = [null, ..channels.Where(c => c.Type is Settings.Models.ChannelType.YouTube).ToArray()]; + } + + public class ViewModel : ContentViewModel, IValidatableObject + { + public string TwitchChannel { get; set; } = string.Empty; + + public string YouTubeChannel { get; set; } = string.Empty; + + [Required] + public string Title { get; set; } = string.Empty; + + [Required] + public string Slug { get; set; } = string.Empty; + + [Required] + public DateTime? ScheduleDate { get; set; } = DateTime.Now; + + [Required] + public DateTime? StartingTime { get; set; } = DateTime.Now; + + [Required] + public DateTime? EndingTime { get; set; } = DateTime.Now.AddHours(1); + + public string TwitchUrl { get; set; } = string.Empty; + + public string? StreamingAbstract { get; set; } + + public string YouTubeUrl { get; set; } = string.Empty; + + public IEnumerable Validate(ValidationContext validationContext) + { + if (string.IsNullOrWhiteSpace(TwitchChannel) && string.IsNullOrWhiteSpace(YouTubeChannel)) + { + yield return new("At least a channel must be selected", [nameof(TwitchChannel), nameof(YouTubeChannel)]); + } + } + } +} diff --git a/src/KITT.Cms.Web.App/KITT.Cms.Web.App.csproj b/src/KITT.Cms.Web.App/KITT.Cms.Web.App.csproj index 37fec1e..83089a9 100644 --- a/src/KITT.Cms.Web.App/KITT.Cms.Web.App.csproj +++ b/src/KITT.Cms.Web.App/KITT.Cms.Web.App.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable @@ -12,7 +12,7 @@ - + @@ -21,6 +21,11 @@ + + True + True + ChannelFormPanel.resx + True True @@ -31,6 +36,11 @@ True StreamingStats.resx + + True + True + Channels.resx + True True @@ -54,6 +64,10 @@ + + PublicResXFileCodeGenerator + ChannelFormPanel.Designer.cs + PublicResXFileCodeGenerator @@ -68,6 +82,10 @@ ResXFileCodeGenerator StreamingStats.Designer.cs + + PublicResXFileCodeGenerator + Channels.Designer.cs + ResXFileCodeGenerator Import.Designer.cs diff --git a/src/KITT.Cms.Web.App/Pages/Settings/Channels.razor b/src/KITT.Cms.Web.App/Pages/Settings/Channels.razor new file mode 100644 index 0000000..54b8d83 --- /dev/null +++ b/src/KITT.Cms.Web.App/Pages/Settings/Channels.razor @@ -0,0 +1,51 @@ +@page "/settings/channels" + +KITT - @localizer[nameof(Resources.Pages.Settings.Channels.PageTitle)] + + + @localizer[nameof(Resources.Pages.Settings.Channels.PageTitle)] + + + + + @localizer[nameof(Resources.Pages.Settings.Channels.AddNewChannelButtonText)] + + + @localizer[nameof(Resources.Pages.Settings.Channels.RefreshChannelsButtonText)] + + + + + + @foreach (var channel in channels) + { + + +

@channel.Name

+

@channel.Url

+ + + + @localizer[nameof(Resources.Pages.Settings.Channels.EditChannelButtonText)] + + + @localizer[nameof(Resources.Pages.Settings.Channels.DeleteChannelButtonText)] + + +
+
+ } +
+
\ No newline at end of file diff --git a/src/KITT.Cms.Web.App/Pages/Settings/Channels.razor.cs b/src/KITT.Cms.Web.App/Pages/Settings/Channels.razor.cs new file mode 100644 index 0000000..0bd4e53 --- /dev/null +++ b/src/KITT.Cms.Web.App/Pages/Settings/Channels.razor.cs @@ -0,0 +1,133 @@ +using KITT.Cms.Web.App.Clients; +using KITT.Cms.Web.App.Components; +using KITT.Cms.Web.Models.Settings; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Microsoft.FluentUI.AspNetCore.Components; + +namespace KITT.Cms.Web.App.Pages.Settings; + +public partial class Channels( + IDialogService dialogService, + IToastService toastService, + IConnectedChannelsClient client, + IStringLocalizer localizer) +{ + private ChannelModel[] channels = []; + + private bool loading = false; + + protected override async Task OnInitializedAsync() + { + await LoadConnectedChannelsAsync(); + } + + private async Task CreateNewChannelAsync(ChannelModel channel) + { + await client.CreateNewConnectedChannelAsync(channel); + } + + private async Task LoadConnectedChannelsAsync() + { + loading = true; + + try + { + channels = await client.GetConnectedChannelsAsync(); + } + finally + { + loading = false; + } + } + + private async Task OpenAddNewChannelPanelAsync() + { + var panelModel = new ChannelFormPanel.ViewModel + { + Model = new(), + OnChannelSave = EventCallback.Factory.Create(this, CreateNewChannelAsync) + }; + + var dialog = await dialogService.ShowPanelAsync( + panelModel, + new DialogParameters() + { + Content = panelModel, + Alignment = HorizontalAlignment.Right, + Title = localizer[nameof(Resources.Pages.Settings.Channels.AddNewChannelPanelTitle)], + Width = "50em", + }); + + var result = await dialog.Result; + if (!result.Cancelled) + { + await LoadConnectedChannelsAsync(); + } + } + + private async Task OpenEditChannelPanelAsync(ChannelModel channel) + { + var channelCopy = new ChannelModel + { + Id = channel.Id, + Name = channel.Name, + Type = channel.Type, + Url = channel.Url, + }; + + var panelModel = new ChannelFormPanel.ViewModel + { + Model = channelCopy, + OnChannelSave = EventCallback.Factory.Create(this, UpdateChannelAsync) + }; + + var dialog = await dialogService.ShowPanelAsync( + panelModel, + new DialogParameters() + { + Content = panelModel, + Alignment = HorizontalAlignment.Right, + Title = localizer[nameof(Resources.Pages.Settings.Channels.EditChannelPanelTitle), channel.Name], + Width = "50em", + }); + + var result = await dialog.Result; + if (!result.Cancelled) + { + await LoadConnectedChannelsAsync(); + } + } + + private async Task UpdateChannelAsync(ChannelModel channel) + { + await client.UpdateConnectedChannelAsync(channel); + } + + private async Task DeleteChannelAsync(ChannelModel channel) + { + var channelName = channel.Name; + + var confirm = await dialogService.ShowConfirmationAsync( + localizer[nameof(Resources.Pages.Settings.Channels.DeleteChannelConfirmMessage)], + title: localizer[nameof(Resources.Pages.Settings.Channels.DeleteChannelConfirmTitle), channelName], + primaryText: localizer[nameof(Resources.Pages.Settings.Channels.DeleteChannelConfirmPrimaryButtonText)]); + + var result = await confirm.Result; + + if (!result.Cancelled) + { + try + { + await client.DeleteConnectedChannelAsync(channel); + toastService.ShowSuccess(localizer[nameof(Resources.Pages.Settings.Channels.DeletedChannelSuccessMessage), channelName]); + + await LoadConnectedChannelsAsync(); + } + catch + { + toastService.ShowError(localizer[nameof(Resources.Pages.Settings.Channels.DeletedChannelErrorMessage), channelName]); + } + } + } +} diff --git a/src/KITT.Cms.Web.App/Pages/Streamings/Import.razor b/src/KITT.Cms.Web.App/Pages/Streamings/Import.razor index c7cf0f7..9642353 100644 --- a/src/KITT.Cms.Web.App/Pages/Streamings/Import.razor +++ b/src/KITT.Cms.Web.App/Pages/Streamings/Import.razor @@ -15,85 +15,6 @@ - - - - - - - - - - - - - - - - - https://www.twitch.tv/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/KITT.Cms.Web.App/Pages/Streamings/Import.razor.cs b/src/KITT.Cms.Web.App/Pages/Streamings/Import.razor.cs index 70faf10..705f480 100644 --- a/src/KITT.Cms.Web.App/Pages/Streamings/Import.razor.cs +++ b/src/KITT.Cms.Web.App/Pages/Streamings/Import.razor.cs @@ -1,44 +1,33 @@ using KITT.Cms.Web.App.Clients; -using KITT.Cms.Web.Models; +using KITT.Cms.Web.App.Components; using KITT.Cms.Web.Models.Streamings; using KITT.Web.App.UI; using Microsoft.AspNetCore.Components; using Microsoft.FluentUI.AspNetCore.Components; -using System.ComponentModel.DataAnnotations; namespace KITT.Cms.Web.App.Pages.Streamings; -public partial class Import +public partial class Import( + IStreamingsClient client, + NavigationManager navigation, + IToastService toastService, + IMessageService messageService) { - [Inject] - public IStreamingsClient Client { get; set; } = default!; + private StreamingForm.ViewModel model = new(); - [Inject] - public NavigationManager Navigation { get; set; } = default!; - - [Inject] - public IToastService ToastService { get; set; } = default!; - - [Inject] - public IMessageService MessageService { get; set; } = default!; - - private ViewModel model = new(); - - private string errorMessage = string.Empty; - - private async Task ImportStreamingAsync(ViewModel model) + private async Task ImportStreamingAsync(StreamingForm.ViewModel model) { try { - await Client.ImportStreamingAsync(model.ToApiModel()); - ToastService.ShowSuccess( + await client.ImportStreamingAsync(ConvertToApiModel(model)); + toastService.ShowSuccess( Localizer[nameof(Resources.Pages.Streamings.Import.StreamingImportedSuccessfully), model.Title]); - Navigation.NavigateTo("/streamings"); + navigation.NavigateTo("/streamings"); } catch (ApplicationException ex) { - await MessageService.ShowMessageBarAsync( + await messageService.ShowMessageBarAsync( ex.Message, MessageIntent.Error, SectionNames.MessagesTopSectionName); @@ -47,59 +36,36 @@ await MessageService.ShowMessageBarAsync( void Cancel() => model = new(); - class ViewModel : ContentViewModel + private static ImportStreamingModel ConvertToApiModel(StreamingForm.ViewModel model) { - [Required] - public string Title { get; set; } = string.Empty; - - [Required] - public string Slug { get; set; } = string.Empty; - - [Required] - public DateTime? ScheduleDate { get; set; } = DateTime.Now; - - [Required] - public DateTime? StartingTime { get; set; } = DateTime.Now; - - [Required] - public DateTime? EndingTime { get; set; } = DateTime.Now.AddHours(1); - - [Required] - public string HostingChannelUrl { get; set; } = string.Empty; - - public string? StreamingAbstract { get; set; } - - public string? YoutubeVideoUrl { get; set; } - - public ImportStreamingModel ToApiModel() + if (!model.ScheduleDate.HasValue) { - if (this.ScheduleDate is null) - { - throw new ArgumentNullException(nameof(ScheduleDate)); - } - - if (this.StartingTime is null) - { - throw new ArgumentNullException(nameof(this.StartingTime)); - } + throw new ArgumentNullException(nameof(model.ScheduleDate)); + } - if (this.EndingTime is null) - { - throw new ArgumentNullException(nameof(this.EndingTime)); - } + if (!model.StartingTime.HasValue) + { + throw new ArgumentNullException(nameof(model.StartingTime)); + } - return new() - { - Title = this.Title, - ScheduleDate = this.ScheduleDate.Value, - EndingTime = this.EndingTime.Value.TimeOfDay, - HostingChannelUrl = $"https://www.twitch.tv/{this.HostingChannelUrl}", - Slug = this.Slug, - StartingTime = this.StartingTime.Value.TimeOfDay, - StreamingAbstract = this.StreamingAbstract, - YoutubeVideoUrl = this.YoutubeVideoUrl, - Seo = this.Seo - }; + if (!model.EndingTime.HasValue) + { + throw new ArgumentNullException(nameof(model.EndingTime)); } + + return new ImportStreamingModel + { + Title = model.Title, + TwitchChannel = model.TwitchChannel, + YouTubeChannel = model.YouTubeChannel, + ScheduleDate = DateOnly.FromDateTime(model.ScheduleDate.Value), + EndingTime = TimeOnly.FromTimeSpan(model.EndingTime.Value.TimeOfDay), + TwitchUrl = model.TwitchUrl, + YouTubeUrl = model.YouTubeUrl, + Slug = model.Slug, + StartingTime = TimeOnly.FromTimeSpan(model.StartingTime.Value.TimeOfDay), + StreamingAbstract = model.StreamingAbstract, + Seo = model.Seo + }; } } diff --git a/src/KITT.Cms.Web.App/Pages/Streamings/Schedule.razor b/src/KITT.Cms.Web.App/Pages/Streamings/Schedule.razor index e415870..d250928 100644 --- a/src/KITT.Cms.Web.App/Pages/Streamings/Schedule.razor +++ b/src/KITT.Cms.Web.App/Pages/Streamings/Schedule.razor @@ -15,5 +15,6 @@ - + diff --git a/src/KITT.Cms.Web.App/Pages/Streamings/Schedule.razor.cs b/src/KITT.Cms.Web.App/Pages/Streamings/Schedule.razor.cs index 54a9c24..ada0829 100644 --- a/src/KITT.Cms.Web.App/Pages/Streamings/Schedule.razor.cs +++ b/src/KITT.Cms.Web.App/Pages/Streamings/Schedule.razor.cs @@ -7,58 +7,63 @@ namespace KITT.Cms.Web.App.Pages.Streamings; -public partial class Schedule +public partial class Schedule( + IStreamingsClient client, + NavigationManager navigation, + IToastService toastService, + IMessageService messageService) { - [Inject] - public IStreamingsClient Client { get; set; } = default!; + private StreamingForm.ViewModel model = new(); - [Inject] - public NavigationManager Navigation { get; set; } = default!; - - [Inject] - public IToastService ToastService { get; set; } = default!; - - [Inject] - public IDialogService DialogService { get; set; } = default!; - - [Inject] - public IMessageService MessageService { get; set; } = default!; - - private ScheduleForm.ViewModel model = new(); - - private async Task ScheduleStreamingAsync(ScheduleForm.ViewModel model) + private async Task ScheduleStreamingAsync(StreamingForm.ViewModel model) { try { var scheduleStreamingModel = ConvertToApiModel(model); - await Client.ScheduleStreamingAsync(scheduleStreamingModel); + await client.ScheduleStreamingAsync(scheduleStreamingModel); - ToastService.ShowSuccess( + toastService.ShowSuccess( Localizer[nameof(Resources.Pages.Streamings.Schedule.StreamingScheduledSuccessfully), model.Title]); - Navigation.NavigateTo("/streamings"); + navigation.NavigateTo("/streamings"); } catch (ApplicationException ex) { - await MessageService.ShowMessageBarAsync( + await messageService.ShowMessageBarAsync( ex.Message, MessageIntent.Error, SectionNames.MessagesTopSectionName); } } - private static ScheduleStreamingModel ConvertToApiModel(ScheduleForm.ViewModel model) + private void Cancel() => model = new(); + + private static ScheduleStreamingModel ConvertToApiModel(StreamingForm.ViewModel model) { - ArgumentNullException.ThrowIfNull(model.ScheduleDate); - ArgumentNullException.ThrowIfNull(model.StartingTime); - ArgumentNullException.ThrowIfNull(model.EndingTime); + if (!model.ScheduleDate.HasValue) + { + throw new ArgumentNullException(nameof(model.ScheduleDate)); + } + + if (!model.StartingTime.HasValue) + { + throw new ArgumentNullException(nameof(model.StartingTime)); + } + + if (!model.EndingTime.HasValue) + { + throw new ArgumentNullException(nameof(model.EndingTime)); + } return new ScheduleStreamingModel { Title = model.Title, + TwitchChannel = model.TwitchChannel, + YouTubeChannel = model.YouTubeChannel, ScheduleDate = DateOnly.FromDateTime(model.ScheduleDate.Value), EndingTime = TimeOnly.FromTimeSpan(model.EndingTime.Value.TimeOfDay), - HostingChannelUrl = $"https://www.twitch.tv/{model.HostingChannelUrl}", + TwitchUrl = model.TwitchUrl, + YouTubeUrl = model.YouTubeUrl, Slug = model.Slug, StartingTime = TimeOnly.FromTimeSpan(model.StartingTime.Value.TimeOfDay), StreamingAbstract = model.StreamingAbstract, diff --git a/src/KITT.Cms.Web.App/Pages/Streamings/StreamingDetail.razor b/src/KITT.Cms.Web.App/Pages/Streamings/StreamingDetail.razor index ac99e61..b0b5191 100644 --- a/src/KITT.Cms.Web.App/Pages/Streamings/StreamingDetail.razor +++ b/src/KITT.Cms.Web.App/Pages/Streamings/StreamingDetail.razor @@ -32,6 +32,32 @@ OnSave="EditStreamingAsync" OnCancel="DisableEditing"> + + + + + + + + + + - @twitchBaseUrl - - + Disabled="@isReadOnly"/> + + diff --git a/src/KITT.Cms.Web.App/Pages/Streamings/StreamingDetail.razor.cs b/src/KITT.Cms.Web.App/Pages/Streamings/StreamingDetail.razor.cs index d370861..3e0af84 100644 --- a/src/KITT.Cms.Web.App/Pages/Streamings/StreamingDetail.razor.cs +++ b/src/KITT.Cms.Web.App/Pages/Streamings/StreamingDetail.razor.cs @@ -1,5 +1,6 @@ using KITT.Cms.Web.App.Clients; using KITT.Cms.Web.Models; +using KITT.Cms.Web.Models.Settings; using KITT.Cms.Web.Models.Streamings; using KITT.Web.App.UI; using Microsoft.AspNetCore.Components; @@ -9,28 +10,25 @@ namespace KITT.Cms.Web.App.Pages.Streamings; -public partial class StreamingDetail +public partial class StreamingDetail( + IStreamingsClient client, + IToastService toastService, + IMessageService messageService, + IConnectedChannelsClient channelsClient) { [Parameter] [EditorRequired] public Guid Id { get; set; } - [Inject] - public IStreamingsClient Client { get; set; } = default!; - - [Inject] - public IToastService ToastService { get; set; } = default!; - - [Inject] - public IMessageService MessageService { get; set; } = default!; - private bool isReadOnly = true; private ViewModel model = new(); private string pageTitle = "StreamingDetail"; - private const string twitchBaseUrl = "https://www.twitch.tv/"; + private ChannelModel?[] availableTwitchChannels = []; + + private ChannelModel?[] availableYouTubeChannels = []; private void EnableEditing() => isReadOnly = false; @@ -47,16 +45,16 @@ private async Task EditStreamingAsync(ViewModel model) try { var detail = model.ToApiModel(Id); - await Client.UpdateStreamingAsync(detail); + await client.UpdateStreamingAsync(detail); isReadOnly = true; - ToastService.ShowSuccess(Localizer[nameof(Resources.Pages.Streamings.StreamingDetail.StreamingSavedSuccessfully)]); + toastService.ShowSuccess(Localizer[nameof(Resources.Pages.Streamings.StreamingDetail.StreamingSavedSuccessfully)]); streamingDetail = detail; } catch (ApplicationException ex) { - await MessageService.ShowMessageBarAsync( + await messageService.ShowMessageBarAsync( ex.Message, MessageIntent.Error, SectionNames.MessagesTopSectionName); @@ -65,14 +63,28 @@ await MessageService.ShowMessageBarAsync( protected override async Task OnInitializedAsync() { - streamingDetail = await Client.GetStreamingDetailAsync(Id) ?? new(); + await LoadConnectedChannelsAsync(); + + streamingDetail = await client.GetStreamingDetailAsync(Id) ?? new(); model = ViewModel.FromStreamingDetailModel(streamingDetail); pageTitle = model.Title; } + private async Task LoadConnectedChannelsAsync() + { + var channels = await channelsClient.GetConnectedChannelsAsync(); + + availableTwitchChannels = [null, .. channels.Where(c => c.Type is Cms.Settings.Models.ChannelType.Twitch).ToArray()]; + availableYouTubeChannels = [null, .. channels.Where(c => c.Type is Cms.Settings.Models.ChannelType.YouTube).ToArray()]; + } + class ViewModel : ContentViewModel { + public string? TwitchChannel { get; set; } + + public string? YouTubeChannel { get; set; } + [Required] public string Title { get; set; } = string.Empty; @@ -87,12 +99,11 @@ class ViewModel : ContentViewModel [Required] public DateTime? EndingTime { get; set; } = DateTime.Now.AddHours(1); - [Required] - public string HostingChannelUrl { get; set; } = string.Empty; + public string TwitchUrl { get; set; } = string.Empty; public string? StreamingAbstract { get; set; } - public string? YoutubeVideoUrl { get; set; } + public string YouTubeUrl { get; set; } = string.Empty; public StreamingDetailModel ToApiModel(Guid streamingId) { @@ -119,14 +130,16 @@ public StreamingDetailModel ToApiModel(Guid streamingId) return new() { Id = streamingId, + TwitchChannel = this.TwitchChannel, + YouTubeChannel = this.YouTubeChannel, Title = this.Title, Slug = this.Slug, ScheduleDate = DateOnly.FromDateTime(this.ScheduleDate.Value), EndingTime = TimeOnly.FromTimeSpan(this.EndingTime.Value.TimeOfDay), - HostingChannelUrl = $"{twitchBaseUrl}{this.HostingChannelUrl}", + TwitchUrl = this.TwitchUrl, StartingTime = TimeOnly.FromTimeSpan(this.StartingTime.Value.TimeOfDay), StreamingAbstract = this.StreamingAbstract, - YoutubeVideoUrl = this.YoutubeVideoUrl, + YouTubeUrl = this.YouTubeUrl, Seo = this.Seo }; } @@ -136,13 +149,16 @@ public static ViewModel FromStreamingDetailModel(StreamingDetailModel model) return new() { Title = model.Title, + TwitchChannel = model.TwitchChannel, + YouTubeChannel = model.YouTubeChannel, Slug = model.Slug, ScheduleDate = model.ScheduleDate.ToDateTime(TimeOnly.MinValue), EndingTime = model.EndingTime.ToDateTime(), StartingTime = model.StartingTime.ToDateTime(), StreamingAbstract = model.StreamingAbstract, - YoutubeVideoUrl = model.YoutubeVideoUrl, - HostingChannelUrl = model.HostingChannelUrl.Replace(twitchBaseUrl, string.Empty).Trim() + YouTubeUrl = model.YouTubeUrl, + TwitchUrl = model.TwitchUrl, + Seo = model.Seo }; } } diff --git a/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.Designer.cs b/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.Designer.cs new file mode 100644 index 0000000..ee6be18 --- /dev/null +++ b/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.Designer.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace KITT.Cms.Web.App.Resources.Components { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class ChannelFormPanel { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ChannelFormPanel() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("KITT.Cms.Web.App.Resources.Components.ChannelFormPanel", typeof(ChannelFormPanel).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Name. + /// + public static string ChannelNameLabel { + get { + return ResourceManager.GetString("ChannelNameLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error saving the channel. + /// + public static string ChannelSavedErrorMessage { + get { + return ResourceManager.GetString("ChannelSavedErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel saved correctly. + /// + public static string ChannelSavedSuccessMessage { + get { + return ResourceManager.GetString("ChannelSavedSuccessMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel type. + /// + public static string ChannelTypeLabel { + get { + return ResourceManager.GetString("ChannelTypeLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Url. + /// + public static string ChannelUrlLabel { + get { + return ResourceManager.GetString("ChannelUrlLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close. + /// + public static string CloseButtonText { + get { + return ResourceManager.GetString("CloseButtonText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save. + /// + public static string SaveButtonText { + get { + return ResourceManager.GetString("SaveButtonText", resourceCulture); + } + } + } +} diff --git a/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.it.resx b/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.it.resx new file mode 100644 index 0000000..a95de29 --- /dev/null +++ b/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.it.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nome + + + Errore durante il salvataggio del canale + + + Canale salvato correttamente + + + Tipologia + + + Url + + + Chiudi + + + Salva + + \ No newline at end of file diff --git a/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.resx b/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.resx new file mode 100644 index 0000000..43a34ab --- /dev/null +++ b/src/KITT.Cms.Web.App/Resources/Components/ChannelFormPanel.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Error saving the channel + + + Channel saved correctly + + + Channel type + + + Url + + + Close + + + Save + + \ No newline at end of file diff --git a/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.Designer.cs b/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.Designer.cs new file mode 100644 index 0000000..53295a0 --- /dev/null +++ b/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.Designer.cs @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace KITT.Cms.Web.App.Resources.Pages.Settings { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Channels { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Channels() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("KITT.Cms.Web.App.Resources.Pages.Settings.Channels", typeof(Channels).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Add new channel. + /// + public static string AddNewChannelButtonText { + get { + return ResourceManager.GetString("AddNewChannelButtonText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add new channel. + /// + public static string AddNewChannelPanelTitle { + get { + return ResourceManager.GetString("AddNewChannelPanelTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete. + /// + public static string DeleteChannelButtonText { + get { + return ResourceManager.GetString("DeleteChannelButtonText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You are going to delete this channel. Are you sure?. + /// + public static string DeleteChannelConfirmMessage { + get { + return ResourceManager.GetString("DeleteChannelConfirmMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Yes. + /// + public static string DeleteChannelConfirmPrimaryButtonText { + get { + return ResourceManager.GetString("DeleteChannelConfirmPrimaryButtonText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete channel {0}. + /// + public static string DeleteChannelConfirmTitle { + get { + return ResourceManager.GetString("DeleteChannelConfirmTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There was an error deleting channel {0}. + /// + public static string DeletedChannelErrorMessage { + get { + return ResourceManager.GetString("DeletedChannelErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} deleted successfully!. + /// + public static string DeletedChannelSuccessMessage { + get { + return ResourceManager.GetString("DeletedChannelSuccessMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit. + /// + public static string EditChannelButtonText { + get { + return ResourceManager.GetString("EditChannelButtonText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit channel {0}. + /// + public static string EditChannelPanelTitle { + get { + return ResourceManager.GetString("EditChannelPanelTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Connected channels. + /// + public static string PageTitle { + get { + return ResourceManager.GetString("PageTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Refresh. + /// + public static string RefreshChannelsButtonText { + get { + return ResourceManager.GetString("RefreshChannelsButtonText", resourceCulture); + } + } + } +} diff --git a/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.it.resx b/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.it.resx new file mode 100644 index 0000000..4f470dd --- /dev/null +++ b/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.it.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nuovo canale + + + Nuovo canale + + + Cancella + + + Stai per cancellare questo canale. Sei sicuro? + + + Sì + + + Cancellazione canale {0} + + + Si è verificato un errore durante la cancellazione del canale {0} + + + {0} cancellato con successo! + + + Modifica + + + Modifica canale {0} + + + Canali collegati + + + Ricarica + + \ No newline at end of file diff --git a/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.resx b/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.resx new file mode 100644 index 0000000..22d9c4c --- /dev/null +++ b/src/KITT.Cms.Web.App/Resources/Pages/Settings/Channels.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add new channel + + + Add new channel + + + Delete + + + You are going to delete this channel. Are you sure? + + + Yes + + + Delete channel {0} + + + There was an error deleting channel {0} + + + {0} deleted successfully! + + + Edit + + + Edit channel {0} + + + Connected channels + + + Refresh + + \ No newline at end of file diff --git a/src/KITT.Cms.Web.App/_Imports.razor b/src/KITT.Cms.Web.App/_Imports.razor index 4dc0f34..43e5256 100644 --- a/src/KITT.Cms.Web.App/_Imports.razor +++ b/src/KITT.Cms.Web.App/_Imports.razor @@ -15,5 +15,7 @@ @using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @using Microsoft.AspNetCore.Authorization +@using FluentUI.Blazor.Community.Components +@using KITT.Cms.Web.Models.Settings @attribute [Authorize] diff --git a/src/KITT.Cms.Web.Models/KITT.Cms.Web.Models.csproj b/src/KITT.Cms.Web.Models/KITT.Cms.Web.Models.csproj index 125f4c9..22054ac 100644 --- a/src/KITT.Cms.Web.Models/KITT.Cms.Web.Models.csproj +++ b/src/KITT.Cms.Web.Models/KITT.Cms.Web.Models.csproj @@ -1,9 +1,13 @@  - net9.0 + net10.0 enable enable + + + + diff --git a/src/KITT.Cms.Web.Models/Settings/ChannelModel.cs b/src/KITT.Cms.Web.Models/Settings/ChannelModel.cs new file mode 100644 index 0000000..7f080d1 --- /dev/null +++ b/src/KITT.Cms.Web.Models/Settings/ChannelModel.cs @@ -0,0 +1,17 @@ +using KITT.Cms.Settings.Models; +using System.ComponentModel.DataAnnotations; + +namespace KITT.Cms.Web.Models.Settings; + +public class ChannelModel +{ + public Guid Id { get; set; } + + public ChannelType Type { get; set; } + + [Required] + public string Name { get; set; } = string.Empty; + + [Required] + public string Url { get; set; } = string.Empty; +} diff --git a/src/KITT.Cms.Web.Models/Settings/CreateNewSettingsModel.cs b/src/KITT.Cms.Web.Models/Settings/CreateNewSettingsModel.cs deleted file mode 100644 index fbb1903..0000000 --- a/src/KITT.Cms.Web.Models/Settings/CreateNewSettingsModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace KITT.Cms.Web.Models.Settings; - -public class CreateNewSettingsModel -{ - [Required] - public string TwitchChannel { get; set; } = string.Empty; -} diff --git a/src/KITT.Cms.Web.Models/Settings/SettingsDetailModel.cs b/src/KITT.Cms.Web.Models/Settings/SettingsDetailModel.cs deleted file mode 100644 index d747792..0000000 --- a/src/KITT.Cms.Web.Models/Settings/SettingsDetailModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace KITT.Cms.Web.Models.Settings; - -public class SettingsDetailModel -{ - public Guid Id { get; init; } - - public string TwitchChannel { get; init; } = string.Empty; -} diff --git a/src/KITT.Cms.Web.Models/Settings/SettingsListModel.cs b/src/KITT.Cms.Web.Models/Settings/SettingsListModel.cs deleted file mode 100644 index 0d10e9d..0000000 --- a/src/KITT.Cms.Web.Models/Settings/SettingsListModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace KITT.Cms.Web.Models.Settings; - -public class SettingsListModel -{ - public IEnumerable Items { get; set; } = []; - - public record SettingsDescriptor - { - public Guid Id { get; set; } - - public string TwitchChannel { get; set; } = string.Empty; - } -} diff --git a/src/KITT.Cms.Web.Models/Streamings/ImportStreamingModel.cs b/src/KITT.Cms.Web.Models/Streamings/ImportStreamingModel.cs index cd54620..0f83aa3 100644 --- a/src/KITT.Cms.Web.Models/Streamings/ImportStreamingModel.cs +++ b/src/KITT.Cms.Web.Models/Streamings/ImportStreamingModel.cs @@ -2,8 +2,12 @@ namespace KITT.Cms.Web.Models.Streamings; -public record ImportStreamingModel +public record ImportStreamingModel : IValidatableObject { + public string TwitchChannel { get; set; } = string.Empty; + + public string YouTubeChannel { get; set; } = string.Empty; + [Required] public string Title { get; set; } = string.Empty; @@ -11,20 +15,36 @@ public record ImportStreamingModel public string Slug { get; set; } = string.Empty; [Required] - public DateTime ScheduleDate { get; set; } = DateTime.Now; + public DateOnly ScheduleDate { get; set; } = DateOnly.FromDateTime(DateTime.Today); [Required] - public TimeSpan StartingTime { get; set; } + public TimeOnly StartingTime { get; set; } [Required] - public TimeSpan EndingTime { get; set; } + public TimeOnly EndingTime { get; set; } - [Required] - public string HostingChannelUrl { get; set; } = string.Empty; + public string TwitchUrl { get; set; } = string.Empty; - public string? StreamingAbstract { get; set; } + public string YouTubeUrl { get; set; } = string.Empty; - public string? YoutubeVideoUrl { get; set; } + public string? StreamingAbstract { get; set; } public SeoData Seo { get; set; } = new(); + + public IEnumerable Validate(ValidationContext validationContext) + { + if (string.IsNullOrWhiteSpace(TwitchChannel) && string.IsNullOrWhiteSpace(YouTubeChannel)) + { + yield return new("At least a channel must be selected", [nameof(TwitchChannel), nameof(YouTubeChannel)]); + } + + if (!string.IsNullOrWhiteSpace(TwitchChannel) && string.IsNullOrWhiteSpace(TwitchUrl)) + { + yield return new("A Twitch URL is required since a Twitch channel has been selected", [nameof(TwitchUrl)]); + } + if (!string.IsNullOrWhiteSpace(YouTubeChannel) && string.IsNullOrWhiteSpace(YouTubeUrl)) + { + yield return new("A YouTube URL is required since a YouTube channel has been selected", [nameof(YouTubeUrl)]); + } + } } diff --git a/src/KITT.Cms.Web.Models/Streamings/ScheduleStreamingModel.cs b/src/KITT.Cms.Web.Models/Streamings/ScheduleStreamingModel.cs index a393921..ceffbbc 100644 --- a/src/KITT.Cms.Web.Models/Streamings/ScheduleStreamingModel.cs +++ b/src/KITT.Cms.Web.Models/Streamings/ScheduleStreamingModel.cs @@ -2,8 +2,12 @@ namespace KITT.Cms.Web.Models.Streamings; -public record ScheduleStreamingModel +public record ScheduleStreamingModel : IValidatableObject { + public string TwitchChannel { get; set; } = string.Empty; + + public string YouTubeChannel { get; set; } = string.Empty; + [Required] public string Title { get; set; } = string.Empty; @@ -19,10 +23,19 @@ public record ScheduleStreamingModel [Required] public TimeOnly EndingTime { get; set; } - [Required] - public string HostingChannelUrl { get; set; } = string.Empty; + public string TwitchUrl { get; set; } = string.Empty; + + public string YouTubeUrl { get; set; } = string.Empty; public string? StreamingAbstract { get; set; } public SeoData Seo { get; set; } = new(); + + public IEnumerable Validate(ValidationContext validationContext) + { + if (string.IsNullOrWhiteSpace(TwitchChannel) && string.IsNullOrWhiteSpace(YouTubeChannel)) + { + yield return new("At least a channel must be selected", [nameof(TwitchChannel), nameof(YouTubeChannel)]); + } + } } diff --git a/src/KITT.Cms.Web.Models/Streamings/StreamingDetailModel.cs b/src/KITT.Cms.Web.Models/Streamings/StreamingDetailModel.cs index 2d4b2ea..48dcde3 100644 --- a/src/KITT.Cms.Web.Models/Streamings/StreamingDetailModel.cs +++ b/src/KITT.Cms.Web.Models/Streamings/StreamingDetailModel.cs @@ -6,6 +6,10 @@ public record StreamingDetailModel { public Guid Id { get; set; } + public string? TwitchChannel { get; set; } + + public string? YouTubeChannel { get; set; } + [Required] public string Title { get; set; } = string.Empty; @@ -20,12 +24,11 @@ public record StreamingDetailModel [Required] public TimeOnly EndingTime { get; set; } - [Required] - public string HostingChannelUrl { get; set; } = string.Empty; + public string TwitchUrl { get; set; } = string.Empty; public string? StreamingAbstract { get; set; } - public string? YoutubeVideoUrl { get; set; } + public string YouTubeUrl { get; set; } = string.Empty; public SeoData Seo { get; set; } = new(); } diff --git a/src/KITT.Core/Commands/IStreamingCommands.cs b/src/KITT.Core/Commands/IStreamingCommands.cs index b9ab979..b98ace9 100644 --- a/src/KITT.Core/Commands/IStreamingCommands.cs +++ b/src/KITT.Core/Commands/IStreamingCommands.cs @@ -2,11 +2,46 @@ public interface IStreamingCommands { - Task ScheduleStreamingAsync(string userId, string twitchChannel, string streamingTitle, string streamingSlug, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string hostingChannelUrl, string streamingAbstract, Content.SeoData seo); + Task ScheduleStreamingAsync( + string userId, + string twitchChannel, + string youTubeChannel, + string streamingTitle, + string streamingSlug, + DateOnly scheduleDate, + TimeOnly startingTime, + TimeOnly endingTime, + string twitchUrl, + string youTubeUrl, + string? streamingAbstract, + Content.SeoData seo); - Task UpdateStreamingAsync(Guid streamingId, string streamingTitle, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string hostingChannelUrl, string streamingAbstract, string youtubeRegistrationLink, Content.SeoData seo); + Task UpdateStreamingAsync( + Guid streamingId, + string? twitchChannel, + string? youTubeChannel, + string streamingTitle, + DateOnly scheduleDate, + TimeOnly startingTime, + TimeOnly endingTime, + string twitchUrl, + string? streamingAbstract, + string youtubeUrl, + Content.SeoData seo); Task DeleteStreamingAsync(Guid streamingId); - Task ImportStreamingAsync(string userId, string twitchChannel, string streamingTitle, string streamingSlug, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string hostingChannelUrl, string streamingAbstract, string youtubeRegistrationLink, Content.SeoData seo); + Task ImportStreamingAsync( + string userId, + string twitchChannel, + string youTubeChannel, + string streamingTitle, + string streamingSlug, + DateOnly scheduleDate, + TimeOnly startingTime, + TimeOnly endingTime, + string twitchUrl, + string? streamingAbstract, + string? youTubeUrl, + Content.SeoData seo); } diff --git a/src/KITT.Core/Commands/IStreamingStatsCommands.cs b/src/KITT.Core/Commands/IStreamingStatsCommands.cs deleted file mode 100644 index d25e355..0000000 --- a/src/KITT.Core/Commands/IStreamingStatsCommands.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace KITT.Core.Commands; - -public interface IStreamingStatsCommands -{ - Task RegisterStreamingStatsAsync( - Guid streamingId, - int viewers, - int subscribers, - int userJoinedNumber, - int userLeftNumber); -} diff --git a/src/KITT.Core/Commands/ProposalCommands.cs b/src/KITT.Core/Commands/ProposalCommands.cs index 11369d6..1e63679 100644 --- a/src/KITT.Core/Commands/ProposalCommands.cs +++ b/src/KITT.Core/Commands/ProposalCommands.cs @@ -100,23 +100,24 @@ public Task ScheduleProposalAsync(Guid proposalId, string userId, string twitchC private void ScheduleStreaming(string userId, string twitchChannel, string streamingTitle, string streamingSlug, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string streamingAbstract, string hostingChannelUrl) { - var streaming = Streaming.Schedule( - streamingTitle, - streamingSlug, - twitchChannel, - scheduleDate, - startingTime, - endingTime, - hostingChannelUrl, - userId); - - if (!string.IsNullOrWhiteSpace(streamingAbstract)) - { - streaming.SetAbstract(streamingAbstract); - } - - _validator.ValidateForScheduleStreaming(streaming); - - _context.Streamings.Add(streaming); + throw new NotImplementedException(); + //var streaming = Streaming.Schedule( + // streamingTitle, + // streamingSlug, + // twitchChannel, + // scheduleDate, + // startingTime, + // endingTime, + // hostingChannelUrl, + // userId); + + //if (!string.IsNullOrWhiteSpace(streamingAbstract)) + //{ + // streaming.SetAbstract(streamingAbstract); + //} + + //_validator.ValidateForScheduleStreaming(streaming); + + //_context.Streamings.Add(streaming); } } diff --git a/src/KITT.Core/Commands/StreamingCommands.cs b/src/KITT.Core/Commands/StreamingCommands.cs index 54123af..8de0d2f 100644 --- a/src/KITT.Core/Commands/StreamingCommands.cs +++ b/src/KITT.Core/Commands/StreamingCommands.cs @@ -1,6 +1,5 @@ using KITT.Core.Validators; using KITT.Telegram.Messages; -using KITT.Telegram.Messages.Streaming; namespace KITT.Core.Commands; @@ -10,25 +9,36 @@ public class StreamingCommands : IStreamingCommands private readonly StreamingValidator _validator; - private readonly IMessageBus _messageBus; - - public StreamingCommands(KittDbContext context, StreamingValidator validator, IMessageBus messageBus) + public StreamingCommands(KittDbContext context, StreamingValidator validator) { _context = context ?? throw new ArgumentNullException(nameof(context)); _validator = validator ?? throw new ArgumentNullException(nameof(validator)); - _messageBus = messageBus ?? throw new ArgumentNullException(nameof(messageBus)); } - public async Task ScheduleStreamingAsync(string userId, string twitchChannel, string streamingTitle, string streamingSlug, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string hostingChannelUrl, string streamingAbstract, Content.SeoData seo) + public async Task ScheduleStreamingAsync( + string userId, + string twitchChannel, + string youTubeChannel, + string streamingTitle, + string streamingSlug, + DateOnly scheduleDate, + TimeOnly startingTime, + TimeOnly endingTime, + string twitchUrl, + string youTubeUrl, + string? streamingAbstract, + Content.SeoData seo) { var streaming = Streaming.Schedule( streamingTitle, streamingSlug, twitchChannel, + youTubeChannel, scheduleDate, startingTime, endingTime, - hostingChannelUrl, + twitchUrl, + youTubeUrl, userId); if (!string.IsNullOrWhiteSpace(streamingAbstract)) @@ -46,80 +56,37 @@ public async Task ScheduleStreamingAsync(string userId, string twitchChann _context.Streamings.Add(streaming); await _context.SaveChangesAsync(); - //var message = new StreamingScheduledMessage( - // streaming.Id, - // streamingTitle, - // streamingSlug, - // scheduleDate, - // startingTime, - // endingTime, - // hostingChannelUrl); - - //await _messageBus.SendAsync(message); - return streaming.Id; } - public async Task UpdateStreamingAsync(Guid streamingId, string streamingTitle, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string hostingChannelUrl, string streamingAbstract, string youtubeRegistrationLink, Content.SeoData seo) + public async Task UpdateStreamingAsync( + Guid streamingId, + string? twitchChannel, + string? youTubeChannel, + string streamingTitle, + DateOnly scheduleDate, + TimeOnly startingTime, + TimeOnly endingTime, + string twitchUrl, + string? streamingAbstract, + string youtubeUrl, + Content.SeoData seo) { - var messagesToSend = new List(); - var streaming = _context.Streamings.SingleOrDefault(s => s.Id == streamingId); if (streaming is null) { throw new ArgumentOutOfRangeException(nameof(streamingId)); } - if (streaming.Title != streamingTitle) - { - streaming.ChangeTitle(streamingTitle); - } - - if (ScheduleHasChanged(streaming, scheduleDate, startingTime, endingTime)) - { - streaming.ChangeSchedule(scheduleDate, startingTime, endingTime); - //var scheduledChangedMessage = new StreamingScheduleChangedMessage( - // streamingId, - // streaming.Title, - // streaming.Slug, - // scheduleDate, - // startingTime, - // endingTime); - - //messagesToSend.Add(scheduledChangedMessage); - } - - if (streaming.HostingChannelUrl != hostingChannelUrl) - { - streaming.ChangeHostingChannelUrl(hostingChannelUrl); - - //var hostingChannelChangedMessage = new StreamingHostingChannelChangedMessage( - // streamingId, - // streaming.Title, - // streaming.Slug, - // hostingChannelUrl, - // scheduleDate, - // startingTime); - - //messagesToSend.Add(hostingChannelChangedMessage); - } + streaming.ChangeInformation( + twitchChannel, + youTubeChannel, + streamingTitle, + streamingAbstract, + twitchUrl, + youtubeUrl); - if (streaming.Abstract != streamingAbstract) - { - streaming.SetAbstract(streamingAbstract); - } - - if (streaming.YouTubeVideoUrl != youtubeRegistrationLink) - { - streaming.SetRegistrationYoutubeUrl(youtubeRegistrationLink); - //var videoUploadedMessage = new StreamingVideoUploadedMessage( - // streamingId, - // streaming.Title, - // streaming.Slug, - // youtubeRegistrationLink); - - //messagesToSend.Add(videoUploadedMessage); - } + streaming.ChangeSchedule(scheduleDate, startingTime, endingTime); if (seo is not null) { @@ -129,11 +96,6 @@ public async Task UpdateStreamingAsync(Guid streamingId, string streamingTitle, _validator.ValidateForUpdateStreaming(streaming); await _context.SaveChangesAsync(); - - //foreach (var message in messagesToSend) - //{ - // await _messageBus.SendAsync(message); - //} } public async Task DeleteStreamingAsync(Guid streamingId) @@ -146,28 +108,32 @@ public async Task DeleteStreamingAsync(Guid streamingId) _context.Streamings.Remove(streaming); await _context.SaveChangesAsync(); - - //var message = new StreamingCanceledMessage( - // streamingId, - // streaming.Title, - // streaming.ScheduleDate, - // streaming.StartingTime, - // streaming.EndingTime); - - //await _messageBus.SendAsync(message); } - public async Task ImportStreamingAsync(string userId, string twitchChannel, string streamingTitle, string streamingSlug, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string hostingChannelUrl, string streamingAbstract, string youtubeRegistrationLink, Content.SeoData seo) + public async Task ImportStreamingAsync( + string userId, + string twitchChannel, + string youTubeChannel, + string streamingTitle, + string streamingSlug, + DateOnly scheduleDate, + TimeOnly startingTime, + TimeOnly endingTime, + string twitchUrl, + string? streamingAbstract, + string? youTubeUrl, + Content.SeoData seo) { var streaming = Streaming.Import( streamingTitle, streamingSlug, twitchChannel, + youTubeChannel, scheduleDate, startingTime, endingTime, - hostingChannelUrl, - youtubeRegistrationLink, + twitchUrl, + youTubeUrl, streamingAbstract, userId); @@ -181,9 +147,4 @@ public async Task ImportStreamingAsync(string userId, string twitchChannel return streaming.Id; } - - #region Private methods - private bool ScheduleHasChanged(Streaming streaming, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime) - => streaming.ScheduleDate != scheduleDate || streaming.StartingTime != startingTime || streaming.EndingTime != endingTime; - #endregion } diff --git a/src/KITT.Core/Commands/StreamingStatsCommands.cs b/src/KITT.Core/Commands/StreamingStatsCommands.cs deleted file mode 100644 index b75676f..0000000 --- a/src/KITT.Core/Commands/StreamingStatsCommands.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace KITT.Core.Commands; - -public class StreamingStatsCommands : IStreamingStatsCommands -{ - private readonly KittDbContext _context; - - public StreamingStatsCommands(KittDbContext context) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - } - - public Task RegisterStreamingStatsAsync(Guid streamingId, int viewers, int subscribers, int userJoinedNumber, int userLeftNumber) - { - var streaming = _context.Streamings.SingleOrDefault(s => s.Id == streamingId); - if (streaming is null) - { - throw new ArgumentOutOfRangeException(nameof(streamingId)); - } - - var stats = StreamingStats.RegisterStats( - streaming, - viewers, - subscribers, - userJoinedNumber, - userLeftNumber); - - _context.Add(stats); - return _context.SaveChangesAsync(); - } -} diff --git a/src/KITT.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/KITT.Core/DependencyInjection/ServiceCollectionExtensions.cs index 58baeb1..e1619eb 100644 --- a/src/KITT.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/KITT.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -8,16 +8,19 @@ namespace KITT.Core.DependencyInjection; public static class ServiceCollectionExtensions { - public static IServiceCollection AddKittCore(this IServiceCollection services) + extension(IServiceCollection services) { - services - .AddValidatorsFromAssemblyContaining() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped(); + public IServiceCollection AddKittCore() + { + services + .AddValidatorsFromAssemblyContaining() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); - return services; + return services; + } } + } diff --git a/src/KITT.Core/KITT.Core.csproj b/src/KITT.Core/KITT.Core.csproj index 58bb36c..08ff8a9 100644 --- a/src/KITT.Core/KITT.Core.csproj +++ b/src/KITT.Core/KITT.Core.csproj @@ -1,18 +1,18 @@ - net9.0 + net10.0 enable + enable - - - - - - - + + + + + + diff --git a/src/KITT.Core/Migrations/20251110054558_Refactor-Streamings.Designer.cs b/src/KITT.Core/Migrations/20251110054558_Refactor-Streamings.Designer.cs new file mode 100644 index 0000000..9501274 --- /dev/null +++ b/src/KITT.Core/Migrations/20251110054558_Refactor-Streamings.Designer.cs @@ -0,0 +1,233 @@ +// +using System; +using KITT.Core.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KITT.Core.Migrations +{ + [DbContext(typeof(KittDbContext))] + [Migration("20251110054558_Refactor-Streamings")] + partial class RefactorStreamings + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("KITT.Core.Models.Content", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Abstract") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(36) + .HasColumnType("nvarchar(36)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("Title"); + + b.HasIndex("UserId"); + + b.ToTable("KITT_Contents", (string)null); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("KITT.Core.Models.Proposal", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AuthorNickname") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("Moderating"); + + b.Property("SubmittedAt") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("KITT_Proposals", (string)null); + }); + + modelBuilder.Entity("KITT.Core.Models.Rating", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("NumberOfDislikes") + .HasColumnType("int"); + + b.Property("NumberOfLikes") + .HasColumnType("int"); + + b.Property("PageUrl") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Website") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.HasKey("Id"); + + b.ToTable("KITT_Ratings", (string)null); + }); + + modelBuilder.Entity("KITT.Core.Models.Settings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("TwitchChannel") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(36) + .HasColumnType("nvarchar(36)"); + + b.HasKey("Id"); + + b.HasIndex("TwitchChannel"); + + b.HasIndex("UserId"); + + b.ToTable("KITT_Settings", (string)null); + }); + + modelBuilder.Entity("KITT.Core.Models.Streaming", b => + { + b.HasBaseType("KITT.Core.Models.Content"); + + b.Property("EndingTime") + .HasColumnType("time"); + + b.Property("ScheduleDate") + .HasColumnType("date"); + + b.Property("StartingTime") + .HasColumnType("time"); + + b.Property("TwitchChannel") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TwitchUrl") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("YouTubeChannel") + .HasColumnType("nvarchar(max)"); + + b.Property("YouTubeUrl") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.HasIndex("TwitchChannel"); + + b.ToTable("KITT_Streamings", (string)null); + }); + + modelBuilder.Entity("KITT.Core.Models.Content", b => + { + b.OwnsOne("KITT.Core.Models.Content+SeoData", "Seo", b1 => + { + b1.Property("ContentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Description") + .HasColumnType("nvarchar(max)"); + + b1.Property("Keywords") + .HasColumnType("nvarchar(max)"); + + b1.Property("Title") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("ContentId"); + + b1.ToTable("KITT_Contents"); + + b1.WithOwner() + .HasForeignKey("ContentId"); + }); + + b.Navigation("Seo") + .IsRequired(); + }); + + modelBuilder.Entity("KITT.Core.Models.Streaming", b => + { + b.HasOne("KITT.Core.Models.Content", null) + .WithOne() + .HasForeignKey("KITT.Core.Models.Streaming", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/KITT.Core/Migrations/20251110054558_Refactor-Streamings.cs b/src/KITT.Core/Migrations/20251110054558_Refactor-Streamings.cs new file mode 100644 index 0000000..c2fc792 --- /dev/null +++ b/src/KITT.Core/Migrations/20251110054558_Refactor-Streamings.cs @@ -0,0 +1,120 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KITT.Core.Migrations +{ + /// + public partial class RefactorStreamings : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "KITT_Expenses"); + + migrationBuilder.DropTable( + name: "KITT_StreamingStats"); + + migrationBuilder.RenameColumn( + name: "YouTubeVideoUrl", + table: "KITT_Streamings", + newName: "YouTubeUrl"); + + migrationBuilder.RenameColumn( + name: "HostingChannelUrl", + table: "KITT_Streamings", + newName: "TwitchUrl"); + + migrationBuilder.AddColumn( + name: "YouTubeChannel", + table: "KITT_Streamings", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AlterColumn( + name: "AuthorNickname", + table: "KITT_Proposals", + type: "nvarchar(50)", + maxLength: 50, + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "nvarchar(50)", + oldMaxLength: 50, + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "YouTubeChannel", + table: "KITT_Streamings"); + + migrationBuilder.RenameColumn( + name: "YouTubeUrl", + table: "KITT_Streamings", + newName: "YouTubeVideoUrl"); + + migrationBuilder.RenameColumn( + name: "TwitchUrl", + table: "KITT_Streamings", + newName: "HostingChannelUrl"); + + migrationBuilder.AlterColumn( + name: "AuthorNickname", + table: "KITT_Proposals", + type: "nvarchar(50)", + maxLength: 50, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(50)", + oldMaxLength: 50); + + migrationBuilder.CreateTable( + name: "KITT_Expenses", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Description = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true), + ExpenseDate = table.Column(type: "datetime2", nullable: false), + Method = table.Column(type: "nvarchar(max)", nullable: false), + PaymentInfo = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Title = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + TotalAmount = table.Column(type: "decimal(10,2)", precision: 10, scale: 2, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_KITT_Expenses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "KITT_StreamingStats", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + StreamingId = table.Column(type: "uniqueidentifier", nullable: true), + Subscribers = table.Column(type: "int", nullable: false), + UserJoinedNumber = table.Column(type: "int", nullable: false), + UserLeftNumber = table.Column(type: "int", nullable: false), + Viewers = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_KITT_StreamingStats", x => x.Id); + table.ForeignKey( + name: "FK_KITT_StreamingStats_KITT_Streamings_StreamingId", + column: x => x.StreamingId, + principalTable: "KITT_Streamings", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_KITT_StreamingStats_StreamingId", + table: "KITT_StreamingStats", + column: "StreamingId"); + } + } +} diff --git a/src/KITT.Core/Migrations/KittDbContextModelSnapshot.cs b/src/KITT.Core/Migrations/KittDbContextModelSnapshot.cs index 33aa1ee..ecf1c44 100644 --- a/src/KITT.Core/Migrations/KittDbContextModelSnapshot.cs +++ b/src/KITT.Core/Migrations/KittDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("ProductVersion", "9.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -69,46 +69,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.UseTptMappingStrategy(); }); - modelBuilder.Entity("KITT.Core.Models.Expense", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("Description") - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("ExpenseDate") - .HasColumnType("datetime2"); - - b.Property("Method") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("PaymentInfo") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("TotalAmount") - .HasPrecision(10, 2) - .HasColumnType("decimal(10,2)"); - - b.HasKey("Id"); - - b.ToTable("KITT_Expenses", (string)null); - }); - modelBuilder.Entity("KITT.Core.Models.Proposal", b => { b.Property("Id") .HasColumnType("uniqueidentifier"); b.Property("AuthorNickname") + .IsRequired() .HasMaxLength(50) .HasColumnType("nvarchar(50)"); @@ -186,33 +153,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("KITT_Settings", (string)null); }); - modelBuilder.Entity("KITT.Core.Models.StreamingStats", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("StreamingId") - .HasColumnType("uniqueidentifier"); - - b.Property("Subscribers") - .HasColumnType("int"); - - b.Property("UserJoinedNumber") - .HasColumnType("int"); - - b.Property("UserLeftNumber") - .HasColumnType("int"); - - b.Property("Viewers") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("StreamingId"); - - b.ToTable("KITT_StreamingStats", (string)null); - }); - modelBuilder.Entity("KITT.Core.Models.Streaming", b => { b.HasBaseType("KITT.Core.Models.Content"); @@ -220,11 +160,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("EndingTime") .HasColumnType("time"); - b.Property("HostingChannelUrl") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - b.Property("ScheduleDate") .HasColumnType("date"); @@ -236,7 +171,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("YouTubeVideoUrl") + b.Property("TwitchUrl") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("YouTubeChannel") + .HasColumnType("nvarchar(max)"); + + b.Property("YouTubeUrl") .HasMaxLength(255) .HasColumnType("nvarchar(255)"); @@ -269,16 +212,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("ContentId"); }); - b.Navigation("Seo"); - }); - - modelBuilder.Entity("KITT.Core.Models.StreamingStats", b => - { - b.HasOne("KITT.Core.Models.Streaming", "Streaming") - .WithMany() - .HasForeignKey("StreamingId"); - - b.Navigation("Streaming"); + b.Navigation("Seo") + .IsRequired(); }); modelBuilder.Entity("KITT.Core.Models.Streaming", b => diff --git a/src/KITT.Core/Models/Content.cs b/src/KITT.Core/Models/Content.cs index 56e40b1..5224e56 100644 --- a/src/KITT.Core/Models/Content.cs +++ b/src/KITT.Core/Models/Content.cs @@ -5,13 +5,13 @@ public abstract class Content #region Properties public Guid Id { get; protected set; } - public string UserId { get; protected set; } + public string UserId { get; protected set; } = string.Empty; - public string Title { get; protected set; } + public string Title { get; protected set; } = string.Empty; - public string Slug { get; protected set; } + public string Slug { get; protected set; } = string.Empty; - public string Abstract { get; protected set; } + public string? Abstract { get; protected set; } public virtual SeoData Seo { get; protected set; } @@ -41,7 +41,7 @@ public virtual void ChangeTitle(string title) Title = title; } - public virtual void SetAbstract(string @abstract) + public virtual void SetAbstract(string? @abstract) { Abstract = @abstract; } @@ -64,11 +64,11 @@ public virtual void PublishOn(DateTime publicationDate) #region Inner classes public record SeoData { - public string Title { get; init; } + public string? Title { get; init; } - public string Description { get; init; } + public string? Description { get; init; } - public string Keywords { get; init; } + public string? Keywords { get; init; } } public enum ContentStatus diff --git a/src/KITT.Core/Models/Expense.cs b/src/KITT.Core/Models/Expense.cs deleted file mode 100644 index 2576820..0000000 --- a/src/KITT.Core/Models/Expense.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace KITT.Core.Models; - -public class Expense -{ - public Guid Id { get; protected set; } - - public string Title { get; protected set; } - - public DateTime ExpenseDate { get; protected set; } - - public decimal TotalAmount { get; protected set; } - - public string Description { get; protected set; } - - public PaymentMethod Method { get; protected set; } - - public string PaymentInfo { get; protected set; } - - public enum PaymentMethod - { - Cash, - Bancomat, - CreditCard, - BankAccount - } -} diff --git a/src/KITT.Core/Models/Rating.cs b/src/KITT.Core/Models/Rating.cs index e322ef4..6e61a5f 100644 --- a/src/KITT.Core/Models/Rating.cs +++ b/src/KITT.Core/Models/Rating.cs @@ -4,9 +4,9 @@ public class Rating { public Guid Id { get; protected set; } - public string Website { get; protected set; } + public string Website { get; protected set; } = string.Empty; - public string PageUrl { get; protected set; } + public string PageUrl { get; protected set; } = string.Empty; public int NumberOfLikes { get; protected set; } diff --git a/src/KITT.Core/Models/Settings.cs b/src/KITT.Core/Models/Settings.cs index 89634ec..d5cfe0d 100644 --- a/src/KITT.Core/Models/Settings.cs +++ b/src/KITT.Core/Models/Settings.cs @@ -4,9 +4,9 @@ public class Settings { public Guid Id { get; protected set; } - public string UserId { get; protected set; } + public string UserId { get; protected set; } = string.Empty; - public string TwitchChannel { get; protected set; } + public string TwitchChannel { get; protected set; } = string.Empty; #region Public methods public void Update(string twitchChannel) diff --git a/src/KITT.Core/Models/Streaming.cs b/src/KITT.Core/Models/Streaming.cs index 9cfc222..16dda70 100644 --- a/src/KITT.Core/Models/Streaming.cs +++ b/src/KITT.Core/Models/Streaming.cs @@ -2,7 +2,9 @@ public class Streaming : Content { - public string TwitchChannel { get; protected set; } + public string? TwitchChannel { get; protected set; } + + public string? YouTubeChannel { get; protected set; } public DateOnly ScheduleDate { get; protected set; } @@ -10,15 +12,31 @@ public class Streaming : Content public TimeOnly EndingTime { get; protected set; } - public string HostingChannelUrl { get; protected set; } + public string? TwitchUrl { get; protected set; } - public string YouTubeVideoUrl { get; protected set; } + public string? YouTubeUrl { get; protected set; } #region Constructor protected Streaming() : base() { } #endregion #region Behaviors + public void ChangeInformation( + string? twitchChannel, + string? youTubeChannel, + string title, + string? @abstract, + string? twitchUrl, + string? youTubeUrl) + { + TwitchChannel = twitchChannel; + YouTubeChannel = youTubeChannel; + Title = title; + Abstract = @abstract; + TwitchUrl = twitchUrl; + YouTubeUrl = youTubeUrl; + } + public void ChangeSchedule(DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime) { if (startingTime >= endingTime) @@ -26,24 +44,32 @@ public void ChangeSchedule(DateOnly scheduleDate, TimeOnly startingTime, TimeOnl throw new ArgumentException("Ending time cannot be previous than starting time", nameof(endingTime)); } + if (!ScheduleHasChanged(scheduleDate, startingTime, endingTime)) + { + return; + } + this.ScheduleDate = scheduleDate; this.StartingTime = startingTime; this.EndingTime = endingTime; } - public void SetRegistrationYoutubeUrl(string youtubeUrl) + public bool ScheduleHasChanged(DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime) + => ScheduleDate != scheduleDate || StartingTime != startingTime || EndingTime != endingTime; + + public void SetYouTubeUrl(string youtubeUrl) { - this.YouTubeVideoUrl = youtubeUrl; + YouTubeUrl = youtubeUrl; } - public void ChangeHostingChannelUrl(string hostingChannelUrl) + public void SetTwitchUrl(string twitchUrl) { - this.HostingChannelUrl = hostingChannelUrl; + TwitchUrl = twitchUrl; } #endregion #region Factory - public static Streaming Schedule(string title, string slug, string twitchChannel, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string hostingChannelUrl, string userId) + public static Streaming Schedule(string title, string slug, string twitchChannel, string youTubeChannel, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string twitchUrl, string youTubeUrl, string userId) { if (string.IsNullOrWhiteSpace(title)) { @@ -55,11 +81,6 @@ public static Streaming Schedule(string title, string slug, string twitchChannel throw new ArgumentException("value cannot be empty", nameof(slug)); } - if (string.IsNullOrWhiteSpace(twitchChannel)) - { - throw new ArgumentException("value cannot be empty", nameof(twitchChannel)); - } - if (scheduleDate < DateOnly.FromDateTime(DateTime.Today)) { throw new ArgumentException("Schedule date cannot be set in the past", nameof(scheduleDate)); @@ -70,11 +91,6 @@ public static Streaming Schedule(string title, string slug, string twitchChannel throw new ArgumentException("Starting time should be previuos than ending time", nameof(endingTime)); } - if (string.IsNullOrWhiteSpace(hostingChannelUrl)) - { - throw new ArgumentException("value cannot be empty", nameof(hostingChannelUrl)); - } - if (string.IsNullOrWhiteSpace(userId)) { throw new ArgumentException("value cannot be empty", nameof(userId)); @@ -87,10 +103,12 @@ public static Streaming Schedule(string title, string slug, string twitchChannel Title = title, Slug = slug, TwitchChannel = twitchChannel, + YouTubeChannel = youTubeChannel, ScheduleDate = scheduleDate, StartingTime = startingTime, EndingTime = endingTime, - HostingChannelUrl = hostingChannelUrl + TwitchUrl = twitchUrl, + YouTubeUrl = youTubeUrl, }; streaming.Publish(); @@ -98,7 +116,7 @@ public static Streaming Schedule(string title, string slug, string twitchChannel return streaming; } - public static Streaming Import(string title, string slug, string twitchChannel, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string hostingChannelUrl, string youtubeVideoUrl, string @abstract, string userId) + public static Streaming Import(string title, string slug, string twitchChannel, string youTubeChannel, DateOnly scheduleDate, TimeOnly startingTime, TimeOnly endingTime, string twitchUrl, string? youTubeUrl, string? @abstract, string userId) { if (string.IsNullOrWhiteSpace(title)) { @@ -110,19 +128,18 @@ public static Streaming Import(string title, string slug, string twitchChannel, throw new ArgumentException("value cannot be empty", nameof(slug)); } - if (string.IsNullOrWhiteSpace(twitchChannel)) - { - throw new ArgumentException("value cannot be empty", nameof(twitchChannel)); - } - if (startingTime >= endingTime) { throw new ArgumentException("Starting time should be previuos than ending time", nameof(endingTime)); } - if (string.IsNullOrWhiteSpace(hostingChannelUrl)) + if (!string.IsNullOrWhiteSpace(twitchChannel) && string.IsNullOrWhiteSpace(twitchUrl)) + { + throw new ArgumentException("value cannot be empty", nameof(twitchUrl)); + } + if (!string.IsNullOrWhiteSpace(youTubeChannel) && string.IsNullOrWhiteSpace(youTubeUrl)) { - throw new ArgumentException("value cannot be empty", nameof(hostingChannelUrl)); + throw new ArgumentException("value cannot be empty", nameof(youTubeChannel)); } if (string.IsNullOrWhiteSpace(userId)) @@ -137,12 +154,13 @@ public static Streaming Import(string title, string slug, string twitchChannel, Title = title, Slug = slug, TwitchChannel = twitchChannel, + YouTubeChannel = youTubeChannel, ScheduleDate = scheduleDate, StartingTime = startingTime, EndingTime = endingTime, - HostingChannelUrl = hostingChannelUrl, + TwitchUrl = twitchUrl, Abstract = @abstract, - YouTubeVideoUrl = youtubeVideoUrl + YouTubeUrl = youTubeUrl }; streaming.PublishOn(scheduleDate.ToDateTime(TimeOnly.MinValue)); diff --git a/src/KITT.Core/Models/StreamingStats.cs b/src/KITT.Core/Models/StreamingStats.cs deleted file mode 100644 index a0ebf04..0000000 --- a/src/KITT.Core/Models/StreamingStats.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace KITT.Core.Models; - -public class StreamingStats -{ - public Guid Id { get; protected set; } - - public virtual Streaming Streaming { get; protected set; } - - public int Viewers { get; protected set; } - - public int Subscribers { get; protected set; } - - public int UserJoinedNumber { get; protected set; } - - public int UserLeftNumber { get; protected set; } - - public static StreamingStats RegisterStats(Streaming streaming, int viewers, int subscribers, int userJoinedNumber, int userLeftNumber) - { - ArgumentNullException.ThrowIfNull(streaming); - - return new StreamingStats - { - Id = Guid.NewGuid(), - Streaming = streaming, - Viewers = viewers, - Subscribers = subscribers, - UserJoinedNumber = userJoinedNumber, - UserLeftNumber = userLeftNumber - }; - } -} diff --git a/src/KITT.Core/Persistence/KittDbContext.cs b/src/KITT.Core/Persistence/KittDbContext.cs index 59d48b6..45507d1 100644 --- a/src/KITT.Core/Persistence/KittDbContext.cs +++ b/src/KITT.Core/Persistence/KittDbContext.cs @@ -15,10 +15,6 @@ public KittDbContext(DbContextOptions options) public DbSet Proposals { get; set; } - public DbSet Expenses { get; set; } - - public DbSet StreamingStats { get; set; } - public DbSet Ratings { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/src/KITT.Core/Persistence/Mapping/ExpenseMapper.cs b/src/KITT.Core/Persistence/Mapping/ExpenseMapper.cs deleted file mode 100644 index 776c31a..0000000 --- a/src/KITT.Core/Persistence/Mapping/ExpenseMapper.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace KITT.Core.Persistence.Mapping; - -internal class ExpenseMapper : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable($"{Defaults.TablePrefix}_Expenses"); - - builder.HasKey(e => e.Id); - builder.Property(e => e.Id).ValueGeneratedNever(); - - builder.Property(e => e.Title) - .HasMaxLength(100) - .IsRequired(); - - builder.Property(e => e.ExpenseDate).IsRequired(); - - builder.Property(e => e.TotalAmount) - .HasPrecision(10, 2) - .IsRequired(); - - builder.Property(e => e.Description).HasMaxLength(255); - builder.Property(e => e.PaymentInfo).HasMaxLength(100); - - builder.Property(e => e.Method).HasConversion(); - } -} diff --git a/src/KITT.Core/Persistence/Mapping/StreamingMapper.cs b/src/KITT.Core/Persistence/Mapping/StreamingMapper.cs index 56f9490..cc4994c 100644 --- a/src/KITT.Core/Persistence/Mapping/StreamingMapper.cs +++ b/src/KITT.Core/Persistence/Mapping/StreamingMapper.cs @@ -11,7 +11,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(s => s.TwitchChannel).HasMaxLength(50).IsRequired(); builder.HasIndex(s => s.TwitchChannel); - builder.Property(s => s.HostingChannelUrl).HasMaxLength(255).IsRequired(); - builder.Property(s => s.YouTubeVideoUrl).HasMaxLength(255); + builder.Property(s => s.TwitchUrl).HasMaxLength(255).IsRequired(); + builder.Property(s => s.YouTubeUrl).HasMaxLength(255); } } diff --git a/src/KITT.Core/Persistence/Mapping/StreamingStatsMapper.cs b/src/KITT.Core/Persistence/Mapping/StreamingStatsMapper.cs deleted file mode 100644 index bd17b85..0000000 --- a/src/KITT.Core/Persistence/Mapping/StreamingStatsMapper.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace KITT.Core.Persistence.Mapping; - -internal class StreamingStatsMapper : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable($"{Defaults.TablePrefix}_StreamingStats"); - - builder.HasKey(e => e.Id); - builder.Property(e => e.Id).ValueGeneratedNever(); - - builder.HasOne(s => s.Streaming).WithMany(); - } -} diff --git a/src/KITT.Core/ReadModels/Database.cs b/src/KITT.Core/ReadModels/Database.cs index 8162126..38f18e2 100644 --- a/src/KITT.Core/ReadModels/Database.cs +++ b/src/KITT.Core/ReadModels/Database.cs @@ -14,8 +14,4 @@ public Database(KittDbContext context) public IQueryable Settings => _context.Settings.AsNoTracking(); public IQueryable Proposals => _context.Proposals.AsNoTracking(); - - public IQueryable Expenses => _context.Expenses.AsNoTracking(); - - public IQueryable StreamingStats => _context.StreamingStats.AsNoTracking(); } diff --git a/src/KITT.Core/ReadModels/Extensions/ContentExtensions.cs b/src/KITT.Core/ReadModels/Extensions/ContentExtensions.cs index c1d1d04..9692e26 100644 --- a/src/KITT.Core/ReadModels/Extensions/ContentExtensions.cs +++ b/src/KITT.Core/ReadModels/Extensions/ContentExtensions.cs @@ -2,18 +2,18 @@ public static class ContentExtensions { - public static IQueryable PublishedOnly(this IQueryable source) where TContent : Content - => source.Where(c => c.Status == Content.ContentStatus.Published && c.PublicationDate <= DateTime.UtcNow); + extension(IQueryable source) where TContent : Content + { + public IQueryable PublishedOnly() + => source.Where(c => c.Status == Content.ContentStatus.Published && c.PublicationDate <= DateTime.UtcNow); - public static IQueryable DraftsOnly(this IQueryable source) where TContent : Content - => source.Where(c => c.Status == Content.ContentStatus.Draft); + public IQueryable DraftsOnly() => source.Where(c => c.Status == Content.ContentStatus.Draft); - public static IQueryable UnpublishedOnly(this IQueryable source) where TContent : Content - => source.Where(c => c.Status == Content.ContentStatus.Unpublished); + public IQueryable UnpublishedOnly() => source.Where(c => c.Status == Content.ContentStatus.Unpublished); - public static IQueryable OrderedByPublicationDate(this IQueryable source) where TContent : Content - => source.OrderBy(c => c.PublicationDate); + public IQueryable OrderedByPublicationDate() => source.OrderBy(c => c.PublicationDate); - public static IQueryable WithSlug(this IQueryable source, string slug) where TContent : Content - => source.Where(c => c.Slug == slug); + public IQueryable WithSlug(string slug) => source.Where(c => c.Slug == slug); + } + } diff --git a/src/KITT.Core/ReadModels/Extensions/ProposalExtensions.cs b/src/KITT.Core/ReadModels/Extensions/ProposalExtensions.cs index 49ff4ae..4628a47 100644 --- a/src/KITT.Core/ReadModels/Extensions/ProposalExtensions.cs +++ b/src/KITT.Core/ReadModels/Extensions/ProposalExtensions.cs @@ -2,18 +2,20 @@ public static class ProposalExtensions { - public static IQueryable Moderating(this IQueryable proposals) - => proposals.Where(p => p.Status == Proposal.ProposalStatus.Moderating); + extension(IQueryable proposals) + { + public IQueryable Moderating() => proposals.Where(p => p.Status == Proposal.ProposalStatus.Moderating); - public static IQueryable WaitingForApproval(this IQueryable proposals) - => proposals.Where(p => p.Status == Proposal.ProposalStatus.WaitingForApproval); + public IQueryable WaitingForApproval() => proposals.Where(p => p.Status == Proposal.ProposalStatus.WaitingForApproval); - public static IQueryable OrderedBySubmissionDate(this IQueryable proposals, bool ascending = true) - { - return ascending switch + public IQueryable OrderedBySubmissionDate(bool ascending = true) { - false => proposals.OrderByDescending(p => p.SubmittedAt), - _ => proposals.OrderBy(p => p.SubmittedAt) - }; + return ascending switch + { + false => proposals.OrderByDescending(p => p.SubmittedAt), + _ => proposals.OrderBy(p => p.SubmittedAt) + }; + } } + } diff --git a/src/KITT.Core/ReadModels/Extensions/SettingsExtensions.cs b/src/KITT.Core/ReadModels/Extensions/SettingsExtensions.cs index 4220a15..1537a74 100644 --- a/src/KITT.Core/ReadModels/Extensions/SettingsExtensions.cs +++ b/src/KITT.Core/ReadModels/Extensions/SettingsExtensions.cs @@ -2,6 +2,8 @@ public static class SettingsExtensions { - public static IQueryable ByUserId(this IQueryable settings, string userId) - => settings.Where(s => s.UserId == userId); + extension(IQueryable settings) + { + public IQueryable ByUserId(string userId) => settings.Where(s => s.UserId == userId); + } } diff --git a/src/KITT.Core/ReadModels/Extensions/StreamingStatsExtensions.cs b/src/KITT.Core/ReadModels/Extensions/StreamingStatsExtensions.cs deleted file mode 100644 index 3c7543f..0000000 --- a/src/KITT.Core/ReadModels/Extensions/StreamingStatsExtensions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace KITT.Core.ReadModels; - -public static class StreamingStatsExtensions -{ - public static IQueryable ByStreaming(this IQueryable stats, Guid streamingId) - => stats.Include(s => s.Streaming).Where(s => s.Streaming.Id == streamingId); -} diff --git a/src/KITT.Core/ReadModels/Extensions/StreamingsExtensions.cs b/src/KITT.Core/ReadModels/Extensions/StreamingsExtensions.cs index 59d0e7f..71fd55d 100644 --- a/src/KITT.Core/ReadModels/Extensions/StreamingsExtensions.cs +++ b/src/KITT.Core/ReadModels/Extensions/StreamingsExtensions.cs @@ -2,22 +2,23 @@ public static class StreamingsExtensions { - public static IQueryable ByUserId(this IQueryable streamings, string userId) - => streamings.Where(s => s.UserId == userId); - - public static IQueryable OrderedBySchedule(this IQueryable streamings, bool ascending = true) + extension(IQueryable streamings) { - return ascending ? - OrderedByScheduleAscending(streamings) : - OrderedByScheduleDescending(streamings); - } + public IQueryable ByUserId(string userId) => streamings.Where(s => s.UserId == userId); - public static IQueryable DeliveredOnly(this IQueryable streamings) - => streamings.Where(s => s.ScheduleDate < DateOnly.FromDateTime(DateTime.Today)); + public IQueryable OrderedBySchedule(bool ascending = true) + { + return ascending ? + OrderedByScheduleAscending(streamings) : + OrderedByScheduleDescending(streamings); + } - public static IQueryable Scheduled(this IQueryable streamings) - { - return streamings.Where(s => s.ScheduleDate >= DateOnly.FromDateTime(DateTime.Today)); + public IQueryable DeliveredOnly() => streamings.Where(s => s.ScheduleDate < DateOnly.FromDateTime(DateTime.Today)); + + public IQueryable Scheduled() + { + return streamings.Where(s => s.ScheduleDate >= DateOnly.FromDateTime(DateTime.Today)); + } } private static IQueryable OrderedByScheduleAscending(IQueryable streamings) diff --git a/src/KITT.Core/ReadModels/IDatabase.cs b/src/KITT.Core/ReadModels/IDatabase.cs index df5a85b..1d10f95 100644 --- a/src/KITT.Core/ReadModels/IDatabase.cs +++ b/src/KITT.Core/ReadModels/IDatabase.cs @@ -7,8 +7,4 @@ public interface IDatabase IQueryable Settings { get; } IQueryable Proposals { get; } - - IQueryable Expenses { get; } - - IQueryable StreamingStats { get; } } diff --git a/src/KITT.Core/Validators/StreamingValidator.cs b/src/KITT.Core/Validators/StreamingValidator.cs index cd2625b..1edb77a 100644 --- a/src/KITT.Core/Validators/StreamingValidator.cs +++ b/src/KITT.Core/Validators/StreamingValidator.cs @@ -27,11 +27,8 @@ public void ValidateForUpdateStreaming(Streaming streaming) => this.Validate(str private void DefineRules() { - RuleFor(streaming => streaming.TwitchChannel).MaximumLength(50).NotEmpty(); RuleFor(streaming => streaming.Title).MaximumLength(255).NotEmpty(); RuleFor(streaming => streaming.Slug).MaximumLength(255).NotEmpty(); - RuleFor(streaming => streaming.HostingChannelUrl).NotEmpty().MaximumLength(255); - RuleFor(streaming => streaming.YouTubeVideoUrl).MaximumLength(255); RuleSet(ScheduleStreamingAction, () => { diff --git a/src/KITT.Proposals.Web.Api/Endpoints/ProposalsEndpoints.cs b/src/KITT.Proposals.Web.Api/Endpoints/ProposalsEndpoints.cs index 2371b62..3ada263 100644 --- a/src/KITT.Proposals.Web.Api/Endpoints/ProposalsEndpoints.cs +++ b/src/KITT.Proposals.Web.Api/Endpoints/ProposalsEndpoints.cs @@ -9,49 +9,44 @@ namespace KITT.Proposals.Web.Api.Endpoints; public static class ProposalsEndpoints { - public static IEndpointRouteBuilder MapProposalsEndpoints(this IEndpointRouteBuilder builder) + extension(IEndpointRouteBuilder builder) { - var proposalsGroup = builder - .MapGroup("api/proposals") - .RequireAuthorization() - .WithParameterValidation(); - - proposalsGroup - .MapGet("", GetAllProposals) - .WithName(nameof(GetAllProposals)) - .WithOpenApi(); - - proposalsGroup - .MapGet("{id:guid}", GetProposalDetail) - .WithName(nameof(GetProposalDetail)) - .WithOpenApi(); - - proposalsGroup - .MapPatch("{id:guid}", AcceptProposal) - .WithName(nameof(AcceptProposal)) - .WithOpenApi(); - - proposalsGroup - .MapDelete("{id:guid}", RejectProposal) - .WithName(nameof(RejectProposal)) - .WithOpenApi(); - - proposalsGroup - .MapDelete("{id:guid}/refuse", RefuseProposal) - .WithName(nameof(RefuseProposal)) - .WithOpenApi(); - - proposalsGroup - .MapPost("{id:guid}/schedule", ScheduleProposal) - .WithName(nameof(ScheduleProposal)) - .WithOpenApi(); - - proposalsGroup - .MapGet("stats", GetProposalsStats) - .WithName(nameof(GetProposalsStats)) - .WithOpenApi(); - - return builder; + public IEndpointRouteBuilder MapProposalsEndpoints() + { + var proposalsGroup = builder + .MapGroup("api/proposals") + .RequireAuthorization(); + + proposalsGroup + .MapGet("", GetAllProposals) + .WithName(nameof(GetAllProposals)); + + proposalsGroup + .MapGet("{id:guid}", GetProposalDetail) + .WithName(nameof(GetProposalDetail)); + + proposalsGroup + .MapPatch("{id:guid}", AcceptProposal) + .WithName(nameof(AcceptProposal)); + + proposalsGroup + .MapDelete("{id:guid}", RejectProposal) + .WithName(nameof(RejectProposal)); + + proposalsGroup + .MapDelete("{id:guid}/refuse", RefuseProposal) + .WithName(nameof(RefuseProposal)); + + proposalsGroup + .MapPost("{id:guid}/schedule", ScheduleProposal) + .WithName(nameof(ScheduleProposal)); + + proposalsGroup + .MapGet("stats", GetProposalsStats) + .WithName(nameof(GetProposalsStats)); + + return builder; + } } private static async Task> GetAllProposals( diff --git a/src/KITT.Proposals.Web.Api/KITT.Proposals.Web.Api.csproj b/src/KITT.Proposals.Web.Api/KITT.Proposals.Web.Api.csproj index 50c6991..0877781 100644 --- a/src/KITT.Proposals.Web.Api/KITT.Proposals.Web.Api.csproj +++ b/src/KITT.Proposals.Web.Api/KITT.Proposals.Web.Api.csproj @@ -1,15 +1,14 @@  - net9.0 + net10.0 enable enable - - - + + diff --git a/src/KITT.Proposals.Web.Api/Program.cs b/src/KITT.Proposals.Web.Api/Program.cs index a971462..8ea2370 100644 --- a/src/KITT.Proposals.Web.Api/Program.cs +++ b/src/KITT.Proposals.Web.Api/Program.cs @@ -12,6 +12,8 @@ builder.AddServiceDefaults(); builder.AddSqlServerDbContext(ServiceNames.Database); +builder.Services.AddValidation(); + builder.Services.AddAuthentication() .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { diff --git a/src/KITT.Proposals.Web.App/Clients/ClientsServiceCollectionExtensions.cs b/src/KITT.Proposals.Web.App/Clients/ClientsServiceCollectionExtensions.cs index bed3238..78d1e47 100644 --- a/src/KITT.Proposals.Web.App/Clients/ClientsServiceCollectionExtensions.cs +++ b/src/KITT.Proposals.Web.App/Clients/ClientsServiceCollectionExtensions.cs @@ -5,13 +5,14 @@ namespace KITT.Proposals.Web.App.Clients; public static class ClientsServiceCollectionExtensions { - public static IServiceCollection AddProposalsClients( - this IServiceCollection services, - string apiBaseUrl) + extension(IServiceCollection services) { - services - .AddHttpClient(client => client.BaseAddress = new(apiBaseUrl)); + public IServiceCollection AddProposalsClients(string apiBaseUrl) + { + services + .AddHttpClient(client => client.BaseAddress = new(apiBaseUrl)); - return services; + return services; + } } } diff --git a/src/KITT.Proposals.Web.App/KITT.Proposals.Web.App.csproj b/src/KITT.Proposals.Web.App/KITT.Proposals.Web.App.csproj index 1135d10..fb0839b 100644 --- a/src/KITT.Proposals.Web.App/KITT.Proposals.Web.App.csproj +++ b/src/KITT.Proposals.Web.App/KITT.Proposals.Web.App.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable @@ -12,7 +12,7 @@ - + diff --git a/src/KITT.Proposals.Web.Models/KITT.Proposals.Web.Models.csproj b/src/KITT.Proposals.Web.Models/KITT.Proposals.Web.Models.csproj index 125f4c9..b760144 100644 --- a/src/KITT.Proposals.Web.Models/KITT.Proposals.Web.Models.csproj +++ b/src/KITT.Proposals.Web.Models/KITT.Proposals.Web.Models.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable diff --git a/src/KITT.ServiceDefaults/KITT.ServiceDefaults.csproj b/src/KITT.ServiceDefaults/KITT.ServiceDefaults.csproj index 4fc2edb..884b8b9 100644 --- a/src/KITT.ServiceDefaults/KITT.ServiceDefaults.csproj +++ b/src/KITT.ServiceDefaults/KITT.ServiceDefaults.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable true @@ -10,13 +10,13 @@ - - - - - - - + + + + + + + diff --git a/src/KITT.Services/KITT.Services.csproj b/src/KITT.Services/KITT.Services.csproj index 125f4c9..b760144 100644 --- a/src/KITT.Services/KITT.Services.csproj +++ b/src/KITT.Services/KITT.Services.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable diff --git a/src/KITT.Services/ServiceNames.cs b/src/KITT.Services/ServiceNames.cs index d83058b..6a606e1 100644 --- a/src/KITT.Services/ServiceNames.cs +++ b/src/KITT.Services/ServiceNames.cs @@ -12,4 +12,7 @@ public static class ServiceNames public const string WebApp = "kitt-webapp"; public const string Seeder = "kitt-support-seeder"; + + public const string DataStorageAccount = "kittdatastorage"; + public const string SettingsTables = "kitt-settings-tables"; } diff --git a/src/KITT.Support.Seeder/KITT.Support.Seeder.csproj b/src/KITT.Support.Seeder/KITT.Support.Seeder.csproj index 98bfb58..1983e04 100644 --- a/src/KITT.Support.Seeder/KITT.Support.Seeder.csproj +++ b/src/KITT.Support.Seeder/KITT.Support.Seeder.csproj @@ -1,20 +1,20 @@ - net9.0 + net10.0 enable enable dotnet-KITT.Support.Seeder-7ba1aa34-63f1-4522-9240-26830e3d203a - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/src/KITT.Telegram.Functions/KITT.Telegram.Functions.csproj b/src/KITT.Telegram.Functions/KITT.Telegram.Functions.csproj index 0b73a36..b107dcb 100644 --- a/src/KITT.Telegram.Functions/KITT.Telegram.Functions.csproj +++ b/src/KITT.Telegram.Functions/KITT.Telegram.Functions.csproj @@ -7,13 +7,10 @@ enable - - - - - - - + + + + diff --git a/src/KITT.Telegram.Messages/KITT.Telegram.Messages.csproj b/src/KITT.Telegram.Messages/KITT.Telegram.Messages.csproj index 75cd54f..6e414b4 100644 --- a/src/KITT.Telegram.Messages/KITT.Telegram.Messages.csproj +++ b/src/KITT.Telegram.Messages/KITT.Telegram.Messages.csproj @@ -1,15 +1,15 @@ - net8.0 + net10.0 enable enable - - - + + + diff --git a/src/KITT.Web.App.UI/KITT.Web.App.UI.csproj b/src/KITT.Web.App.UI/KITT.Web.App.UI.csproj index 72d738a..a1d7593 100644 --- a/src/KITT.Web.App.UI/KITT.Web.App.UI.csproj +++ b/src/KITT.Web.App.UI/KITT.Web.App.UI.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable @@ -12,11 +12,12 @@ - - - - - + + + + + + diff --git a/src/KITT.Web.App.UI/ServiceCollectionExtensions.cs b/src/KITT.Web.App.UI/ServiceCollectionExtensions.cs index 5bed970..83d2bb2 100644 --- a/src/KITT.Web.App.UI/ServiceCollectionExtensions.cs +++ b/src/KITT.Web.App.UI/ServiceCollectionExtensions.cs @@ -5,14 +5,17 @@ namespace KITT.Web.App.UI; public static class ServiceCollectionExtensions { - public static IServiceCollection AddDefaultServices(this IServiceCollection services) + extension(IServiceCollection services) { - services.AddLocalization(); - services.AddFluentUIComponents(options => + public IServiceCollection AddDefaultServices() { - options.ValidateClassNames = false; - }); + services.AddLocalization(); + services.AddFluentUIComponents(options => + { + options.ValidateClassNames = false; + }); - return services; + return services; + } } } diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Clients/ClientsServiceCollectionExtensions.cs b/src/KITT.Web.App/KITT.Web.App.Client/Clients/ClientsServiceCollectionExtensions.cs deleted file mode 100644 index 0fd30f1..0000000 --- a/src/KITT.Web.App/KITT.Web.App.Client/Clients/ClientsServiceCollectionExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using KITT.Web.App.Client.Clients.Http; - -namespace KITT.Web.App.Client.Clients; - -public static class ClientsServiceCollectionExtensions -{ - public static IServiceCollection AddGeneralClients( - this IServiceCollection services, - string apiBaseUrl) - { - services - .AddHttpClient(client => client.BaseAddress = new(apiBaseUrl)); - - return services; - } -} diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Clients/Http/SettingsHttpClient.cs b/src/KITT.Web.App/KITT.Web.App.Client/Clients/Http/SettingsHttpClient.cs deleted file mode 100644 index 893a04c..0000000 --- a/src/KITT.Web.App/KITT.Web.App.Client/Clients/Http/SettingsHttpClient.cs +++ /dev/null @@ -1,31 +0,0 @@ -using KITT.Cms.Web.Models.Settings; -using System.Net.Http.Json; - -namespace KITT.Web.App.Client.Clients.Http; - -public class SettingsHttpClient : ISettingsClient -{ - public HttpClient Client { get; } - - public string ApiResource { get; } = "/api/cms/settings"; - - public SettingsHttpClient(HttpClient client) - { - Client = client ?? throw new ArgumentNullException(nameof(client)); - } - - public async Task CreateNewSettingsAsync(CreateNewSettingsModel model) - { - var response = await Client.PostAsJsonAsync(ApiResource, model); - if (!response.IsSuccessStatusCode) - { - throw new ApplicationException("Error creating settings"); - } - } - - public async Task GetAllSettingsAsync() - { - var model = await Client.GetFromJsonAsync(ApiResource); - return model ?? new(); - } -} diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Clients/ISettingsClient.cs b/src/KITT.Web.App/KITT.Web.App.Client/Clients/ISettingsClient.cs deleted file mode 100644 index 6793cda..0000000 --- a/src/KITT.Web.App/KITT.Web.App.Client/Clients/ISettingsClient.cs +++ /dev/null @@ -1,10 +0,0 @@ -using KITT.Cms.Web.Models.Settings; - -namespace KITT.Web.App.Client.Clients; - -public interface ISettingsClient -{ - Task GetAllSettingsAsync(); - - Task CreateNewSettingsAsync(CreateNewSettingsModel model); -} diff --git a/src/KITT.Web.App/KITT.Web.App.Client/KITT.Web.App.Client.csproj b/src/KITT.Web.App/KITT.Web.App.Client/KITT.Web.App.Client.csproj index 9d2a983..147064e 100644 --- a/src/KITT.Web.App/KITT.Web.App.Client/KITT.Web.App.Client.csproj +++ b/src/KITT.Web.App/KITT.Web.App.Client/KITT.Web.App.Client.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable true @@ -9,10 +9,9 @@ - - - - + + + diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Layout/NavMenu.razor b/src/KITT.Web.App/KITT.Web.App.Client/Layout/NavMenu.razor index 9e0636c..2d5058f 100644 --- a/src/KITT.Web.App/KITT.Web.App.Client/Layout/NavMenu.razor +++ b/src/KITT.Web.App/KITT.Web.App.Client/Layout/NavMenu.razor @@ -1,4 +1,4 @@ -@inject IStringLocalizer Localizer +@inject IStringLocalizer localizer @@ -8,11 +8,13 @@ @@ -23,4 +25,6 @@ private bool expanded = true; private bool contentsExpanded = true; + + private bool settingsExpanded = true; } diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Pages/CreateNewSettings.razor b/src/KITT.Web.App/KITT.Web.App.Client/Pages/CreateNewSettings.razor deleted file mode 100644 index 823a4e2..0000000 --- a/src/KITT.Web.App/KITT.Web.App.Client/Pages/CreateNewSettings.razor +++ /dev/null @@ -1,74 +0,0 @@ -@page "/settings/new" -@using KITT.Cms.Web.Models.Settings - -@inject ISettingsClient Client -@inject NavigationManager Navigation -@inject IMessageService MessageService - -@inject IStringLocalizer CommonLocalizer -@inject IStringLocalizer Localizer - -@*@attribute [Authorize]*@ - -KITT - @Localizer[nameof(Resources.Pages.CreateNewSettings.PageTitle)] - - - @Localizer[nameof(Resources.Pages.CreateNewSettings.PageTitle)] - - - - @CommonLocalizer[nameof(UI.Resources.Common.Back)] - - - - - - - - - - https://www.twitch.tv/ - - - - - - - @CommonLocalizer[nameof(UI.Resources.Common.Cancel)] - - - @CommonLocalizer[nameof(UI.Resources.Common.Save)] - - - - - -@code { - private CreateNewSettingsModel model = new(); - - private void Cancel() => model = new(); - - private async Task CreateNewSettingsAsync() - { - try - { - await Client.CreateNewSettingsAsync(model); - model = new(); - - Navigation.NavigateTo("settings"); - } - catch (ApplicationException ex) - { - await MessageService.ShowMessageBarAsync( - ex.Message, - MessageIntent.Error, - SectionNames.MessagesTopSectionName); - } - } -} diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Pages/Settings.razor b/src/KITT.Web.App/KITT.Web.App.Client/Pages/Settings.razor deleted file mode 100644 index 6e1c61b..0000000 --- a/src/KITT.Web.App/KITT.Web.App.Client/Pages/Settings.razor +++ /dev/null @@ -1,56 +0,0 @@ -@page "/settings" -@using KITT.Cms.Web.Models.Settings -@inject ISettingsClient Client -@inject IStringLocalizer Localizer -@inject NavigationManager Navigation -@*@attribute [Authorize]*@ - -KITT - @Localizer[nameof(Resources.Pages.Settings.PageTitle)] - - - @Localizer[nameof(Resources.Pages.Settings.PageTitle)] - - - - @Localizer[nameof(Resources.Pages.Settings.CreateNewSettingsLabel)] - - - - - Loading... - -

@Localizer[nameof(Resources.Pages.Settings.NoSettingsConfiguredMessage)]

-
- - - -
- -@code { - private bool loading = false; - - private GridItemsProvider settingsProvider = default!; - - protected override void OnInitialized() - { - loading = true; - - settingsProvider = async request => - { - try - { - var settings = await Client.GetAllSettingsAsync(); - - return GridItemsProviderResult.From( - items: settings.Items.ToList(), - totalItemCount: settings.Items.Count()); - } - finally - { - loading = false; - StateHasChanged(); - } - }; - } -} diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Program.cs b/src/KITT.Web.App/KITT.Web.App.Client/Program.cs index d6c0fd1..609a5a9 100644 --- a/src/KITT.Web.App/KITT.Web.App.Client/Program.cs +++ b/src/KITT.Web.App/KITT.Web.App.Client/Program.cs @@ -2,7 +2,6 @@ using KITT.Cms.Web.App.Clients; using KITT.Proposals.Web.App.Clients; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using KITT.Web.App.Client.Clients; var builder = WebAssemblyHostBuilder.CreateDefault(args); @@ -12,7 +11,6 @@ builder.Services.AddDefaultServices(); -builder.Services.AddGeneralClients(builder.HostEnvironment.BaseAddress); builder.Services.AddCmsClients(builder.HostEnvironment.BaseAddress); builder.Services.AddProposalsClients(builder.HostEnvironment.BaseAddress); diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.Designer.cs b/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.Designer.cs index 998bc40..07d305e 100644 --- a/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.Designer.cs +++ b/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.Designer.cs @@ -19,7 +19,7 @@ namespace KITT.Web.App.Client.Resources.Shared { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class NavMenu { @@ -87,6 +87,15 @@ public static string ProposalsMenuLabel { } } + /// + /// Looks up a localized string similar to Connected channels. + /// + public static string SettingsChannelsMenuLabel { + get { + return ResourceManager.GetString("SettingsChannelsMenuLabel", resourceCulture); + } + } + /// /// Looks up a localized string similar to Settings. /// diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.it.resx b/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.it.resx index ba26043..08f8cf0 100644 --- a/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.it.resx +++ b/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.it.resx @@ -126,10 +126,13 @@ Proposte + + Canali collegati + Impostazioni Dirette - + \ No newline at end of file diff --git a/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.resx b/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.resx index 570bd06..7851d28 100644 --- a/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.resx +++ b/src/KITT.Web.App/KITT.Web.App.Client/Resources/Shared/NavMenu.resx @@ -126,6 +126,9 @@ Proposals + + Connected channels + Settings diff --git a/src/KITT.Web.App/KITT.Web.App.Client/_Imports.razor b/src/KITT.Web.App/KITT.Web.App.Client/_Imports.razor index aaa9974..bd1927b 100644 --- a/src/KITT.Web.App/KITT.Web.App.Client/_Imports.razor +++ b/src/KITT.Web.App/KITT.Web.App.Client/_Imports.razor @@ -11,7 +11,6 @@ @using Microsoft.JSInterop @using KITT.Web.App.Client @using KITT.Web.App.Client.Components -@using KITT.Web.App.Client.Clients @using KITT.Cms.Web.Models.Streamings @using KITT.Web.App.UI @using KITT.Web.App.UI.Components diff --git a/src/KITT.Web.App/KITT.Web.App/Endpoints/AuthenticationEndpoints.cs b/src/KITT.Web.App/KITT.Web.App/Endpoints/AuthenticationEndpoints.cs index 4662852..e17982b 100644 --- a/src/KITT.Web.App/KITT.Web.App/Endpoints/AuthenticationEndpoints.cs +++ b/src/KITT.Web.App/KITT.Web.App/Endpoints/AuthenticationEndpoints.cs @@ -7,20 +7,23 @@ namespace KITT.Web.App.Endpoints; public static class AuthenticationEndpoints { - public static IEndpointRouteBuilder MapAuthenticationEndpoints(this IEndpointRouteBuilder builder) + extension(IEndpointRouteBuilder builder) { - var authenticationGroup = builder.MapGroup("/authentication"); + public IEndpointRouteBuilder MapAuthenticationEndpoints() + { + var authenticationGroup = builder.MapGroup("/authentication"); - authenticationGroup - .MapGet("login", Login) - .WithName(nameof(Login)) - .AllowAnonymous(); + authenticationGroup + .MapGet("login", Login) + .WithName(nameof(Login)) + .AllowAnonymous(); - authenticationGroup - .MapGet("logout", Logout) - .WithName(nameof(Logout)); + authenticationGroup + .MapGet("logout", Logout) + .WithName(nameof(Logout)); - return builder; + return builder; + } } private static async Task Logout( diff --git a/src/KITT.Web.App/KITT.Web.App/Endpoints/CmsEndpoints.cs b/src/KITT.Web.App/KITT.Web.App/Endpoints/CmsEndpoints.cs index 5f972c3..c266dbc 100644 --- a/src/KITT.Web.App/KITT.Web.App/Endpoints/CmsEndpoints.cs +++ b/src/KITT.Web.App/KITT.Web.App/Endpoints/CmsEndpoints.cs @@ -5,59 +5,62 @@ namespace KITT.Web.App.Endpoints; internal static class CmsEndpoints { - public static IEndpointRouteBuilder MapCmsEndpoints(this IEndpointRouteBuilder builder) + extension(IEndpointRouteBuilder builder) { - builder.MapForwarder( - "/api/cms/streamings", - $"https+http://{ServiceNames.CmsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/streamings", - GetScopes)) - .RequireAuthorization(); + public IEndpointRouteBuilder MapCmsEndpoints() + { + builder.MapForwarder( + "/api/cms/streamings", + $"https+http://{ServiceNames.CmsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/streamings", + GetScopes)) + .RequireAuthorization(); - builder.MapForwarder( - "/api/cms/streamings/{id}", - $"https+http://{ServiceNames.CmsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/streamings/{id}", - GetScopes)) - .RequireAuthorization(); + builder.MapForwarder( + "/api/cms/streamings/{id}", + $"https+http://{ServiceNames.CmsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/streamings/{id}", + GetScopes)) + .RequireAuthorization(); - builder.MapForwarder( - "/api/cms/streamings/import", - $"https+http://{ServiceNames.CmsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/streamings/import", - GetScopes)) - .RequireAuthorization(); + builder.MapForwarder( + "/api/cms/streamings/import", + $"https+http://{ServiceNames.CmsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/streamings/import", + GetScopes)) + .RequireAuthorization(); - builder.MapForwarder( - "/api/cms/streamings/stats", - $"https+http://{ServiceNames.CmsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/streamings/stats", - GetScopes)) - .RequireAuthorization(); + builder.MapForwarder( + "/api/cms/streamings/stats", + $"https+http://{ServiceNames.CmsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/streamings/stats", + GetScopes)) + .RequireAuthorization(); - builder.MapForwarder( - "/api/cms/settings", - $"https+http://{ServiceNames.CmsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/settings", - GetScopes)) - .RequireAuthorization(); + builder.MapForwarder( + "/api/cms/settings/channels", + $"https+http://{ServiceNames.CmsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/settings/channels", + GetScopes)) + .RequireAuthorization(); - builder.MapForwarder( - "/api/cms/settings/{id}", - $"https+http://{ServiceNames.CmsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/settings/{id}", - GetScopes)) - .RequireAuthorization(); + builder.MapForwarder( + "/api/cms/settings/channels/{id}", + $"https+http://{ServiceNames.CmsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/settings/channels/{id}", + GetScopes)) + .RequireAuthorization(); - return builder; + return builder; + } } - + private static IEnumerable GetScopes(IConfiguration configuration) => [ $"api://{configuration["Identity:Cms:AppId"]}/Cms.Read", diff --git a/src/KITT.Web.App/KITT.Web.App/Endpoints/ProposalEndpoints.cs b/src/KITT.Web.App/KITT.Web.App/Endpoints/ProposalEndpoints.cs index 8418680..4076d19 100644 --- a/src/KITT.Web.App/KITT.Web.App/Endpoints/ProposalEndpoints.cs +++ b/src/KITT.Web.App/KITT.Web.App/Endpoints/ProposalEndpoints.cs @@ -5,42 +5,46 @@ namespace KITT.Web.App.Endpoints; internal static class ProposalEndpoints { - public static IEndpointRouteBuilder MapProposalEndpoints(this IEndpointRouteBuilder builder) + extension(IEndpointRouteBuilder builder) { - builder.MapForwarder( - "/api/proposals", - $"https+http://{ServiceNames.ProposalsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( + public IEndpointRouteBuilder MapProposalEndpoints() + { + builder.MapForwarder( "/api/proposals", - GetScopes)) - .RequireAuthorization(); + $"https+http://{ServiceNames.ProposalsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/proposals", + GetScopes)) + .RequireAuthorization(); - builder.MapForwarder( - "/api/proposals/{id}", - $"https+http://{ServiceNames.ProposalsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/proposals/{id}", - GetScopes)) - .RequireAuthorization(); + builder.MapForwarder( + "/api/proposals/{id}", + $"https+http://{ServiceNames.ProposalsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/proposals/{id}", + GetScopes)) + .RequireAuthorization(); - builder.MapForwarder( - "/api/proposals/{id}/refuse", - $"https+http://{ServiceNames.ProposalsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/proposals/{id}/refuse", - GetScopes)) - .RequireAuthorization(); - - builder.MapForwarder( - "/api/proposals/stats", - $"https+http://{ServiceNames.ProposalsApi}", - transformBuilder => transformBuilder.ConfigureWithTargetPath( - "/api/proposals/stats", - GetScopes)) - .RequireAuthorization(); + builder.MapForwarder( + "/api/proposals/{id}/refuse", + $"https+http://{ServiceNames.ProposalsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/proposals/{id}/refuse", + GetScopes)) + .RequireAuthorization(); + + builder.MapForwarder( + "/api/proposals/stats", + $"https+http://{ServiceNames.ProposalsApi}", + transformBuilder => transformBuilder.ConfigureWithTargetPath( + "/api/proposals/stats", + GetScopes)) + .RequireAuthorization(); - return builder; + return builder; + } } + private static IEnumerable GetScopes(IConfiguration configuration) => [ diff --git a/src/KITT.Web.App/KITT.Web.App/Endpoints/ReverseProxy/TransformBuilderContextExtensions.cs b/src/KITT.Web.App/KITT.Web.App/Endpoints/ReverseProxy/TransformBuilderContextExtensions.cs index 69097e7..0dae98c 100644 --- a/src/KITT.Web.App/KITT.Web.App/Endpoints/ReverseProxy/TransformBuilderContextExtensions.cs +++ b/src/KITT.Web.App/KITT.Web.App/Endpoints/ReverseProxy/TransformBuilderContextExtensions.cs @@ -11,10 +11,10 @@ public static void ConfigureWithTargetPath( string targetPath, Func> scopesResolver) { + transformBuilder.AddPathRouteValues(targetPath); + transformBuilder.AddRequestTransform(async transformContext => { - transformContext.Path = new(targetPath); - var tokenAcquisition = transformContext.HttpContext.RequestServices.GetRequiredService(); var configuration = transformContext.HttpContext.RequestServices.GetRequiredService(); var user = transformContext.HttpContext.User; diff --git a/src/KITT.Web.App/KITT.Web.App/KITT.Web.App.csproj b/src/KITT.Web.App/KITT.Web.App/KITT.Web.App.csproj index 923a605..42d4668 100644 --- a/src/KITT.Web.App/KITT.Web.App/KITT.Web.App.csproj +++ b/src/KITT.Web.App/KITT.Web.App/KITT.Web.App.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable @@ -14,13 +14,13 @@
- - - + + + - - - - + + + +
diff --git a/src/KITT.Web.Models/KITT.Web.Models.csproj b/src/KITT.Web.Models/KITT.Web.Models.csproj index 9623deb..93f5ab4 100644 --- a/src/KITT.Web.Models/KITT.Web.Models.csproj +++ b/src/KITT.Web.Models/KITT.Web.Models.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable diff --git a/src/KITT.Web.Shared/Azure/AzureKeyVaultExtensions.cs b/src/KITT.Web.Shared/Azure/AzureKeyVaultExtensions.cs index 653edbb..089b9d4 100644 --- a/src/KITT.Web.Shared/Azure/AzureKeyVaultExtensions.cs +++ b/src/KITT.Web.Shared/Azure/AzureKeyVaultExtensions.cs @@ -7,41 +7,44 @@ namespace KITT.Web.Shared.Azure; public static class AzureKeyVaultExtensions { - public static IHostApplicationBuilder AddAzureKeyVaultClientWithEmulatorFallback(this IHostApplicationBuilder builder, string connectionStringName) + extension(IHostApplicationBuilder builder) { - ArgumentNullException.ThrowIfNull(builder); - - if (builder.Environment.IsDevelopment()) - { - var vaultUri = builder.Configuration.GetConnectionString(connectionStringName); - builder.Services.AddAzureKeyVaultEmulator(vaultUri); - } - else + public IHostApplicationBuilder AddAzureKeyVaultClientWithEmulatorFallback(string connectionStringName) { - builder.AddAzureKeyVaultClient(connectionStringName); - } + ArgumentNullException.ThrowIfNull(builder); - return builder; - } + if (builder.Environment.IsDevelopment()) + { + var vaultUri = builder.Configuration.GetConnectionString(connectionStringName); + builder.Services.AddAzureKeyVaultEmulator(vaultUri); + } + else + { + builder.AddAzureKeyVaultClient(connectionStringName); + } - public static IHostApplicationBuilder AddAzureKeyVaultSecretsWithEmulatorFallback(this IHostApplicationBuilder builder, string connectionStringName) - { - ArgumentNullException.ThrowIfNull(builder); + return builder; + } - if (builder.Environment.IsDevelopment()) + public IHostApplicationBuilder AddAzureKeyVaultSecretsWithEmulatorFallback(string connectionStringName) { - var vaultUri = builder.Configuration.GetConnectionString(connectionStringName); + ArgumentNullException.ThrowIfNull(builder); - var secretClient = KeyVaultHelper.GetSecretClient(vaultUri); - builder.Configuration.AddAzureKeyVault( - secretClient, - new AzureKeyVaultConfigurationOptions()); - } - else - { - builder.Configuration.AddAzureKeyVaultSecrets(connectionStringName); + if (builder.Environment.IsDevelopment()) + { + var vaultUri = builder.Configuration.GetConnectionString(connectionStringName); + + var secretClient = KeyVaultHelper.GetSecretClient(vaultUri); + builder.Configuration.AddAzureKeyVault( + secretClient, + new AzureKeyVaultConfigurationOptions()); + } + else + { + builder.Configuration.AddAzureKeyVaultSecrets(connectionStringName); + } + + return builder; } - - return builder; } } diff --git a/src/KITT.Web.Shared/KITT.Web.Shared.csproj b/src/KITT.Web.Shared/KITT.Web.Shared.csproj index 57a2a37..6d4479c 100644 --- a/src/KITT.Web.Shared/KITT.Web.Shared.csproj +++ b/src/KITT.Web.Shared/KITT.Web.Shared.csproj @@ -1,17 +1,17 @@  - net9.0 + net10.0 enable enable - - - - - + + + + + diff --git a/src/KITT.Web.Shared/Security/ClaimsPrincipalExtensions.cs b/src/KITT.Web.Shared/Security/ClaimsPrincipalExtensions.cs index a46fcd5..8cd7e6d 100644 --- a/src/KITT.Web.Shared/Security/ClaimsPrincipalExtensions.cs +++ b/src/KITT.Web.Shared/Security/ClaimsPrincipalExtensions.cs @@ -5,6 +5,8 @@ namespace KITT.Web.Shared.Security; public static class ClaimsPrincipalExtensions { - public static string GetUserId(this ClaimsPrincipal user) - => user.GetObjectId()!; + extension(ClaimsPrincipal user) + { + public string GetUserId() => user.GetObjectId()!; + } } diff --git a/src/LemonBot.Clients/LemonBot.Clients.csproj b/src/LemonBot.Clients/LemonBot.Clients.csproj index 7714b8c..351ccd9 100644 --- a/src/LemonBot.Clients/LemonBot.Clients.csproj +++ b/src/LemonBot.Clients/LemonBot.Clients.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/LemonBot.Commands/LemonBot.Commands.csproj b/src/LemonBot.Commands/LemonBot.Commands.csproj index c1839c2..5e507f6 100644 --- a/src/LemonBot.Commands/LemonBot.Commands.csproj +++ b/src/LemonBot.Commands/LemonBot.Commands.csproj @@ -7,12 +7,8 @@ - - - - - - + + diff --git a/src/LemonBot/LemonBot.csproj b/src/LemonBot/LemonBot.csproj index dc6c81f..079a315 100644 --- a/src/LemonBot/LemonBot.csproj +++ b/src/LemonBot/LemonBot.csproj @@ -6,9 +6,9 @@ enable - - - + + + diff --git a/tests/KITT.Cms.Web.Api.Test/KITT.Cms.Web.Api.Test.csproj b/tests/KITT.Cms.Web.Api.Test/KITT.Cms.Web.Api.Test.csproj index ab70dd0..9e365b6 100644 --- a/tests/KITT.Cms.Web.Api.Test/KITT.Cms.Web.Api.Test.csproj +++ b/tests/KITT.Cms.Web.Api.Test/KITT.Cms.Web.Api.Test.csproj @@ -1,22 +1,22 @@  - net9.0 + net10.0 enable enable false - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/KITT.Cms.Web.Api.Test/SettingsEndpointsTest.cs b/tests/KITT.Cms.Web.Api.Test/SettingsEndpointsTest.cs index b9ff3aa..26a920c 100644 --- a/tests/KITT.Cms.Web.Api.Test/SettingsEndpointsTest.cs +++ b/tests/KITT.Cms.Web.Api.Test/SettingsEndpointsTest.cs @@ -31,38 +31,6 @@ public async Task GetAllSettings_Should_Return_Unauthorized_If_User_Is_Not_Authe // Assert Assert.Equal(System.Net.HttpStatusCode.Unauthorized, response.StatusCode); } - - [Fact(Skip = "Must be fixed")] - public async Task GetAllSettings_Should_Return_Empty_List_If_No_Settings_Are_Available() - { - // Arrange - using var app = _factory.WithWebHostBuilder(builder => - { - builder.ConfigureServices( - services => services.ConfigureTestJwtOptions(CmsWebApplicationFactory.TenantId, CmsWebApplicationFactory.AppId)); - }); - - var token = TestJwtTokenProvider.JwtSecurityTokenHandler.WriteToken( - new JwtSecurityToken( - $"https://sts.windows.net/{CmsWebApplicationFactory.TenantId}/", - $"api://{CmsWebApplicationFactory.AppId}", - new TestClaimsProvider().Claims, - expires: DateTime.Now.AddMinutes(30), - signingCredentials: TestJwtTokenProvider.SigningCredentials)); - - using var client = app.CreateClient(); - client.DefaultRequestHeaders.Authorization = new("Bearer", token); - - // Act - var response = await client.GetAsync(endpoint); - - // Assert - response.EnsureSuccessStatusCode(); - var model = await response.Content.ReadFromJsonAsync(); - - Assert.NotNull(model); - Assert.Empty(model.Items); - } #endregion #region GetSettingsDetails tests diff --git a/tests/KITT.Core.Test/Commands/StreamingCommandsTest.cs b/tests/KITT.Core.Test/Commands/StreamingCommandsTest.cs index ca6538e..40cb50f 100644 --- a/tests/KITT.Core.Test/Commands/StreamingCommandsTest.cs +++ b/tests/KITT.Core.Test/Commands/StreamingCommandsTest.cs @@ -26,9 +26,8 @@ public void Ctor_Should_Throw_ArgumentNullException_If_Context_Is_Null() { KittDbContext context = null; var validator = new StreamingValidator(_fixture.Context); - var messageBus = new Mock().Object; - - var ex = Assert.Throws(() => new StreamingCommands(context, validator, messageBus)); + + var ex = Assert.Throws(() => new StreamingCommands(context, validator)); Assert.Equal(nameof(context), ex.ParamName); } @@ -37,9 +36,8 @@ public void Ctor_Should_Throw_ArgumentNullException_If_Validator_Is_Null() { KittDbContext context = _fixture.Context; StreamingValidator validator = null; - var messageBus = new Mock().Object; - - var ex = Assert.Throws(() => new StreamingCommands(context, validator, messageBus)); + + var ex = Assert.Throws(() => new StreamingCommands(context, validator)); Assert.Equal(nameof(validator), ex.ParamName); } #endregion @@ -55,10 +53,12 @@ public async Task ScheduleStreamingAsync_Should_Throw_ValidationException_If_Str "test1", "test-slug", "albx87", + "@albx87", DateOnly.FromDateTime(DateTime.Today.AddDays(1)), TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)), TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)), "https://www.twitch.tv/albx87", + "https://www.youtube.com/@albx87", userId); context.Add(streaming); @@ -66,16 +66,17 @@ public async Task ScheduleStreamingAsync_Should_Throw_ValidationException_If_Str }); var validator = new StreamingValidator(_fixture.Context); - var messageBus = new Mock().Object; - var commands = new StreamingCommands(_fixture.Context, validator, messageBus); + var commands = new StreamingCommands(_fixture.Context, validator); string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; string streamingTitle = "test"; string streamingSlug = "test-slug"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today.AddDays(2)); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(19)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(20)); - string hostingChannelUrl = "https://www.twitch.tv/albx87"; + string twitchUrl = "https://www.twitch.tv/albx87"; + string youTubeUrl = "https://www.youtube.com/@albx87"; string streamingAbstract = "streaming abstract"; var seo = new Content.SeoData(); @@ -83,12 +84,14 @@ public async Task ScheduleStreamingAsync_Should_Throw_ValidationException_If_Str () => commands.ScheduleStreamingAsync( userId, twitchChannel, + youTubeChannel, streamingTitle, streamingSlug, scheduleDate, startingTime, endingTime, - hostingChannelUrl, + twitchUrl, + youTubeUrl, streamingAbstract, seo)); @@ -99,29 +102,32 @@ public async Task ScheduleStreamingAsync_Should_Throw_ValidationException_If_Str public async Task ScheduleStreamingAsync_Should_Add_Streaming_With_Specified_Values() { var validator = new StreamingValidator(_fixture.Context); - var messageBus = new Mock().Object; - var commands = new StreamingCommands(_fixture.Context, validator, messageBus); + var commands = new StreamingCommands(_fixture.Context, validator); string userId = Guid.NewGuid().ToString(); string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; string streamingTitle = "test"; string streamingSlug = "test-schedule-streaming-slug"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); - string hostingChannelUrl = "https://www.twitch.tv/albx87"; + string twitchUrl = "https://www.twitch.tv/albx87"; + string youTubeUrl = "https://www.youtube.com/@albx87"; string streamingAbstract = "streaming abstract"; var seo = new Content.SeoData(); var scheduledStreamingId = await commands.ScheduleStreamingAsync( userId, twitchChannel, + youTubeChannel, streamingTitle, streamingSlug, scheduleDate, startingTime, endingTime, - hostingChannelUrl, + twitchUrl, + youTubeUrl, streamingAbstract, seo); @@ -134,53 +140,9 @@ public async Task ScheduleStreamingAsync_Should_Add_Streaming_With_Specified_Val Assert.Equal(scheduleDate, scheduledStreaming.ScheduleDate); Assert.Equal(startingTime, scheduledStreaming.StartingTime); Assert.Equal(endingTime, scheduledStreaming.EndingTime); - Assert.Equal(hostingChannelUrl, scheduledStreaming.HostingChannelUrl); + Assert.Equal(twitchUrl, scheduledStreaming.TwitchUrl); Assert.Equal(streamingAbstract, scheduledStreaming.Abstract); } - - [Fact(Skip = "At this moment the message bus has been disabled")] - public async Task ScheduleStreamingAsync_Should_Send_ScheduledStreamingMessage_Correctly() - { - var messageBusMock = new Mock(); - - var validator = new StreamingValidator(_fixture.Context); - var messageBus = messageBusMock.Object; - var commands = new StreamingCommands(_fixture.Context, validator, messageBus); - - string userId = Guid.NewGuid().ToString(); - string twitchChannel = "albx87"; - string streamingTitle = "test"; - string streamingSlug = "test-message-slug"; - DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); - TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); - TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); - string hostingChannelUrl = "https://www.twitch.tv/albx87"; - string streamingAbstract = "streaming abstract"; - var seo = new Content.SeoData(); - - var scheduledStreamingId = await commands.ScheduleStreamingAsync( - userId, - twitchChannel, - streamingTitle, - streamingSlug, - scheduleDate, - startingTime, - endingTime, - hostingChannelUrl, - streamingAbstract, - seo); - - var expectedMessage = new StreamingScheduledMessage( - scheduledStreamingId, - streamingTitle, - streamingSlug, - scheduleDate, - startingTime, - endingTime, - hostingChannelUrl); - - messageBusMock.Verify(m => m.SendAsync(expectedMessage), Times.Once); - } #endregion #region UpdateStreamingAsync tests @@ -190,8 +152,7 @@ public async Task UpdateStreamingAsync_Should_Update_Streaming_Information_Corre var streamingId = Guid.Empty; var validator = new StreamingValidator(_fixture.Context); - var messageBus = new Mock().Object; - var commands = new StreamingCommands(_fixture.Context, validator, messageBus); + var commands = new StreamingCommands(_fixture.Context, validator); _fixture.PrepareData(context => { @@ -200,10 +161,12 @@ public async Task UpdateStreamingAsync_Should_Update_Streaming_Information_Corre "title", "slug", "albx87", + "@albx87", DateOnly.FromDateTime(DateTime.Today), TimeOnly.FromTimeSpan(TimeSpan.FromHours(19)), TimeOnly.FromTimeSpan(TimeSpan.FromHours(20)), "https://www.twitch.tv/albx87", + "https://www.youtube.com/@albx87", userId); context.Add(newStreaming); @@ -223,6 +186,8 @@ public async Task UpdateStreamingAsync_Should_Update_Streaming_Information_Corre await commands.UpdateStreamingAsync( streamingId, + "https://twitch.tv/albx87", + "https://youtube.com/@albx87", streamingTitle, scheduleDate, startingTime, @@ -238,9 +203,9 @@ await commands.UpdateStreamingAsync( Assert.Equal(scheduleDate, updatedStreaming.ScheduleDate); Assert.Equal(startingTime, updatedStreaming.StartingTime); Assert.Equal(endingTime, updatedStreaming.EndingTime); - Assert.Equal(hostingChannelUrl, updatedStreaming.HostingChannelUrl); + Assert.Equal(hostingChannelUrl, updatedStreaming.TwitchUrl); Assert.Equal(streamingAbstract, updatedStreaming.Abstract); - Assert.Equal(youtubeRegistrationLink, updatedStreaming.YouTubeVideoUrl); + Assert.Equal(youtubeRegistrationLink, updatedStreaming.YouTubeUrl); } [Fact] @@ -252,8 +217,7 @@ public async Task UpdateStreamingAsync_Should_Not_Update_Schedule_If_It_Is_Not_C var endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(20)); var validator = new StreamingValidator(_fixture.Context); - var messageBus = new Mock().Object; - var commands = new StreamingCommands(_fixture.Context, validator, messageBus); + var commands = new StreamingCommands(_fixture.Context, validator); _fixture.PrepareData(context => { @@ -262,6 +226,7 @@ public async Task UpdateStreamingAsync_Should_Not_Update_Schedule_If_It_Is_Not_C "title", "slug", "albx87", + "@albx87", scheduleDate, startingTime, endingTime, @@ -284,6 +249,8 @@ public async Task UpdateStreamingAsync_Should_Not_Update_Schedule_If_It_Is_Not_C await commands.UpdateStreamingAsync( streamingId, + "https://twitch.tv/albx87", + "https://youtube.com/@albx87", streamingTitle, scheduleDate, startingTime, diff --git a/tests/KITT.Core.Test/KITT.Core.Test.csproj b/tests/KITT.Core.Test/KITT.Core.Test.csproj index 26246c2..ec44d5d 100644 --- a/tests/KITT.Core.Test/KITT.Core.Test.csproj +++ b/tests/KITT.Core.Test/KITT.Core.Test.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable false @@ -14,15 +14,15 @@ - - + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/KITT.Core.Test/Models/StreamingTests.cs b/tests/KITT.Core.Test/Models/StreamingTests.cs index 1df5e9f..c753d74 100644 --- a/tests/KITT.Core.Test/Models/StreamingTests.cs +++ b/tests/KITT.Core.Test/Models/StreamingTests.cs @@ -14,14 +14,16 @@ public void Schedule_Should_Throw_ArgumentException_If_Title_Is_Empty(string tit { string slug = "test"; string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); - string hostingChannelUrl = "albx87"; + string twitchUrl = "albx87"; + string youTubeUrl = "@albx87"; string userId = Guid.NewGuid().ToString(); var ex = Assert.Throws( - () => Streaming.Schedule(title, slug, twitchChannel, scheduleDate, startingTime, endingTime, hostingChannelUrl, userId)); + () => Streaming.Schedule(title, slug, twitchChannel, youTubeChannel, scheduleDate, startingTime, endingTime, twitchUrl, youTubeUrl, userId)); Assert.Equal(nameof(title), ex.ParamName); } @@ -34,52 +36,36 @@ public void Schedule_Should_Throw_ArgumentException_If_Slug_Is_Empty(string slug { string title = "test"; string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); - string hostingChannelUrl = "albx87"; + string twitchUrl = "albx87"; + string youTubeUrl = "@albx87"; string userId = Guid.NewGuid().ToString(); var ex = Assert.Throws( - () => Streaming.Schedule(title, slug, twitchChannel, scheduleDate, startingTime, endingTime, hostingChannelUrl, userId)); + () => Streaming.Schedule(title, slug, twitchChannel, youTubeChannel, scheduleDate, startingTime, endingTime, twitchUrl, youTubeUrl, userId)); Assert.Equal(nameof(slug), ex.ParamName); } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void Schedule_Should_Throw_ArgumentException_If_Twitch_Channel_Is_Empty(string twitchChannel) - { - string title = "test"; - string slug = "test"; - DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); - TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); - TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); - string hostingChannelUrl = "albx87"; - string userId = Guid.NewGuid().ToString(); - - var ex = Assert.Throws( - () => Streaming.Schedule(title, slug, twitchChannel, scheduleDate, startingTime, endingTime, hostingChannelUrl, userId)); - - Assert.Equal(nameof(twitchChannel), ex.ParamName); - } - [Fact] public void Schedule_Should_Throw_ArgumentException_If_Schedule_Date_Is_Past_Date() { string title = "test"; string slug = "test"; string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today.AddDays(-1)); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); - string hostingChannelUrl = "albx87"; + string twitchUrl = "albx87"; + string youTubeUrl = "@albx87"; string userId = Guid.NewGuid().ToString(); var ex = Assert.Throws( - () => Streaming.Schedule(title, slug, twitchChannel, scheduleDate, startingTime, endingTime, hostingChannelUrl, userId)); + () => Streaming.Schedule(title, slug, twitchChannel, youTubeChannel, scheduleDate, startingTime, endingTime, twitchUrl, youTubeUrl, userId)); Assert.Equal(nameof(scheduleDate), ex.ParamName); } @@ -90,14 +76,16 @@ public void Schedule_Should_Throw_ArgumentException_If_Ending_Time_Is_Previous_T string title = "test"; string slug = "test"; string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(15)); - string hostingChannelUrl = "albx87"; + string twitchUrl = "albx87"; + string youTubeUrl = "@albx87"; string userId = Guid.NewGuid().ToString(); var ex = Assert.Throws( - () => Streaming.Schedule(title, slug, twitchChannel, scheduleDate, startingTime, endingTime, hostingChannelUrl, userId)); + () => Streaming.Schedule(title, slug, twitchChannel, youTubeChannel, scheduleDate, startingTime, endingTime, twitchUrl, youTubeUrl, userId)); Assert.Equal(nameof(endingTime), ex.ParamName); } @@ -106,20 +94,22 @@ public void Schedule_Should_Throw_ArgumentException_If_Ending_Time_Is_Previous_T [InlineData(null)] [InlineData("")] [InlineData(" ")] - public void Schedule_Should_Throw_ArgumentException_If_Hosting_Channel_Url_Is_Empty(string hostingChannelUrl) + public void Schedule_Should_Throw_ArgumentException_If_Twitch_Url_Is_Empty(string twitchUrl) { string title = "test"; string slug = "test"; string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); + string youTubeUrl = "@albx87"; string userId = Guid.NewGuid().ToString(); var ex = Assert.Throws( - () => Streaming.Schedule(title, slug, twitchChannel, scheduleDate, startingTime, endingTime, hostingChannelUrl, userId)); + () => Streaming.Schedule(title, slug, twitchChannel, youTubeChannel, scheduleDate, startingTime, endingTime, twitchUrl, youTubeUrl, userId)); - Assert.Equal(nameof(hostingChannelUrl), ex.ParamName); + Assert.Equal(nameof(twitchUrl), ex.ParamName); } [Theory] @@ -131,13 +121,15 @@ public void Schedule_Should_Throw_ArgumentException_If_UserId_Is_Empty(string us string title = "test"; string slug = "test"; string twitchChannel = "albx87"; - string hostingChannelUrl = "albx87"; + string youTubeChannel = "@albx87"; + string twitchUrl = "albx87"; + string youTubeUrl = "@albx87"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); var ex = Assert.Throws( - () => Streaming.Schedule(title, slug, twitchChannel, scheduleDate, startingTime, endingTime, hostingChannelUrl, userId)); + () => Streaming.Schedule(title, slug, twitchChannel, youTubeChannel, scheduleDate, startingTime, endingTime, twitchUrl, youTubeUrl, userId)); Assert.Equal(nameof(userId), ex.ParamName); } @@ -148,29 +140,35 @@ public void Schedule_Should_Create_A_New_Streaming_With_Specified_Values() string title = "test"; string slug = "test"; string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); - string hostingChannelUrl = "albx87"; + string twitchUrl = "albx87"; + string youTubeUrl = "@albx87"; string userId = Guid.NewGuid().ToString(); var streaming = Streaming.Schedule( title, slug, twitchChannel, + youTubeChannel, scheduleDate, startingTime, endingTime, - hostingChannelUrl, + twitchUrl, + youTubeUrl, userId); Assert.Equal(title, streaming.Title); Assert.Equal(slug, streaming.Slug); Assert.Equal(twitchChannel, streaming.TwitchChannel); + Assert.Equal(youTubeChannel, streaming.YouTubeChannel); Assert.Equal(scheduleDate, streaming.ScheduleDate); Assert.Equal(startingTime, streaming.StartingTime); Assert.Equal(endingTime, streaming.EndingTime); - Assert.Equal(hostingChannelUrl, streaming.HostingChannelUrl); + Assert.Equal(twitchUrl, streaming.TwitchUrl); + Assert.Equal(youTubeUrl, streaming.YouTubeUrl); Assert.NotEqual(Guid.Empty, streaming.Id); Assert.Equal(userId, streaming.UserId); } @@ -248,8 +246,8 @@ public void SetRegistrationYoutubeUrl_Should_Set_Youtube_Registration_Url_With_S var streaming = CreateStreamingForTests(); string youtubeUrl = "https://www.youtube.com"; - streaming.SetRegistrationYoutubeUrl(youtubeUrl); - Assert.Equal(youtubeUrl, streaming.YouTubeVideoUrl); + streaming.SetYouTubeUrl(youtubeUrl); + Assert.Equal(youtubeUrl, streaming.YouTubeUrl); } #endregion @@ -260,19 +258,23 @@ private Streaming CreateStreamingForTests() string title = "test"; string slug = "test"; string twitchChannel = "albx87"; + string youTubeChannel = "@albx87"; DateOnly scheduleDate = DateOnly.FromDateTime(DateTime.Today); TimeOnly startingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(16)); TimeOnly endingTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(18)); - string hostingChannelUrl = "albx87"; + string twitchUrl = "albx87"; + string youTubeUrl = "@albx87"; var streaming = Streaming.Schedule( title, slug, twitchChannel, + youTubeChannel, scheduleDate, startingTime, endingTime, - hostingChannelUrl, + twitchUrl, + youTubeUrl, userId); return streaming; diff --git a/tests/KITT.Telegram.Functions.Test/KITT.Telegram.Functions.Test.csproj b/tests/KITT.Telegram.Functions.Test/KITT.Telegram.Functions.Test.csproj index 413a24b..7dab461 100644 --- a/tests/KITT.Telegram.Functions.Test/KITT.Telegram.Functions.Test.csproj +++ b/tests/KITT.Telegram.Functions.Test/KITT.Telegram.Functions.Test.csproj @@ -10,9 +10,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -22,8 +22,4 @@ - - - - diff --git a/tests/KITT.Web.Testing/KITT.Web.Testing.csproj b/tests/KITT.Web.Testing/KITT.Web.Testing.csproj index 75a6224..5b66ddc 100644 --- a/tests/KITT.Web.Testing/KITT.Web.Testing.csproj +++ b/tests/KITT.Web.Testing/KITT.Web.Testing.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable @@ -11,9 +11,9 @@ - - - + + +