commit 5a060b0afe1a75252952eac3bdc1c46a0df656b0 Author: vladzvx Date: Mon Aug 5 03:22:49 2024 +0300 first commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4d72b4f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6584b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,279 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +*.yaml + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +env/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +output/ +[Pp]roperties/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +[Log]log* +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj +*.pwd +*.env + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + + +#deploy +deploy*.bat +.github + +docker-compose-env/ +*.env + +.vs +.vscode \ No newline at end of file diff --git a/BGC.Client/.gitignore b/BGC.Client/.gitignore new file mode 100644 index 0000000..8afdcb6 --- /dev/null +++ b/BGC.Client/.gitignore @@ -0,0 +1,454 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/BGC.Client/BGC.Client.Android/BGC.Client.Android.csproj b/BGC.Client/BGC.Client.Android/BGC.Client.Android.csproj new file mode 100644 index 0000000..4548c02 --- /dev/null +++ b/BGC.Client/BGC.Client.Android/BGC.Client.Android.csproj @@ -0,0 +1,28 @@ + + + Exe + net8.0-android + 21 + enable + com.CompanyName.BGC.Client + 1 + 1.0 + apk + False + + + + + Resources\drawable\Icon.png + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.Android/Icon.png b/BGC.Client/BGC.Client.Android/Icon.png new file mode 100644 index 0000000..41a2a61 Binary files /dev/null and b/BGC.Client/BGC.Client.Android/Icon.png differ diff --git a/BGC.Client/BGC.Client.Android/MainActivity.cs b/BGC.Client/BGC.Client.Android/MainActivity.cs new file mode 100644 index 0000000..a125c47 --- /dev/null +++ b/BGC.Client/BGC.Client.Android/MainActivity.cs @@ -0,0 +1,24 @@ +using Android.App; +using Android.Content.PM; +using Avalonia; +using Avalonia.Android; +using Avalonia.ReactiveUI; + +namespace BGC.Client.Android +{ + [Activity( + Label = "BGC.Client.Android", + Theme = "@style/MyTheme.NoActionBar", + Icon = "@drawable/icon", + MainLauncher = true, + ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)] + public class MainActivity : AvaloniaMainActivity + { + protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .WithInterFont() + .UseReactiveUI(); + } + } +} diff --git a/BGC.Client/BGC.Client.Android/Resources/AboutResources.txt b/BGC.Client/BGC.Client.Android/Resources/AboutResources.txt new file mode 100644 index 0000000..c2bca97 --- /dev/null +++ b/BGC.Client/BGC.Client.Android/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. \ No newline at end of file diff --git a/BGC.Client/BGC.Client.Android/Resources/drawable-night-v31/avalonia_anim.xml b/BGC.Client/BGC.Client.Android/Resources/drawable-night-v31/avalonia_anim.xml new file mode 100644 index 0000000..dde4b5a --- /dev/null +++ b/BGC.Client/BGC.Client.Android/Resources/drawable-night-v31/avalonia_anim.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.Android/Resources/drawable-v31/avalonia_anim.xml b/BGC.Client/BGC.Client.Android/Resources/drawable-v31/avalonia_anim.xml new file mode 100644 index 0000000..94f27d9 --- /dev/null +++ b/BGC.Client/BGC.Client.Android/Resources/drawable-v31/avalonia_anim.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.Android/Resources/drawable/splash_screen.xml b/BGC.Client/BGC.Client.Android/Resources/drawable/splash_screen.xml new file mode 100644 index 0000000..2e920b4 --- /dev/null +++ b/BGC.Client/BGC.Client.Android/Resources/drawable/splash_screen.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.Android/Resources/values-night/colors.xml b/BGC.Client/BGC.Client.Android/Resources/values-night/colors.xml new file mode 100644 index 0000000..3d47b6f --- /dev/null +++ b/BGC.Client/BGC.Client.Android/Resources/values-night/colors.xml @@ -0,0 +1,4 @@ + + + #212121 + diff --git a/BGC.Client/BGC.Client.Android/Resources/values-v31/styles.xml b/BGC.Client/BGC.Client.Android/Resources/values-v31/styles.xml new file mode 100644 index 0000000..d5ecec4 --- /dev/null +++ b/BGC.Client/BGC.Client.Android/Resources/values-v31/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/BGC.Client/BGC.Client.Android/Resources/values/colors.xml b/BGC.Client/BGC.Client.Android/Resources/values/colors.xml new file mode 100644 index 0000000..59279d5 --- /dev/null +++ b/BGC.Client/BGC.Client.Android/Resources/values/colors.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + diff --git a/BGC.Client/BGC.Client.Android/Resources/values/styles.xml b/BGC.Client/BGC.Client.Android/Resources/values/styles.xml new file mode 100644 index 0000000..6e534de --- /dev/null +++ b/BGC.Client/BGC.Client.Android/Resources/values/styles.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/BGC.Client/BGC.Client.Browser/AppBundle/Logo.svg b/BGC.Client/BGC.Client.Browser/AppBundle/Logo.svg new file mode 100644 index 0000000..9685a23 --- /dev/null +++ b/BGC.Client/BGC.Client.Browser/AppBundle/Logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/BGC.Client/BGC.Client.Browser/AppBundle/app.css b/BGC.Client/BGC.Client.Browser/AppBundle/app.css new file mode 100644 index 0000000..a424538 --- /dev/null +++ b/BGC.Client/BGC.Client.Browser/AppBundle/app.css @@ -0,0 +1,74 @@ +:root { + --sat: env(safe-area-inset-top); + --sar: env(safe-area-inset-right); + --sab: env(safe-area-inset-bottom); + --sal: env(safe-area-inset-left); +} + +/* HTML styles for the splash screen */ + +.highlight { + color: white; + font-size: 2.5rem; + display: block; +} + +.purple { + color: #8b44ac; +} + +.icon { + opacity: 0.05; + height: 35%; + width: 35%; + position: absolute; + background-repeat: no-repeat; + right: 0px; + bottom: 0px; + margin-right: 3%; + margin-bottom: 5%; + z-index: 5000; + background-position: right bottom; + pointer-events: none; +} + +#avalonia-splash a { + color: whitesmoke; + text-decoration: none; +} + +.center { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +#avalonia-splash { + position: relative; + height: 100%; + width: 100%; + color: whitesmoke; + background: #1b2a4e; + font-family: 'Nunito', sans-serif; + background-position: center; + background-size: cover; + background-repeat: no-repeat; + justify-content: center; + align-items: center; +} + +.splash-close { + animation: fadeout 0.25s linear forwards; +} + +@keyframes fadeout { + 0% { + opacity: 100%; + } + + 100% { + opacity: 0; + visibility: collapse; + } +} diff --git a/BGC.Client/BGC.Client.Browser/AppBundle/favicon.ico b/BGC.Client/BGC.Client.Browser/AppBundle/favicon.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/BGC.Client/BGC.Client.Browser/AppBundle/favicon.ico differ diff --git a/BGC.Client/BGC.Client.Browser/AppBundle/index.html b/BGC.Client/BGC.Client.Browser/AppBundle/index.html new file mode 100644 index 0000000..ecf7fe5 --- /dev/null +++ b/BGC.Client/BGC.Client.Browser/AppBundle/index.html @@ -0,0 +1,30 @@ + + + + + BGC.Client.Browser + + + + + + + + + + +
+
+
+

+ Powered by + Avalonia UI +

