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