+
+ Avalonia Logo +
+
+ + + + \ No newline at end of file diff --git a/BGC.Client/BGC.Client.Browser/AppBundle/main.js b/BGC.Client/BGC.Client.Browser/AppBundle/main.js new file mode 100644 index 0000000..3df7852 --- /dev/null +++ b/BGC.Client/BGC.Client.Browser/AppBundle/main.js @@ -0,0 +1,13 @@ +import { dotnet } from './dotnet.js' + +const is_browser = typeof window != "undefined"; +if (!is_browser) throw new Error(`Expected to be running in a browser`); + +const dotnetRuntime = await dotnet + .withDiagnosticTracing(false) + .withApplicationArgumentsFromQuery() + .create(); + +const config = dotnetRuntime.getConfig(); + +await dotnetRuntime.runMain(config.mainAssemblyName, [window.location.search]); diff --git a/BGC.Client/BGC.Client.Browser/BGC.Client.Browser.csproj b/BGC.Client/BGC.Client.Browser/BGC.Client.Browser.csproj new file mode 100644 index 0000000..8e98af3 --- /dev/null +++ b/BGC.Client/BGC.Client.Browser/BGC.Client.Browser.csproj @@ -0,0 +1,20 @@ + + + net8.0 + browser-wasm + AppBundle\main.js + Exe + + + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.Browser/Dockerfile b/BGC.Client/BGC.Client.Browser/Dockerfile new file mode 100644 index 0000000..e3396de --- /dev/null +++ b/BGC.Client/BGC.Client.Browser/Dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER app +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +RUN dotnet workload install wasm-tools +RUN apt-get update +RUN apt-get install -y python3 + +COPY . . + +RUN dotnet restore "./BGC.Client/BGC.Client.Browser/BGC.Client.Browser.csproj" + +WORKDIR "/src/BGC.Client/BGC.Client.Browser" + +RUN dotnet build "./BGC.Client.Browser.csproj" -c Release -o /app/build + +FROM nginx +COPY --from=build /app/build/AppBundle /usr/share/nginx/html/ \ No newline at end of file diff --git a/BGC.Client/BGC.Client.Browser/Program.cs b/BGC.Client/BGC.Client.Browser/Program.cs new file mode 100644 index 0000000..0906d15 --- /dev/null +++ b/BGC.Client/BGC.Client.Browser/Program.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Browser; +using Avalonia.ReactiveUI; +using BGC.Client; +using System.Runtime.Versioning; +using System.Threading.Tasks; + +[assembly: SupportedOSPlatform("browser")] + +internal sealed partial class Program +{ + private static Task Main(string[] args) => BuildAvaloniaApp() + .WithInterFont() + .UseReactiveUI() + .StartBrowserAppAsync("out"); + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure(); +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client.Browser/runtimeconfig.template.json b/BGC.Client/BGC.Client.Browser/runtimeconfig.template.json new file mode 100644 index 0000000..c6990ba --- /dev/null +++ b/BGC.Client/BGC.Client.Browser/runtimeconfig.template.json @@ -0,0 +1,11 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "html-path": "index.html", + "Host": "browser" + } + ] + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client.Desktop/BGC.Client.Desktop.csproj b/BGC.Client/BGC.Client.Desktop/BGC.Client.Desktop.csproj new file mode 100644 index 0000000..4eb7a08 --- /dev/null +++ b/BGC.Client/BGC.Client.Desktop/BGC.Client.Desktop.csproj @@ -0,0 +1,24 @@ + + + WinExe + + net8.0 + enable + true + + + + app.manifest + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.Desktop/Program.cs b/BGC.Client/BGC.Client.Desktop/Program.cs new file mode 100644 index 0000000..6ae0b60 --- /dev/null +++ b/BGC.Client/BGC.Client.Desktop/Program.cs @@ -0,0 +1,24 @@ +using Avalonia; +using Avalonia.ReactiveUI; +using System; + +namespace BGC.Client.Desktop +{ + internal sealed class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI(); + } +} diff --git a/BGC.Client/BGC.Client.Desktop/app.manifest b/BGC.Client/BGC.Client.Desktop/app.manifest new file mode 100644 index 0000000..eb983d3 --- /dev/null +++ b/BGC.Client/BGC.Client.Desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.iOS/AppDelegate.cs b/BGC.Client/BGC.Client.iOS/AppDelegate.cs new file mode 100644 index 0000000..5ac0861 --- /dev/null +++ b/BGC.Client/BGC.Client.iOS/AppDelegate.cs @@ -0,0 +1,23 @@ +using Avalonia; +using Avalonia.iOS; +using Avalonia.ReactiveUI; +using Foundation; + +namespace BGC.Client.iOS +{ + // The UIApplicationDelegate for the application. This class is responsible for launching the + // User Interface of the application, as well as listening (and optionally responding) to + // application events from iOS. + [Register("AppDelegate")] +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix + public partial class AppDelegate : AvaloniaAppDelegate +#pragma warning restore CA1711 // Identifiers should not have incorrect suffix + { + protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .WithInterFont() + .UseReactiveUI(); + } + } +} diff --git a/BGC.Client/BGC.Client.iOS/BGC.Client.iOS.csproj b/BGC.Client/BGC.Client.iOS/BGC.Client.iOS.csproj new file mode 100644 index 0000000..51c4b9d --- /dev/null +++ b/BGC.Client/BGC.Client.iOS/BGC.Client.iOS.csproj @@ -0,0 +1,16 @@ + + + Exe + net8.0-ios + 13.0 + enable + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.iOS/Entitlements.plist b/BGC.Client/BGC.Client.iOS/Entitlements.plist new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/BGC.Client/BGC.Client.iOS/Entitlements.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/BGC.Client/BGC.Client.iOS/Info.plist b/BGC.Client/BGC.Client.iOS/Info.plist new file mode 100644 index 0000000..986424b --- /dev/null +++ b/BGC.Client/BGC.Client.iOS/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDisplayName + BGC.Client + CFBundleIdentifier + companyName.BGC.Client + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 13.0 + UIDeviceFamily + + 1 + 2 + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/BGC.Client/BGC.Client.iOS/Main.cs b/BGC.Client/BGC.Client.iOS/Main.cs new file mode 100644 index 0000000..e839d91 --- /dev/null +++ b/BGC.Client/BGC.Client.iOS/Main.cs @@ -0,0 +1,15 @@ +using UIKit; + +namespace BGC.Client.iOS +{ + public class Application + { + // This is the main entry point of the application. + private static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/BGC.Client/BGC.Client.iOS/Resources/LaunchScreen.xib b/BGC.Client/BGC.Client.iOS/Resources/LaunchScreen.xib new file mode 100644 index 0000000..5cc2ed0 --- /dev/null +++ b/BGC.Client/BGC.Client.iOS/Resources/LaunchScreen.xib @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client.sln b/BGC.Client/BGC.Client.sln new file mode 100644 index 0000000..086a952 --- /dev/null +++ b/BGC.Client/BGC.Client.sln @@ -0,0 +1,54 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32811.315 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.Client", "BGC.Client\BGC.Client.csproj", "{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.Client.Desktop", "BGC.Client.Desktop\BGC.Client.Desktop.csproj", "{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.Client.Browser", "BGC.Client.Browser\BGC.Client.Browser.csproj", "{1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.Client.iOS", "BGC.Client.iOS\BGC.Client.iOS.csproj", "{EBD9022F-BC83-4846-9A11-6F7F3772DC64}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.Client.Android", "BGC.Client.Android\BGC.Client.Android.csproj", "{7AD1DAC8-7FBE-49D5-8614-7321233DB82E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3DA99C4E-89E3-4049-9C22-0A7EC60D83D8}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|Any CPU.Build.0 = Release|Any CPU + {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|Any CPU.Build.0 = Release|Any CPU + {1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}.Release|Any CPU.Build.0 = Release|Any CPU + {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Release|Any CPU.Build.0 = Release|Any CPU + {7AD1DAC8-7FBE-49D5-8614-7321233DB82E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AD1DAC8-7FBE-49D5-8614-7321233DB82E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AD1DAC8-7FBE-49D5-8614-7321233DB82E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AD1DAC8-7FBE-49D5-8614-7321233DB82E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {83CB65B8-011F-4ED7-BCD3-A6CFA935EF7E} + EndGlobalSection +EndGlobal diff --git a/BGC.Client/BGC.Client/App.axaml b/BGC.Client/BGC.Client/App.axaml new file mode 100644 index 0000000..dd4060d --- /dev/null +++ b/BGC.Client/BGC.Client/App.axaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/BGC.Client/BGC.Client/App.axaml.cs b/BGC.Client/BGC.Client/App.axaml.cs new file mode 100644 index 0000000..9dc28c7 --- /dev/null +++ b/BGC.Client/BGC.Client/App.axaml.cs @@ -0,0 +1,36 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using BGC.Client.ViewModels; +using BGC.Client.Views; + +namespace BGC.Client +{ + public partial class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow + { + DataContext = new MainViewModel() + }; + } + else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) + { + singleViewPlatform.MainView = new MainView + { + DataContext = new MainViewModel() + }; + } + + base.OnFrameworkInitializationCompleted(); + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/Assets/avalonia-logo.ico b/BGC.Client/BGC.Client/Assets/avalonia-logo.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/BGC.Client/BGC.Client/Assets/avalonia-logo.ico differ diff --git a/BGC.Client/BGC.Client/BGC.Client.csproj b/BGC.Client/BGC.Client/BGC.Client.csproj new file mode 100644 index 0000000..e9a5983 --- /dev/null +++ b/BGC.Client/BGC.Client/BGC.Client.csproj @@ -0,0 +1,46 @@ + + + net8.0 + enable + latest + true + + + + + + + + + + + + + + + + + + + GameBrowsePage2View.axaml + + + GameBrowsePage1View.axaml + + + GameBrowserView.axaml + + + GamesTableView.axaml + + + LoginView.axaml + + + + + + + + + diff --git a/BGC.Client/BGC.Client/ViewLocator.cs b/BGC.Client/BGC.Client/ViewLocator.cs new file mode 100644 index 0000000..3780060 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewLocator.cs @@ -0,0 +1,33 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using BGC.Client.ViewModels; +using System; + +namespace BGC.Client +{ + public class ViewLocator : IDataTemplate + { + public Control? Build(object? data) + { + if (data is null) + { + return null; + } + + var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type)!; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/ViewModels/Common/GameForTable.cs b/BGC.Client/BGC.Client/ViewModels/Common/GameForTable.cs new file mode 100644 index 0000000..8aed76f --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/Common/GameForTable.cs @@ -0,0 +1,28 @@ +using BGC.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BGC.Client.ViewModels.Common +{ + public class GameForTable + { + public string Name => NameRu ?? NameEng; + public string Age => DataConverter.GetAge(AgeMin, AgeMax); + public string Players => DataConverter.GetPlayers(PlayersMin, PlayersMax); + public string Duration => DataConverter.GetDuration(GameDurationMin, GameDurationMax); + + + public long Id { get; init; } + public string NameRu { get; init; } = string.Empty; + public string NameEng { get; init; } = string.Empty; + public int PlayersMin { get; init; } = 1; + public int? PlayersMax { get; init; } + public int AgeMin { get; init; } = 1; + public int? AgeMax { get; init; } + public TimeSpan? GameDurationMin { get; init; } + public TimeSpan? GameDurationMax { get; init; } + } +} diff --git a/BGC.Client/BGC.Client/ViewModels/Common/GameState.cs b/BGC.Client/BGC.Client/ViewModels/Common/GameState.cs new file mode 100644 index 0000000..12c3c32 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/Common/GameState.cs @@ -0,0 +1,18 @@ +using System; + +namespace BGC.Client.ViewModels.Common +{ + public class GameState + { + public long Id { get; set; } + public string NameRu { get; init; } = string.Empty; + public string NameEng { get; init; } = string.Empty; + private DateTime? IssueDate { get; init; } + public int PlayersMin { get; init; } = 1; + public int? PlayersMax { get; init; } + public int AgeMin { get; init; } = 1; + public int? AgeMax { get; init; } + public TimeSpan? GameDurationMin { get; init; } + public TimeSpan? GameDurationMax { get; init; } + } +} diff --git a/BGC.Client/BGC.Client/ViewModels/Common/PointerEventLog.cs b/BGC.Client/BGC.Client/ViewModels/Common/PointerEventLog.cs new file mode 100644 index 0000000..6e28230 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/Common/PointerEventLog.cs @@ -0,0 +1,12 @@ +using System; + +namespace BGC.Client.ViewModels.Common +{ + internal class PointerEventLog + { + public DateTime Timestamp { get; init; } = DateTime.UtcNow; + public PointerEventType EventType { get; init; } + public double X { get; init; } + public double Y { get; init; } + } +} diff --git a/BGC.Client/BGC.Client/ViewModels/Common/PointerEventType.cs b/BGC.Client/BGC.Client/ViewModels/Common/PointerEventType.cs new file mode 100644 index 0000000..5d8797a --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/Common/PointerEventType.cs @@ -0,0 +1,10 @@ +namespace BGC.Client.ViewModels.Common +{ + internal enum PointerEventType + { + Unknown = 0, + Move = 1, + Press = 2, + Release = 4, + } +} diff --git a/BGC.Client/BGC.Client/ViewModels/GameBrowsePage1ViewModel.cs b/BGC.Client/BGC.Client/ViewModels/GameBrowsePage1ViewModel.cs new file mode 100644 index 0000000..0ddf972 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/GameBrowsePage1ViewModel.cs @@ -0,0 +1,40 @@ +using BGC.Client.ViewModels.Common; +using ReactiveUI; +using System.Windows.Input; + +namespace BGC.Client.ViewModels +{ + public class GameBrowsePage1ViewModel : GameBrowsePageBaseViewModel + { + public string? Title1 => GameState?.NameRu; + + internal override string[] Fields { get; set; } = { "Title1" }; + + + + + public ICommand Command { get; } + public GameBrowsePage1ViewModel() + { + UpdateState(new GameState() + { + NameRu = " ", + NameEng = "Game of thrones", + Id = 2, + }); + Command = ReactiveCommand.Create(Act); + } + + private void Act() + { + var old = GameState; + var st = new GameState() + { + Id = old?.Id ?? 1, + NameRu = old?.NameRu + "_", + NameEng = old?.NameEng ?? string.Empty, + }; + UpdateState(st); + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/ViewModels/GameBrowsePage2ViewModel.cs b/BGC.Client/BGC.Client/ViewModels/GameBrowsePage2ViewModel.cs new file mode 100644 index 0000000..1ea7057 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/GameBrowsePage2ViewModel.cs @@ -0,0 +1,9 @@ +namespace BGC.Client.ViewModels +{ + public class GameBrowsePage2ViewModel : GameBrowsePage1ViewModel + { + public string? Title2 => GameState?.NameEng; + + internal override string[] Fields { get; set; } = { "Title2" }; + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/ViewModels/GameBrowsePageBaseViewModel.cs b/BGC.Client/BGC.Client/ViewModels/GameBrowsePageBaseViewModel.cs new file mode 100644 index 0000000..3159859 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/GameBrowsePageBaseViewModel.cs @@ -0,0 +1,22 @@ +using BGC.Client.ViewModels.Common; +using ReactiveUI; +using System; + +namespace BGC.Client.ViewModels +{ + public class GameBrowsePageBaseViewModel : ViewModelBase + { + public GameState? GameState { get; private set; } + + public void UpdateState(GameState gameState) + { + GameState = gameState; + foreach (var field in Fields) + { + this.RaisePropertyChanged(field); + } + } + + internal virtual string[] Fields { get; set; } = Array.Empty(); + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/ViewModels/GameBrowserViewModel.cs b/BGC.Client/BGC.Client/ViewModels/GameBrowserViewModel.cs new file mode 100644 index 0000000..3650fc1 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/GameBrowserViewModel.cs @@ -0,0 +1,110 @@ +using BGC.Client.ViewModels.Common; +using ReactiveUI; +using System; +using System.Windows.Input; + +namespace BGC.Client.ViewModels +{ + public class GameBrowserViewModel : ViewModelBase + { + #region static pointer dispatching + + private readonly static object _lock = new(); + private static PointerEventLog? _pressLog = null; + internal static PointerEventLog? PressLog + { + get + { + lock (_lock) + { + return _pressLog; + } + } + set + { + lock (_lock) + { + _pressLog = value; + } + } + } + + private static Action? SetPage1Action; + private static Action? SetPage2Action; + + internal static void SetEventLog(PointerEventLog pointerEventLog) + { + if (pointerEventLog.EventType == PointerEventType.Press) + { + PressLog = pointerEventLog; + } + else if (pointerEventLog.EventType == PointerEventType.Release && PressLog != null) + { + var dx = pointerEventLog.X - PressLog.X; + var dt = pointerEventLog.Timestamp - PressLog.Timestamp; + if (dt > TimeSpan.FromSeconds(0.1)) + { + try + { + if (dx > 30) + { + // + SetPage1Action?.Invoke(); + } + if (dx < -30) + { + // + SetPage2Action?.Invoke(); + } + } + catch (Exception ex) + { + + } + } + } + } + #endregion + + + private GameBrowsePageBaseViewModel _currentPage; + public ICommand SetPage1Command { get; } + public ICommand SetPage2Command { get; } + + private readonly GameBrowsePageBaseViewModel[] _pages = new GameBrowsePageBaseViewModel[2]; + + public GameBrowserViewModel() + { + _pages[0] = new GameBrowsePage1ViewModel(); + _pages[1] = new GameBrowsePage2ViewModel(); ; + + _currentPage = _pages[0]; + SetPage1Command = ReactiveCommand.Create(SetPage1); + SetPage2Command = ReactiveCommand.Create(SetPage2); + SetPage1Action += SetPage1; + SetPage2Action += SetPage2; + } + + public GameBrowsePageBaseViewModel CurrentPage + { + get { return _currentPage; } + private set { this.RaiseAndSetIfChanged(ref _currentPage, value); } + } + + private void SetPage1() + { + if (CurrentPage != _pages[0]) + { + CurrentPage = _pages[0]; + } + } + + private void SetPage2() + { + if (CurrentPage != _pages[1]) + { + CurrentPage = _pages[1]; + } + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/ViewModels/GamesTableViewModel.cs b/BGC.Client/BGC.Client/ViewModels/GamesTableViewModel.cs new file mode 100644 index 0000000..a849d4a --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/GamesTableViewModel.cs @@ -0,0 +1,25 @@ +using BGC.Client.ViewModels.Common; +using ReactiveUI; +using System.Collections.ObjectModel; +using System; +using Avalonia.Controls; + +namespace BGC.Client.ViewModels +{ + public class GamesTableViewModel : ViewModelBase + { + public ObservableCollection Games { get;} + + public GamesTableViewModel() + { + + var gameForTables = new GameForTable[] + { + new GameForTable(){NameRu = "Game1", Id=1}, + new GameForTable(){NameRu = "Game2", Id=2}, + new GameForTable(){NameRu = "Game3", Id=3} + }; + Games = new ObservableCollection(gameForTables); + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/ViewModels/LoginViewModel.cs b/BGC.Client/BGC.Client/ViewModels/LoginViewModel.cs new file mode 100644 index 0000000..9707aa2 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/LoginViewModel.cs @@ -0,0 +1,9 @@ +using System.Windows.Input; + +namespace BGC.Client.ViewModels +{ + public class LoginViewModel : ViewModelBase + { + public ICommand LoginCommand { get; init; } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/ViewModels/MainViewModel.cs b/BGC.Client/BGC.Client/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..a7d5e61 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/MainViewModel.cs @@ -0,0 +1,40 @@ +using ReactiveUI; +using System.Windows.Input; + +namespace BGC.Client.ViewModels +{ + public class MainViewModel : ViewModelBase + { + private ViewModelBase _currentView; + public ICommand LoginCommand { get; } + + private readonly ViewModelBase[] _views = new ViewModelBase[3]; + + public MainViewModel() + { + LoginCommand = ReactiveCommand.Create(Login); + var login = new LoginViewModel() + { + LoginCommand = LoginCommand, + }; + _views[1] = login; + _views[0] = new GamesTableViewModel(); + _views[2] = new GameBrowserViewModel(); + _currentView = _views[0]; + } + + public ViewModelBase CurrentScreen + { + get { return _currentView; } + private set { this.RaiseAndSetIfChanged(ref _currentView, value); } + } + + private void Login() + { + if (CurrentScreen != _views[1]) + { + CurrentScreen = _views[1]; + } + } + } +} diff --git a/BGC.Client/BGC.Client/ViewModels/ViewModelBase.cs b/BGC.Client/BGC.Client/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..75a82e3 --- /dev/null +++ b/BGC.Client/BGC.Client/ViewModels/ViewModelBase.cs @@ -0,0 +1,8 @@ +using ReactiveUI; + +namespace BGC.Client.ViewModels +{ + public class ViewModelBase : ReactiveObject + { + } +} diff --git a/BGC.Client/BGC.Client/Views/GameBrowsePage1View.axaml b/BGC.Client/BGC.Client/Views/GameBrowsePage1View.axaml new file mode 100644 index 0000000..d711cde --- /dev/null +++ b/BGC.Client/BGC.Client/Views/GameBrowsePage1View.axaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client/Views/GameBrowsePage1View.axaml.cs b/BGC.Client/BGC.Client/Views/GameBrowsePage1View.axaml.cs new file mode 100644 index 0000000..710094c --- /dev/null +++ b/BGC.Client/BGC.Client/Views/GameBrowsePage1View.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace BGC.Client.Views +{ + public partial class GameBrowsePage1View : UserControl + { + public GameBrowsePage1View() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/Views/GameBrowsePage2View.axaml b/BGC.Client/BGC.Client/Views/GameBrowsePage2View.axaml new file mode 100644 index 0000000..c9fc49c --- /dev/null +++ b/BGC.Client/BGC.Client/Views/GameBrowsePage2View.axaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client/Views/GameBrowsePage2View.axaml.cs b/BGC.Client/BGC.Client/Views/GameBrowsePage2View.axaml.cs new file mode 100644 index 0000000..ed4731c --- /dev/null +++ b/BGC.Client/BGC.Client/Views/GameBrowsePage2View.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace BGC.Client.Views +{ + public partial class GameBrowsePage2View : UserControl + { + public GameBrowsePage2View() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/Views/GameBrowserView.axaml b/BGC.Client/BGC.Client/Views/GameBrowserView.axaml new file mode 100644 index 0000000..0543054 --- /dev/null +++ b/BGC.Client/BGC.Client/Views/GameBrowserView.axaml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/BGC.Client/BGC.Client/Views/GameBrowserView.axaml.cs b/BGC.Client/BGC.Client/Views/GameBrowserView.axaml.cs new file mode 100644 index 0000000..82455ba --- /dev/null +++ b/BGC.Client/BGC.Client/Views/GameBrowserView.axaml.cs @@ -0,0 +1,39 @@ +using Avalonia.Controls; +using Avalonia.Input; +using BGC.Client.ViewModels; + +namespace BGC.Client.Views +{ + public partial class GameBrowserView : UserControl + { + public GameBrowserView() + { + InitializeComponent(); + + this.PointerPressed += OnPointerPressed; + this.PointerReleased += OnPointerReleased; + } + + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + var p = e.GetPosition(null); + GameBrowserViewModel.SetEventLog(new ViewModels.Common.PointerEventLog() + { + EventType = ViewModels.Common.PointerEventType.Press, + X = p.X, + Y = p.Y, + }); + } + + private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + var p = e.GetPosition(null); + GameBrowserViewModel.SetEventLog(new ViewModels.Common.PointerEventLog() + { + EventType = ViewModels.Common.PointerEventType.Release, + X = p.X, + Y = p.Y, + }); + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/Views/GamesTableView.axaml b/BGC.Client/BGC.Client/Views/GamesTableView.axaml new file mode 100644 index 0000000..9ba170c --- /dev/null +++ b/BGC.Client/BGC.Client/Views/GamesTableView.axaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/BGC.Client/BGC.Client/Views/GamesTableView.axaml.cs b/BGC.Client/BGC.Client/Views/GamesTableView.axaml.cs new file mode 100644 index 0000000..a8565e3 --- /dev/null +++ b/BGC.Client/BGC.Client/Views/GamesTableView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; + +namespace BGC.Client.Views +{ + public partial class GamesTableView : UserControl + { + public GamesTableView() + { + InitializeComponent(); + } + + public void DataGridCellPointerPressed(object sender, DataGridCellPointerPressedEventArgs args) + { + + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/Views/LoginView.axaml b/BGC.Client/BGC.Client/Views/LoginView.axaml new file mode 100644 index 0000000..1a404b6 --- /dev/null +++ b/BGC.Client/BGC.Client/Views/LoginView.axaml @@ -0,0 +1,13 @@ + + + + + + diff --git a/BGC.Client/BGC.Client/Views/LoginView.axaml.cs b/BGC.Client/BGC.Client/Views/LoginView.axaml.cs new file mode 100644 index 0000000..f02a124 --- /dev/null +++ b/BGC.Client/BGC.Client/Views/LoginView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace BGC.Client.Views +{ + public partial class LoginView : UserControl + { + public LoginView() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/Views/MainView.axaml b/BGC.Client/BGC.Client/Views/MainView.axaml new file mode 100644 index 0000000..263c4b9 --- /dev/null +++ b/BGC.Client/BGC.Client/Views/MainView.axaml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/BGC.Client/BGC.Client/Views/MainView.axaml.cs b/BGC.Client/BGC.Client/Views/MainView.axaml.cs new file mode 100644 index 0000000..9d5d647 --- /dev/null +++ b/BGC.Client/BGC.Client/Views/MainView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace BGC.Client.Views +{ + public partial class MainView : UserControl + { + public MainView() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/BGC.Client/BGC.Client/Views/MainWindow.axaml b/BGC.Client/BGC.Client/Views/MainWindow.axaml new file mode 100644 index 0000000..f1f79f9 --- /dev/null +++ b/BGC.Client/BGC.Client/Views/MainWindow.axaml @@ -0,0 +1,12 @@ + + + diff --git a/BGC.Client/BGC.Client/Views/MainWindow.axaml.cs b/BGC.Client/BGC.Client/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..2306a8a --- /dev/null +++ b/BGC.Client/BGC.Client/Views/MainWindow.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace BGC.Client.Views +{ + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/BGC.Client/Directory.Build.props b/BGC.Client/Directory.Build.props new file mode 100644 index 0000000..b7d1f09 --- /dev/null +++ b/BGC.Client/Directory.Build.props @@ -0,0 +1,6 @@ + + + enable + 11.0.6 + + diff --git a/BGC.Common/BGC.Common.csproj b/BGC.Common/BGC.Common.csproj new file mode 100644 index 0000000..b007a4d --- /dev/null +++ b/BGC.Common/BGC.Common.csproj @@ -0,0 +1,8 @@ + + + + net8.0 + enable + enable + + diff --git a/BGC.Common/Catalog/CatalogRepositoryHttp.cs b/BGC.Common/Catalog/CatalogRepositoryHttp.cs new file mode 100644 index 0000000..b0aedaa --- /dev/null +++ b/BGC.Common/Catalog/CatalogRepositoryHttp.cs @@ -0,0 +1,90 @@ +using BGC.Common.Catalog; +using BGC.Common.Catalog.Models; +using System.Text; +using System.Text.Json; + +namespace BGC.Client.Services +{ + public class CatalogRepositoryHttp : ICatalogRepository + { + private readonly HttpClient _httpClient; + private readonly JsonSerializerOptions jsonSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + public CatalogRepositoryHttp(string serverUrl) + { + _httpClient = new HttpClient//todo настройка неразрывности соединения + { + BaseAddress = new Uri(serverUrl) + }; + } + + public async Task GetDataForSelection() + { + var resp = await _httpClient.GetAsync($"Catalog/GetDataForSelection"); + if (resp.IsSuccessStatusCode) + { + var stringContent = await resp.Content.ReadAsStringAsync(); + var res = System.Text.Json.JsonSerializer.Deserialize(stringContent, jsonSerializerOptions); + if (res != null) + { + return res; + } + } + + return new DataForSelection(); + } + + public async Task GetGameFull(long id) + { + var resp = await _httpClient.GetAsync($"Catalog/GetGameFull?gameId={id}"); + if (resp.IsSuccessStatusCode) + { + var stringContent = await resp.Content.ReadAsStringAsync(); + var res = System.Text.Json.JsonSerializer.Deserialize(stringContent, jsonSerializerOptions); + if (res != null) + { + return res; + } + } + + return new GetFullGameRespone(); + } + + public async Task GetGamesByFilter(GamesFilter filter) + { + var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(filter), Encoding.UTF8, "application/json"); + var resp = await _httpClient.PostAsync($"Catalog/GetGamesByFilter", content); + if (resp.IsSuccessStatusCode) + { + var stringContent = await resp.Content.ReadAsStringAsync(); + var res = System.Text.Json.JsonSerializer.Deserialize(stringContent, jsonSerializerOptions); + if (res != null) + { + return res; + } + } + + return new GetGamesByFilterResponse(); + } + + public async Task UpsertGame(GameUpsertingRequest gameFull) + { + var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(gameFull), Encoding.UTF8, "application/json"); + var resp = await _httpClient.PostAsync($"Catalog/UpsertGame", content); + if (resp.IsSuccessStatusCode) + { + var stringContent = await resp.Content.ReadAsStringAsync(); + var res = System.Text.Json.JsonSerializer.Deserialize(stringContent, jsonSerializerOptions); + if (res != null) + { + return res; + } + } + + return new GetFullGameRespone(); + } + } +} diff --git a/BGC.Common/Catalog/ICatalogRepository.cs b/BGC.Common/Catalog/ICatalogRepository.cs new file mode 100644 index 0000000..9d49431 --- /dev/null +++ b/BGC.Common/Catalog/ICatalogRepository.cs @@ -0,0 +1,12 @@ +using BGC.Common.Catalog.Models; + +namespace BGC.Common.Catalog +{ + public interface ICatalogRepository + { + public Task GetGamesByFilter(GamesFilter filter); + public Task GetGameFull(long ig); + public Task GetDataForSelection(); + public Task UpsertGame(GameUpsertingRequest gameFull); + } +} diff --git a/BGC.Common/Catalog/Models/Author.cs b/BGC.Common/Catalog/Models/Author.cs new file mode 100644 index 0000000..a9bee27 --- /dev/null +++ b/BGC.Common/Catalog/Models/Author.cs @@ -0,0 +1,9 @@ +namespace BGC.Common.Catalog.Models +{ + public class Author + { + public long Id { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + } +} diff --git a/BGC.Common/Catalog/Models/DataForSelection.cs b/BGC.Common/Catalog/Models/DataForSelection.cs new file mode 100644 index 0000000..4e9ec14 --- /dev/null +++ b/BGC.Common/Catalog/Models/DataForSelection.cs @@ -0,0 +1,12 @@ +namespace BGC.Common.Catalog.Models +{ + public class DataForSelection + { + public GameReduced[] Games { get; set; } = Array.Empty(); + public Genre[] Genres { get; set; } = Array.Empty(); + public Theme[] Themes { get; set; } = Array.Empty(); + public Owner[] Owners { get; set; } = Array.Empty(); + public Author[] Authors { get; set; } = Array.Empty(); + public Rating[] Ratings { get; set; } = Array.Empty(); + } +} diff --git a/BGC.Common/Catalog/Models/GameFull.cs b/BGC.Common/Catalog/Models/GameFull.cs new file mode 100644 index 0000000..b9e74e7 --- /dev/null +++ b/BGC.Common/Catalog/Models/GameFull.cs @@ -0,0 +1,11 @@ +namespace BGC.Common.Catalog.Models +{ + public class GameFull : GameReduced + { + public Genre[] Genres { get; set; } = Array.Empty(); + public Theme[] Themes { get; set; } = Array.Empty(); + public Owner[] Owners { get; set; } = Array.Empty(); + public Author[] Authors { get; set; } = Array.Empty(); + public Rating[] Ratings { get; set; } = Array.Empty(); + } +} diff --git a/BGC.Common/Catalog/Models/GameReduced.cs b/BGC.Common/Catalog/Models/GameReduced.cs new file mode 100644 index 0000000..f8a0c41 --- /dev/null +++ b/BGC.Common/Catalog/Models/GameReduced.cs @@ -0,0 +1,16 @@ +namespace BGC.Common.Catalog.Models +{ + public class GameReduced + { + public long Id { get; set; } + public string NameRu { get; set; } = string.Empty; + public string NameEng { get; set; } = string.Empty; + private DateTime? IssueDate { get; set; } + public int PlayersMin { get; set; } = 1; + public int? PlayersMax { get; set; } + public int AgeMin { get; set; } = 1; + public int? AgeMax { get; set; } + public TimeSpan? GameDurationMin { get; set; } + public TimeSpan? GameDurationMax { get; set; } + } +} diff --git a/BGC.Common/Catalog/Models/GameUpsertingRequest.cs b/BGC.Common/Catalog/Models/GameUpsertingRequest.cs new file mode 100644 index 0000000..fc029db --- /dev/null +++ b/BGC.Common/Catalog/Models/GameUpsertingRequest.cs @@ -0,0 +1,11 @@ +namespace BGC.Common.Catalog.Models +{ + public class GameUpsertingRequest : GameReduced + { + public long[] GenresIds { get; set; } = Array.Empty(); + public long[] ThemesIds { get; set; } = Array.Empty(); + public long[] OwnersIds { get; set; } = Array.Empty(); + public long[] AuthorsIds { get; set; } = Array.Empty(); + public Rating[] Ratings { get; set; } = Array.Empty(); + } +} diff --git a/BGC.Common/Catalog/Models/GamesFilter.cs b/BGC.Common/Catalog/Models/GamesFilter.cs new file mode 100644 index 0000000..ccaf04f --- /dev/null +++ b/BGC.Common/Catalog/Models/GamesFilter.cs @@ -0,0 +1,7 @@ +namespace BGC.Common.Catalog.Models +{ + public class GamesFilter + { + public long? OwnerId { get; set; } + } +} diff --git a/BGC.Common/Catalog/Models/Genre.cs b/BGC.Common/Catalog/Models/Genre.cs new file mode 100644 index 0000000..ec4dff1 --- /dev/null +++ b/BGC.Common/Catalog/Models/Genre.cs @@ -0,0 +1,8 @@ +namespace BGC.Common.Catalog.Models +{ + public class Genre + { + public long Id { get; set; } + public string? Name { get; set; } + } +} diff --git a/BGC.Common/Catalog/Models/GetFullGameRespone.cs b/BGC.Common/Catalog/Models/GetFullGameRespone.cs new file mode 100644 index 0000000..e4e0015 --- /dev/null +++ b/BGC.Common/Catalog/Models/GetFullGameRespone.cs @@ -0,0 +1,7 @@ +namespace BGC.Common.Catalog.Models +{ + public class GetFullGameRespone + { + public GameFull? Game { get; set; } + } +} diff --git a/BGC.Common/Catalog/Models/GetGamesByFilterResponse.cs b/BGC.Common/Catalog/Models/GetGamesByFilterResponse.cs new file mode 100644 index 0000000..829f614 --- /dev/null +++ b/BGC.Common/Catalog/Models/GetGamesByFilterResponse.cs @@ -0,0 +1,7 @@ +namespace BGC.Common.Catalog.Models +{ + public class GetGamesByFilterResponse + { + public GameReduced[] Games { get; set; } = Array.Empty(); + } +} diff --git a/BGC.Common/Catalog/Models/Owner.cs b/BGC.Common/Catalog/Models/Owner.cs new file mode 100644 index 0000000..aec36c5 --- /dev/null +++ b/BGC.Common/Catalog/Models/Owner.cs @@ -0,0 +1,7 @@ +namespace BGC.Common.Catalog.Models +{ + public class Owner + { + public long Id { get; set; } + } +} diff --git a/BGC.Common/Catalog/Models/Rating.cs b/BGC.Common/Catalog/Models/Rating.cs new file mode 100644 index 0000000..3014601 --- /dev/null +++ b/BGC.Common/Catalog/Models/Rating.cs @@ -0,0 +1,10 @@ +namespace BGC.Common.Catalog.Models +{ + public class Rating + { + public long Id { get; set; } + public string? Name { get; set; } + public string? Scale { get; set; } + public decimal Value { get; set; } + } +} diff --git a/BGC.Common/Catalog/Models/Theme.cs b/BGC.Common/Catalog/Models/Theme.cs new file mode 100644 index 0000000..0972165 --- /dev/null +++ b/BGC.Common/Catalog/Models/Theme.cs @@ -0,0 +1,8 @@ +namespace BGC.Common.Catalog.Models +{ + public class Theme + { + public long Id { get; set; } + public string? Name { get; set; } + } +} diff --git a/BGC.Common/DataConverter.cs b/BGC.Common/DataConverter.cs new file mode 100644 index 0000000..f1df86c --- /dev/null +++ b/BGC.Common/DataConverter.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Intrinsics.X86; +using System.Text; +using System.Threading.Tasks; + +namespace BGC.Common +{ + public static class DataConverter + { + public static string GetAge(int ageMin, int? ageMax) + { + if (ageMin >= 18) + { + return "18+"; + } + else if (ageMax.HasValue) + { + return ageMin.ToString() + "-" + ageMax.ToString(); + } + else + { + return ageMin.ToString() + "+"; + } + } + + public static string GetPlayers(int minPlayers, int? maxPlayers) + { + return GetAge(minPlayers, maxPlayers); + } + + public static string GetLength(int minPlayers, int? maxPlayers) + { + return GetAge(minPlayers, maxPlayers); + } + + public static string GetDuration(TimeSpan? min, TimeSpan? max) + { + if (!min.HasValue && !max.HasValue) + { + return string.Empty; + } + else if (min.HasValue && !max.HasValue) + { + return "от " + GetTime(min); + } + else if (!min.HasValue && max.HasValue) + { + return "до " + GetTime(max); + } + else + { + return "от " + GetTime(min) + " до " + GetTime(max); + } + } + + private static string GetTime(TimeSpan? timeSpan) + { + if (!timeSpan.HasValue) + { + return string.Empty; + } + else if (timeSpan < TimeSpan.FromMinutes(5)) + { + return "5 мин"; + } + else if (timeSpan >= TimeSpan.FromMinutes(5) && timeSpan < TimeSpan.FromMinutes(60)) + { + return timeSpan.Value.Minutes.ToString() + " мин"; + } + else if (timeSpan.Value.Minutes == 60) + { + return "1 час"; + } + else if (timeSpan >= TimeSpan.FromMinutes(60) && timeSpan < TimeSpan.FromMinutes(120)) + { + var delta = timeSpan.Value - TimeSpan.FromMinutes(60); + return "1 час " + delta.Minutes.ToString() + " мин"; + } + else + { + return "более 2 часов"; + } + + } + } +} diff --git a/BGC.Server/BGC.Server.csproj b/BGC.Server/BGC.Server.csproj new file mode 100644 index 0000000..3e53fcc --- /dev/null +++ b/BGC.Server/BGC.Server.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/BGC.Server/Catalog/CatalogRepository.cs b/BGC.Server/Catalog/CatalogRepository.cs new file mode 100644 index 0000000..49fd87b --- /dev/null +++ b/BGC.Server/Catalog/CatalogRepository.cs @@ -0,0 +1,230 @@ +using BGC.Common.Catalog; +using BGC.Common.Catalog.Models; +using BGC.Server.DataLayer; +using BGC.Server.DataLayer.Entities; +using Microsoft.EntityFrameworkCore; + +namespace BGC.Server.Catalog +{ + public class CatalogRepository : ICatalogRepository + { + private readonly IDbContextFactory _dbContextFactory; + + public CatalogRepository(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + #region public + public async Task GetDataForSelection() + { + using var context = await _dbContextFactory.CreateDbContextAsync(); + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + var authors = await context.Authors.ToArrayAsync(); + var themes = await context.Themes.ToArrayAsync(); + var genres = await context.Genres.ToArrayAsync(); + var ratings = await context.Ratings.ToArrayAsync(); + + return new DataForSelection() + { + Authors = authors.Select(Map).ToArray(), + Themes = themes.Select(Map).ToArray(), + Genres = genres.Select(Map).ToArray(), + Ratings = ratings.Select(Map).ToArray(), + }; + } + + public async Task GetGameFull(long id) + { + using var context = await _dbContextFactory.CreateDbContextAsync(); + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + var g = await context.Games.Where(g => g.Id == id) + .Include(g => g.Themes) + .Include(g => g.Genres) + .Include(g => g.Authors) + .Include(g => g.Owners) + .Include(g => g.GameRatings) + .ThenInclude(gr => gr.RatingType) + .FirstOrDefaultAsync(); + return new GetFullGameRespone() { Game = Map(g) }; + } + + public async Task UpsertGame(GameUpsertingRequest gameSavingRequest) + { + using var context = await _dbContextFactory.CreateDbContextAsync(); + var oldGame = await context.Games.FirstOrDefaultAsync(g => g.Id == gameSavingRequest.Id); + if (oldGame != null) + { + oldGame.DeleteDate = DateTime.UtcNow; + } + + var game = new Game() + { + AgeMax = gameSavingRequest.AgeMax, + AgeMin = gameSavingRequest.AgeMin, + GameDurationMax = gameSavingRequest.GameDurationMax, + GameDurationMin = gameSavingRequest.GameDurationMin, + NameEng = gameSavingRequest.NameEng, + NameRu = gameSavingRequest.NameRu, + PlayersMax = gameSavingRequest.PlayersMax, + PlayersMin = gameSavingRequest.PlayersMin, + }; + + game.Themes = await context.Themes + .Where(t => gameSavingRequest.ThemesIds.Contains(t.Id)) + .ToArrayAsync(); + + game.Authors = await context.Authors + .Where(t => gameSavingRequest.AuthorsIds.Contains(t.Id)) + .ToArrayAsync(); + + game.Owners = await context.Owners + .Where(t => gameSavingRequest.OwnersIds.Contains(t.Id)) + .ToArrayAsync(); + + game.Genres = await context.Genres + .Where(t => gameSavingRequest.GenresIds.Contains(t.Id)) + .ToArrayAsync(); + + var ratings = await context.Ratings + .Where(t => gameSavingRequest.Ratings.Select(r => r.Id).Contains(t.Id)) + .ToArrayAsync(); + + var gameRatings = new GameRating[ratings.Length]; + + for (int i = 0; i < ratings.Length; i++) + { + gameRatings[i] = new GameRating() + { + RatingType = ratings[i], + Game = game, + Rating = gameSavingRequest.Ratings.FirstOrDefault(gr => gr.Id == ratings[i].Id)?.Value ?? 0 + }; + } + + game.GameRatings = gameRatings; + await context.Games.AddAsync(game); + await context.SaveChangesAsync(); + + return new GetFullGameRespone() { Game = Map(game) }; + } + + public async Task GetGamesByFilter(GamesFilter filter) + { + + using var context = await _dbContextFactory.CreateDbContextAsync(); + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + var result = new GetGamesByFilterResponse(); + if (filter.OwnerId.HasValue) + { + var games = await context.GameOwners + .Where(go => go.OwnerId == filter.OwnerId.Value) + .Include(go => go.Game) + .Select(go => go.Game) + .Where(g => g.DeleteDate == null) + .ToArrayAsync(); + result.Games = games.Select(MapToReducedGame).ToArray(); + } + else + { + var games = await context.Games + .Where(g => g.DeleteDate == null) + .ToArrayAsync(); + result.Games = games.Select(MapToReducedGame).ToArray(); + } + + + return result; + } + #endregion + + #region private mappings + private static GameReduced MapToReducedGame(Game game) + { + return new GameReduced() + { + AgeMax = game.AgeMax, + AgeMin = game.AgeMin, + GameDurationMax = game.GameDurationMax, + GameDurationMin = game.GameDurationMin, + Id = game.Id, + NameEng = game.NameEng, + NameRu = game.NameRu, + PlayersMax = game.PlayersMax, + PlayersMin = game.PlayersMin, + }; + } + + + private static GameFull? Map(Game? game) + { + return game == null ? null : new GameFull() + { + AgeMax = game.AgeMax, + AgeMin = game.AgeMin, + GameDurationMax = game.GameDurationMax, + GameDurationMin = game.GameDurationMin, + Id = game.Id, + NameEng = game.NameEng, + NameRu = game.NameRu, + PlayersMax = game.PlayersMax, + PlayersMin = game.PlayersMin, + Authors = game.Authors.Select(Map).ToArray(), + Genres = game.Genres.Select(Map).ToArray(), + Ratings = game.GameRatings.Select(Map).ToArray(), + Themes = game.Themes.Select(Map).ToArray(), + }; + } + + private static BGC.Common.Catalog.Models.Genre Map(BGC.Server.DataLayer.Entities.Genre genre) + { + return new Common.Catalog.Models.Genre() + { + Id = genre.Id, + Name = genre.Name, + }; + } + + private static BGC.Common.Catalog.Models.Theme Map(BGC.Server.DataLayer.Entities.Theme theme) + { + return new Common.Catalog.Models.Theme() + { + Id = theme.Id, + Name = theme.Name, + }; + } + + private static BGC.Common.Catalog.Models.Author Map(BGC.Server.DataLayer.Entities.Author author) + { + return new Common.Catalog.Models.Author() + { + Id = author.Id, + FirstName = author.FirstName, + LastName = author.LastName, + }; + } + + private static BGC.Common.Catalog.Models.Rating Map(BGC.Server.DataLayer.Entities.RatingType ratingType) + { + return new Common.Catalog.Models.Rating() + { + Id = ratingType.Id, + Name = ratingType.Name, + Scale = ratingType.Scale, + }; + } + + private static BGC.Common.Catalog.Models.Rating Map(BGC.Server.DataLayer.Entities.GameRating gameRating) + { + return new Common.Catalog.Models.Rating() + { + Id = gameRating.RatingTypeId, + Name = gameRating.RatingType?.Name, + Value = gameRating.Rating, + }; + } + #endregion + } +} diff --git a/BGC.Server/DataLayer/BgcDbContext.cs b/BGC.Server/DataLayer/BgcDbContext.cs new file mode 100644 index 0000000..bf363a7 --- /dev/null +++ b/BGC.Server/DataLayer/BgcDbContext.cs @@ -0,0 +1,109 @@ +using BGC.Server.DataLayer.Entities; +using Microsoft.EntityFrameworkCore; + +namespace BGC.Server.DataLayer +{ + public class BgcDbContext : DbContext + { + public DbSet Games { get; set; } + public DbSet Genres { get; set; } + public DbSet Authors { get; set; } + public DbSet Owners { get; set; } + public DbSet Ratings { get; set; } + public DbSet Themes { get; set; } + public DbSet GameOwners { get; set; } + public BgcDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => e1.Id); + entity + .HasMany(e => e.Themes) + .WithMany(e => e.Games) + .UsingEntity(); + + entity + .HasMany(e => e.Genres) + .WithMany(e => e.Games) + .UsingEntity(); + entity + .HasMany(e => e.Authors) + .WithMany(e => e.Games) + .UsingEntity(); + entity + .HasMany(e => e.Owners) + .WithMany(e => e.Games) + .UsingEntity(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => new { e1.GameId, e1.OwnerId }); + entity.HasOne(e => e.Game).WithMany(e => e.GameOwners); + entity.HasOne(e => e.Owner).WithMany(e => e.GameOwners); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => new { e1.GameId, e1.AuthorId }); + entity.HasOne(e => e.Game).WithMany(e => e.GameAuthors); + entity.HasOne(e => e.Author).WithMany(e => e.GameAuthors); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => new { e1.GameId, e1.ThemeId }); + entity.HasOne(e => e.Game).WithMany(e => e.GameThemes); + entity.HasOne(e => e.Theme).WithMany(e => e.GameThemes); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => new { e1.GameId, e1.GenreId }); + entity.HasOne(e => e.Game).WithMany(e => e.GameGenres); + entity.HasOne(e => e.Genre).WithMany(e => e.GameGenres); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => new { e1.GameId, e1.RatingTypeId }); + entity.HasOne(e => e.Game).WithMany(e => e.GameRatings); + entity.HasOne(e => e.RatingType).WithMany(e => e.GameRatings); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => e1.Id); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => e1.Id); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => e1.Id); + entity + .HasMany(e => e.Games) + .WithMany(e => e.Owners) + .UsingEntity(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => e1.Id); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e1 => e1.Id); + }); + } + } +} diff --git a/BGC.Server/DataLayer/Entities/Author.cs b/BGC.Server/DataLayer/Entities/Author.cs new file mode 100644 index 0000000..bc12ab4 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/Author.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BGC.Server.DataLayer.Entities +{ + [Table("authors")] + public class Author + { + [Column("id")] + public long Id { get; set; } + + [Column("first_name")] + public string? FirstName { get; set; } + + [Column("last_name")] + public string? LastName { get; set; } + + public ICollection Games { get; set; } = new List(); + public ICollection GameAuthors { get; set; } = new List(); + } +} diff --git a/BGC.Server/DataLayer/Entities/Game.cs b/BGC.Server/DataLayer/Entities/Game.cs new file mode 100644 index 0000000..ff6e1a3 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/Game.cs @@ -0,0 +1,50 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BGC.Server.DataLayer.Entities +{ + [Table("games")] + public class Game + { + [Column("id")] + public long Id { get; set; } + + [Column("name_ru")] + public string NameRu { get; set; } = string.Empty; + + [Column("name_eng")] + public string NameEng { get; set; } = string.Empty; + + [Column("issue_date")] + public DateTime? IssueDate { get; set; } + + [Column("delete_date")] + public DateTime? DeleteDate { get; set; } + + [Column("players_min")] + public int PlayersMin { get; set; } = 1; + + [Column("players_max")] + public int? PlayersMax { get; set; } + + [Column("age_min")] + public int AgeMin { get; set; } = 1; + + [Column("age_max")] + public int? AgeMax { get; set; } + + [Column("game_time_min")] + public TimeSpan? GameDurationMin { get; set; } + + [Column("game_time_max")] + public TimeSpan? GameDurationMax { get; set; } + public virtual ICollection Themes { get; set; } = new List(); + public virtual ICollection GameThemes { get; set; } = new List(); + public virtual ICollection GameRatings { get; set; } = new List(); + public virtual ICollection GameGenres { get; set; } = new List(); + public virtual ICollection Genres { get; set; } = new List(); + public ICollection Authors { get; set; } = new List(); + public ICollection GameAuthors { get; set; } = new List(); + public ICollection Owners { get; set; } = new List(); + public ICollection GameOwners { get; set; } = new List(); + } +} diff --git a/BGC.Server/DataLayer/Entities/GameAuthor.cs b/BGC.Server/DataLayer/Entities/GameAuthor.cs new file mode 100644 index 0000000..9618f17 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/GameAuthor.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace BGC.Server.DataLayer.Entities +{ + [Table("game_authors")] + public class GameAuthor + { + [Column("id_game")] + public long GameId { get; set; } + + [Column("id_author")] + public long AuthorId { get; set; } + public required Game Game { get; set; } + public required Author Author { get; set; } + } +} diff --git a/BGC.Server/DataLayer/Entities/GameGenre.cs b/BGC.Server/DataLayer/Entities/GameGenre.cs new file mode 100644 index 0000000..ff6f7ac --- /dev/null +++ b/BGC.Server/DataLayer/Entities/GameGenre.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace BGC.Server.DataLayer.Entities +{ + [Table("game_genres")] + public class GameGenre + { + [Column("id_game")] + public long GameId { get; set; } + + [Column("id_genre")] + public long GenreId { get; set; } + public required Game Game { get; set; } + public required Genre Genre { get; set; } + } +} diff --git a/BGC.Server/DataLayer/Entities/GameOwner.cs b/BGC.Server/DataLayer/Entities/GameOwner.cs new file mode 100644 index 0000000..b372891 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/GameOwner.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace BGC.Server.DataLayer.Entities +{ + [Table("game_collection")] + public class GameOwner + { + [Column("id_game")] + public long GameId { get; set; } + + [Column("id_owner")] + public long OwnerId { get; set; } + public required Game Game { get; set; } + public required Owner Owner { get; set; } + } +} diff --git a/BGC.Server/DataLayer/Entities/GameRating.cs b/BGC.Server/DataLayer/Entities/GameRating.cs new file mode 100644 index 0000000..1c5fb53 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/GameRating.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BGC.Server.DataLayer.Entities +{ + [Table("game_ratings")] + public class GameRating + { + [Column("id_game")] + public long GameId { get; set; } + + [Column("id_rating_type")] + public long RatingTypeId { get; set; } + + [Column("rating")] + public decimal Rating { get; set; } + public Game? Game { get; set; } + public RatingType? RatingType { get; set; } + } +} diff --git a/BGC.Server/DataLayer/Entities/GameTheme.cs b/BGC.Server/DataLayer/Entities/GameTheme.cs new file mode 100644 index 0000000..d91d561 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/GameTheme.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BGC.Server.DataLayer.Entities +{ + [Table("game_themes")] + public class GameTheme + { + [Column("id_game")] + public long GameId { get; set; } + + [Column("id_theme")] + public long ThemeId { get; set; } + public required Game Game { get; set; } + public required Theme Theme { get; set; } + } +} diff --git a/BGC.Server/DataLayer/Entities/Genre.cs b/BGC.Server/DataLayer/Entities/Genre.cs new file mode 100644 index 0000000..1607552 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/Genre.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BGC.Server.DataLayer.Entities +{ + [Table("genres")] + public class Genre + { + [Column("id")] + public long Id { get; set; } + + [Column("name")] + public required string Name { get; set; } + + public virtual ICollection GameGenres { get; set; } = new List(); + public virtual ICollection Games { get; set; } = new List(); + } +} diff --git a/BGC.Server/DataLayer/Entities/Owner.cs b/BGC.Server/DataLayer/Entities/Owner.cs new file mode 100644 index 0000000..d6ffd74 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/Owner.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BGC.Server.DataLayer.Entities +{ + [Table("owners")] + public class Owner + { + [Column("id")] + public long Id { get; set; } + + [Column("first_name")] + public string? FirstName { get; set; } + + [Column("last_name")] + public string? LastName { get; set; } + public ICollection Games { get; set; } = new List(); + public ICollection GameOwners { get; set; } = new List(); + } +} diff --git a/BGC.Server/DataLayer/Entities/RatingType.cs b/BGC.Server/DataLayer/Entities/RatingType.cs new file mode 100644 index 0000000..6c67635 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/RatingType.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BGC.Server.DataLayer.Entities +{ + [Table("rating_types")] + public class RatingType + { + [Column("id")] + public long Id { get; set; } + + [Column("name")] + public string? Name { get; set; } + + [Column("scale")] + public string? Scale { get; set; } + public virtual ICollection? GameRatings { get; set; } + } +} diff --git a/BGC.Server/DataLayer/Entities/Theme.cs b/BGC.Server/DataLayer/Entities/Theme.cs new file mode 100644 index 0000000..093e4c4 --- /dev/null +++ b/BGC.Server/DataLayer/Entities/Theme.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BGC.Server.DataLayer.Entities +{ + [Table("themes")] + public class Theme + { + [Column("id")] + public long Id { get; set; } + + [Column("name")] + public string? Name { get; set; } + + public virtual ICollection Games { get; set; } = new List(); + public virtual ICollection GameThemes { get; set; } = new List(); + } +} diff --git a/BGC.TestApi/BGC.TestApi.csproj b/BGC.TestApi/BGC.TestApi.csproj new file mode 100644 index 0000000..5db7f0e --- /dev/null +++ b/BGC.TestApi/BGC.TestApi.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + Linux + ..\docker-compose.dcproj + + + + + + + + + + + + diff --git a/BGC.TestApi/Controllers/TestController.cs b/BGC.TestApi/Controllers/TestController.cs new file mode 100644 index 0000000..45b5b46 --- /dev/null +++ b/BGC.TestApi/Controllers/TestController.cs @@ -0,0 +1,41 @@ +using BGC.Common.Catalog; +using BGC.Common.Catalog.Models; +using Microsoft.AspNetCore.Mvc; + +namespace BGC.TestApi.Controllers +{ + [ApiController] + [Route("[controller]/[action]")] + public class TestController : ControllerBase + { + private readonly ICatalogRepository _catalogRepository; + public TestController(ICatalogRepository catalogRepository) + { + _catalogRepository = catalogRepository; + } + + [HttpGet] + public async Task GetDataForSelection() + { + return await _catalogRepository.GetDataForSelection(); + } + + [HttpGet] + public async Task GetGameFull([FromQuery] long gameId) + { + return await _catalogRepository.GetGameFull(gameId); + } + + [HttpPost] + public async Task UpsertGame([FromBody] GameUpsertingRequest game) + { + return await _catalogRepository.UpsertGame(game); + } + + [HttpPost] + public async Task GetGamesByFilter([FromBody] GamesFilter game) + { + return await _catalogRepository.GetGamesByFilter(game); + } + } +} diff --git a/BGC.TestApi/Dockerfile b/BGC.TestApi/Dockerfile new file mode 100644 index 0000000..740cec4 --- /dev/null +++ b/BGC.TestApi/Dockerfile @@ -0,0 +1,26 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Directory.Build.props", "."] +COPY ["BGC.TestApi/BGC.TestApi.csproj", "BGC.TestApi/"] +COPY ["BGC.Common/BGC.Common.csproj", "BGC.Common/"] +RUN dotnet restore "./BGC.TestApi/BGC.TestApi.csproj" +COPY . . +WORKDIR "/src/BGC.TestApi" +RUN dotnet build "./BGC.TestApi.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./BGC.TestApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "BGC.TestApi.dll"] \ No newline at end of file diff --git a/BGC.TestApi/Program.cs b/BGC.TestApi/Program.cs new file mode 100644 index 0000000..142dcda --- /dev/null +++ b/BGC.TestApi/Program.cs @@ -0,0 +1,18 @@ +using BGC.Client.Services; +using BGC.Common.Catalog; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddSingleton(new CatalogRepositoryHttp("http://webapi:8080/Catalog")); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +app.UseSwagger(); +app.UseSwaggerUI(); + +app.MapControllers(); + +app.Run(); diff --git a/BGC.TestApi/appsettings.Development.json b/BGC.TestApi/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/BGC.TestApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/BGC.TestApi/appsettings.json b/BGC.TestApi/appsettings.json new file mode 100644 index 0000000..4d56694 --- /dev/null +++ b/BGC.TestApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/BGC.WebApi/BGC.WebApi.csproj b/BGC.WebApi/BGC.WebApi.csproj new file mode 100644 index 0000000..a99b672 --- /dev/null +++ b/BGC.WebApi/BGC.WebApi.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + Linux + ..\docker-compose.dcproj + + + + + + + + + + + + diff --git a/BGC.WebApi/Controllers/CatalogController.cs b/BGC.WebApi/Controllers/CatalogController.cs new file mode 100644 index 0000000..3e0de70 --- /dev/null +++ b/BGC.WebApi/Controllers/CatalogController.cs @@ -0,0 +1,42 @@ +using BGC.Common.Catalog; +using BGC.Common.Catalog.Models; +using Microsoft.AspNetCore.Mvc; + +namespace BGC.WebApi.Controllers +{ + [Route("[controller]/[action]")] + [ApiController] + public class CatalogController : ControllerBase + { + private readonly ICatalogRepository _catalogRepository; + + public CatalogController(ICatalogRepository catalogRepository) + { + _catalogRepository = catalogRepository; + } + + [HttpGet] + public async Task GetDataForSelection() + { + return await _catalogRepository.GetDataForSelection(); + } + + [HttpGet] + public async Task GetGameFull([FromQuery] long gameId) + { + return await _catalogRepository.GetGameFull(gameId); + } + + [HttpPost] + public async Task UpsertGame([FromBody] GameUpsertingRequest game) + { + return await _catalogRepository.UpsertGame(game); + } + + [HttpPost] + public async Task GetGamesByFilter([FromBody] GamesFilter game) + { + return await _catalogRepository.GetGamesByFilter(game); + } + } +} diff --git a/BGC.WebApi/Controllers/TestController.cs b/BGC.WebApi/Controllers/TestController.cs new file mode 100644 index 0000000..3536399 --- /dev/null +++ b/BGC.WebApi/Controllers/TestController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; + +namespace BGC.WebApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class TestController : ControllerBase + { + [HttpGet] + public async Task Get() + { + await Task.Delay(1); + return ""; + } + } +} diff --git a/BGC.WebApi/Dockerfile b/BGC.WebApi/Dockerfile new file mode 100644 index 0000000..b739ee7 --- /dev/null +++ b/BGC.WebApi/Dockerfile @@ -0,0 +1,26 @@ + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Directory.Build.props", "."] +COPY ["BGC.WebApi/BGC.WebApi.csproj", "BGC.WebApi/"] +COPY ["BGC.Server/BGC.Server.csproj", "BGC.Server/"] +COPY ["BGC.Common/BGC.Common.csproj", "BGC.Common/"] +RUN dotnet restore "./BGC.WebApi/BGC.WebApi.csproj" +COPY . . +WORKDIR "/src/BGC.WebApi" +RUN dotnet build "./BGC.WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./BGC.WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "BGC.WebApi.dll"] \ No newline at end of file diff --git a/BGC.WebApi/Program.cs b/BGC.WebApi/Program.cs new file mode 100644 index 0000000..37aceff --- /dev/null +++ b/BGC.WebApi/Program.cs @@ -0,0 +1,24 @@ +using BGC.Common.Catalog; +using BGC.Server.Catalog; +using BGC.Server.DataLayer; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSingleton(); +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddDbContextFactory(options => +{ + options.UseNpgsql(Environment.GetEnvironmentVariable("PGSQLCNNSTR")); +}); +var app = builder.Build(); + +app.UseSwagger(); +app.UseSwaggerUI(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/BGC.WebApi/appsettings.Development.json b/BGC.WebApi/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/BGC.WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/BGC.WebApi/appsettings.json b/BGC.WebApi/appsettings.json new file mode 100644 index 0000000..4d56694 --- /dev/null +++ b/BGC.WebApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/BoardGamesCollection.sln b/BoardGamesCollection.sln new file mode 100644 index 0000000..afbb399 --- /dev/null +++ b/BoardGamesCollection.sln @@ -0,0 +1,98 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.Common", "BGC.Common\BGC.Common.csproj", "{A1C29F1D-D123-4635-99F8-F7D851C99E9E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.Server", "BGC.Server\BGC.Server.csproj", "{99A88C01-6949-4EB3-9EDC-A56D8B2B098D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.WebApi", "BGC.WebApi\BGC.WebApi.csproj", "{C2A9A963-BDA1-4F9D-B13D-C5277812F6C3}" +EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{1D3DDCD0-A2A5-458A-A933-C8A307ED36AA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Элементы решения", "Элементы решения", "{2D3ABB7D-384D-44E5-87B7-C740270DBC92}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + init.sql = init.sql + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BGC.TestApi", "BGC.TestApi\BGC.TestApi.csproj", "{E736C652-75A2-4582-AD39-91AB6DED08B0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BGC.Client", "BGC.Client", "{66BF81EC-7830-431B-966A-8000BCB13DAF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BGC.Client", "BGC.Client\BGC.Client\BGC.Client.csproj", "{8F9C9834-7CE8-46A8-9FB5-7B92DC089BC4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BGC.Client.Android", "BGC.Client\BGC.Client.Android\BGC.Client.Android.csproj", "{462BB4A6-B856-40B6-BEF6-06B89B240B29}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BGC.Client.Desktop", "BGC.Client\BGC.Client.Desktop\BGC.Client.Desktop.csproj", "{436BE515-4CB9-4CD3-991D-226FE07727A8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BGC.Client.iOS", "BGC.Client\BGC.Client.iOS\BGC.Client.iOS.csproj", "{FCAEAF0C-4FFD-4B77-B4E7-C127D751D28F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BGC.Client.Browser", "BGC.Client\BGC.Client.Browser\BGC.Client.Browser.csproj", "{AE5E2930-B5D8-4032-81D5-3D5A1ECCEDFA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1C29F1D-D123-4635-99F8-F7D851C99E9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1C29F1D-D123-4635-99F8-F7D851C99E9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1C29F1D-D123-4635-99F8-F7D851C99E9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1C29F1D-D123-4635-99F8-F7D851C99E9E}.Release|Any CPU.Build.0 = Release|Any CPU + {99A88C01-6949-4EB3-9EDC-A56D8B2B098D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99A88C01-6949-4EB3-9EDC-A56D8B2B098D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99A88C01-6949-4EB3-9EDC-A56D8B2B098D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99A88C01-6949-4EB3-9EDC-A56D8B2B098D}.Release|Any CPU.Build.0 = Release|Any CPU + {C2A9A963-BDA1-4F9D-B13D-C5277812F6C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2A9A963-BDA1-4F9D-B13D-C5277812F6C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2A9A963-BDA1-4F9D-B13D-C5277812F6C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2A9A963-BDA1-4F9D-B13D-C5277812F6C3}.Release|Any CPU.Build.0 = Release|Any CPU + {1D3DDCD0-A2A5-458A-A933-C8A307ED36AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D3DDCD0-A2A5-458A-A933-C8A307ED36AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D3DDCD0-A2A5-458A-A933-C8A307ED36AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D3DDCD0-A2A5-458A-A933-C8A307ED36AA}.Release|Any CPU.Build.0 = Release|Any CPU + {E736C652-75A2-4582-AD39-91AB6DED08B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E736C652-75A2-4582-AD39-91AB6DED08B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E736C652-75A2-4582-AD39-91AB6DED08B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E736C652-75A2-4582-AD39-91AB6DED08B0}.Release|Any CPU.Build.0 = Release|Any CPU + {8F9C9834-7CE8-46A8-9FB5-7B92DC089BC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F9C9834-7CE8-46A8-9FB5-7B92DC089BC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F9C9834-7CE8-46A8-9FB5-7B92DC089BC4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F9C9834-7CE8-46A8-9FB5-7B92DC089BC4}.Release|Any CPU.Build.0 = Release|Any CPU + {462BB4A6-B856-40B6-BEF6-06B89B240B29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {462BB4A6-B856-40B6-BEF6-06B89B240B29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {462BB4A6-B856-40B6-BEF6-06B89B240B29}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {462BB4A6-B856-40B6-BEF6-06B89B240B29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {462BB4A6-B856-40B6-BEF6-06B89B240B29}.Release|Any CPU.Build.0 = Release|Any CPU + {462BB4A6-B856-40B6-BEF6-06B89B240B29}.Release|Any CPU.Deploy.0 = Release|Any CPU + {436BE515-4CB9-4CD3-991D-226FE07727A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {436BE515-4CB9-4CD3-991D-226FE07727A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {436BE515-4CB9-4CD3-991D-226FE07727A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {436BE515-4CB9-4CD3-991D-226FE07727A8}.Release|Any CPU.Build.0 = Release|Any CPU + {FCAEAF0C-4FFD-4B77-B4E7-C127D751D28F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCAEAF0C-4FFD-4B77-B4E7-C127D751D28F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCAEAF0C-4FFD-4B77-B4E7-C127D751D28F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {FCAEAF0C-4FFD-4B77-B4E7-C127D751D28F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCAEAF0C-4FFD-4B77-B4E7-C127D751D28F}.Release|Any CPU.Build.0 = Release|Any CPU + {FCAEAF0C-4FFD-4B77-B4E7-C127D751D28F}.Release|Any CPU.Deploy.0 = Release|Any CPU + {AE5E2930-B5D8-4032-81D5-3D5A1ECCEDFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE5E2930-B5D8-4032-81D5-3D5A1ECCEDFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE5E2930-B5D8-4032-81D5-3D5A1ECCEDFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE5E2930-B5D8-4032-81D5-3D5A1ECCEDFA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8F9C9834-7CE8-46A8-9FB5-7B92DC089BC4} = {66BF81EC-7830-431B-966A-8000BCB13DAF} + {462BB4A6-B856-40B6-BEF6-06B89B240B29} = {66BF81EC-7830-431B-966A-8000BCB13DAF} + {436BE515-4CB9-4CD3-991D-226FE07727A8} = {66BF81EC-7830-431B-966A-8000BCB13DAF} + {FCAEAF0C-4FFD-4B77-B4E7-C127D751D28F} = {66BF81EC-7830-431B-966A-8000BCB13DAF} + {AE5E2930-B5D8-4032-81D5-3D5A1ECCEDFA} = {66BF81EC-7830-431B-966A-8000BCB13DAF} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F896C22A-22EA-4B67-80E0-E687F8C2A97A} + EndGlobalSection +EndGlobal diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..b7d1f09 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,6 @@ + + + enable + 11.0.6 + + diff --git a/docker-compose-avalonia.yml b/docker-compose-avalonia.yml new file mode 100644 index 0000000..bad3ef9 --- /dev/null +++ b/docker-compose-avalonia.yml @@ -0,0 +1,11 @@ +version: '3.4' + +services: + bgc.browser: + ports: + - 5002:80 + image: bgc.browser + container_name: BGC.Browser + build: + context: . + dockerfile: BGC.Client/BGC.Client.Browser/Dockerfile \ No newline at end of file diff --git a/docker-compose.dcproj b/docker-compose.dcproj new file mode 100644 index 0000000..e31b97b --- /dev/null +++ b/docker-compose.dcproj @@ -0,0 +1,20 @@ + + + + 2.1 + Linux + False + 1d3ddcd0-a2a5-458a-a933-c8a307ed36aa + LaunchBrowser + {Scheme}://localhost:{ServicePort}/swagger + bgc.webapi + + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..60f2603 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1 @@ +version: '3.4' \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0dbab89 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,57 @@ +version: '3.4' + +services: + bgc.webapi: + image: bgc.webapi + ports: + - 5002:8080 + hostname: webapi + container_name: BGC.WebApi + depends_on: + - bgc.debugpostgresql + environment: + PGSQLCNNSTR: "${PGSQLCNNSTR}" + build: + context: . + dockerfile: BGC.WebApi/Dockerfile + + bgc.debugpostgresql: + ports: + - 54322:5432 + container_name: BGC.PostgreSQL + hostname: debug_postgresql + image: postgres:16 + restart: always + #volumes: + #- reports_pg_data:/var/lib/postgresql/data + # - ./init.sql:/docker-entrypoint-initdb.d/init_debug.sql + command: + - "postgres" + - "-c" + - "max_connections=100" + - "-c" + - "shared_buffers=512MB" + - "-c" + - "temp_buffers=64MB" + - "-c" + - "log_statement=all" + environment: + POSTGRES_PASSWORD: QW12cv9001 + POSTGRES_DB: bgc + + # bgc.browser: + # ports: + # - 5002:80 + # image: bgc.browser + # container_name: BGC.Browser + # build: + # context: . + # dockerfile: BGC.Client/BGC.Client.Browser/Dockerfile + bgc.testapi: + image: ${DOCKER_REGISTRY-}bgctestapi + ports: + - 5003:8080 + build: + context: . + dockerfile: BGC.TestApi/Dockerfile + diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..fb5869b --- /dev/null +++ b/init.sql @@ -0,0 +1,99 @@ +create table games( + id bigserial PRIMARY KEY, + name_ru text, + name_eng text, + issue_date date, + age_min int, + age_max int, + players_min int, + players_max int, + game_time_min interval, + game_time_max interval, + edition int +); + +create table countries( + id bigserial PRIMARY KEY, + name text +); + +create table genres( + id bigserial PRIMARY KEY, + name text +); + +create table themes( + id bigserial PRIMARY KEY, + name text +); + +create table authors( + id bigserial PRIMARY KEY, + first_name text, + last_name text, + id_country bigint REFERENCES countries (id) +); + +create table owners( + id bigserial PRIMARY KEY, + first_name text, + last_name text +); + +create table rating_types( + id bigserial PRIMARY KEY, + name text, +scale text +); + +create table game_collection( + id_owner bigint REFERENCES owners (id), +id_game bigint REFERENCES games (id), +PRIMARY KEY(id_owner, id_game) +); + +create table game_authors( + id_author bigint REFERENCES authors (id), +id_game bigint REFERENCES games (id), +PRIMARY KEY(id_author, id_game) +); + +create table game_genres( + id_game bigint REFERENCES games (id), +id_genre bigint REFERENCES genres (id), +PRIMARY KEY(id_genre, id_game) +); + +create table game_themes( + id_game bigint REFERENCES games (id), + id_theme bigint REFERENCES themes (id), + PRIMARY KEY(id_theme, id_game) +); + +create table game_ratings( + id_game bigint REFERENCES games (id), + id_rating_type bigint REFERENCES rating_types (id), + rating numeric, + PRIMARY KEY(id_rating_type, id_game) +); + +ALTER TABLE games +ADD CHECK (name_ru is not null or name_eng is not null); + +ALTER TABLE games +ADD CHECK (age_min >= 0 and age_min <= 99); + +ALTER TABLE games +ADD CHECK (age_max >= 0 and age_max <= 99); + +ALTER TABLE games +ADD CHECK (players_min > 0 and players_min < 99); + +ALTER TABLE games +ADD CHECK (players_max > 0 and players_max < 99); + +ALTER TABLE games +ADD CHECK (game_time_min > 0 and game_time_min < 1440); + +ALTER TABLE games +ADD CHECK (game_time_max > 0 and game_time_max < 1440); \ No newline at end of file diff --git a/launchSettings.json b/launchSettings.json new file mode 100644 index 0000000..b98bbee --- /dev/null +++ b/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "Docker Compose": { + "commandName": "DockerCompose", + "commandVersion": "1.0", + "serviceActions": { + "bgc.webapi": "StartDebugging", + "bgc.client.browser": "StartDebugging", + "bgc.testapi": "StartDebugging" + } + } + } +} \ No newline at end of file