commit d21315cf323db13f01c47897ff0269f984b014be Author: Derek Holloway Date: Mon Jun 16 17:04:55 2025 -0700 Init Commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..425f98b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] +csharp_new_line_before_open_brace = none +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false \ No newline at end of file diff --git a/.env_Template b/.env_Template new file mode 100755 index 0000000..513b796 --- /dev/null +++ b/.env_Template @@ -0,0 +1,11 @@ +Stripe_Key= + +MySQL_Server=mistox-database +MySQL_User=root +MySQL_Database=mistox +MySQL_Pass=oasv34$8gpv023dd # Random value for the server and MySQL to communicate with + +Email_Server= # Hostname of email server +Email_Port= # SMTP port used +Email_Address= # Email Address to send from +Email_Password= # Password for the email address \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..00bed4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +**/bin +**/obj +.git +.env +.vscode +data \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..8f30ba5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# Build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +# Copy all projects +COPY ["src", "."] + +# Restore the Server +RUN dotnet restore 'MistoxWebsite.Server/MistoxWebsite.Server.csproj' + +# Publish +FROM build as publish +RUN dotnet publish 'MistoxWebsite.Server/MistoxWebsite.Server.csproj' -c Release -o /app/publish + +# Run the app +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +ENV ASPNETCORE_HTTP_PORTS=5001 +ENV StripeKey=null +ENV MySQLServer=null +ENV MySQLUser=null +ENV MySQLPass=null +ENV MySQLDatabase=Mistox +EXPOSE 5001 +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "MistoxWebsite.Server.dll"] diff --git a/MistoxWebsite.sln b/MistoxWebsite.sln new file mode 100755 index 0000000..9ce3fc2 --- /dev/null +++ b/MistoxWebsite.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MistoxWebsite.Client", "src\MistoxWebsite.Client\MistoxWebsite.Client.csproj", "{9E4D64F9-2F56-4AC5-85CE-51EFEE1513C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MistoxWebsite.Server", "src\MistoxWebsite.Server\MistoxWebsite.Server.csproj", "{76F2B6C1-FF9A-4BD8-AB7A-7456E8122C44}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MistoxWebsite.Shared", "src\MistoxWebsite.Shared\MistoxWebsite.Shared.csproj", "{19C67017-8C26-439B-95B3-FE346D1AC7D5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E4D64F9-2F56-4AC5-85CE-51EFEE1513C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E4D64F9-2F56-4AC5-85CE-51EFEE1513C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E4D64F9-2F56-4AC5-85CE-51EFEE1513C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E4D64F9-2F56-4AC5-85CE-51EFEE1513C0}.Release|Any CPU.Build.0 = Release|Any CPU + {76F2B6C1-FF9A-4BD8-AB7A-7456E8122C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76F2B6C1-FF9A-4BD8-AB7A-7456E8122C44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76F2B6C1-FF9A-4BD8-AB7A-7456E8122C44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76F2B6C1-FF9A-4BD8-AB7A-7456E8122C44}.Release|Any CPU.Build.0 = Release|Any CPU + {19C67017-8C26-439B-95B3-FE346D1AC7D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19C67017-8C26-439B-95B3-FE346D1AC7D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19C67017-8C26-439B-95B3-FE346D1AC7D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19C67017-8C26-439B-95B3-FE346D1AC7D5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B413876B-4048-47F1-B8B8-B974DF5E9E2A} + EndGlobalSection +EndGlobal diff --git a/ToDo.txt b/ToDo.txt new file mode 100755 index 0000000..a7eb16d --- /dev/null +++ b/ToDo.txt @@ -0,0 +1,27 @@ +Fix stripe payments *Updated API* + Havent Tested + +After a new account is created notify a user that they need to verify their email before logging in + +Cleanup SQL queries + On page load for example there are 4 different queries + +AccountInventory.cs + SetInventory isnt fully implimented + +ProjectMistData.cs + Data inside the sql doesnt match what is inside the database + +ForgotPassword Email / Resetpassword Email + Needs styles that match the theme of the website + +Manage / Data tabs in Account settings + Theme needs to be updated to match + Delete button doesn't shade when mouse hovers + frame that comes up isnt themed either + +Store Catalog + Add to cart wraps text when screen is too small + +Program + Probably need to turn on cors at some point \ No newline at end of file diff --git a/database/Dockerfile b/database/Dockerfile new file mode 100755 index 0000000..a017402 --- /dev/null +++ b/database/Dockerfile @@ -0,0 +1,8 @@ +FROM mysql + +ENV MYSQL_DATABASE=mistox +ENV MYSQL_ROOT_PASSWORD=90pa8pav89h4g08hads + +ADD mistox.sql /docker-entrypoint-initdb.d + +EXPOSE 3306 diff --git a/database/mistox.sql b/database/mistox.sql new file mode 100755 index 0000000..425f64b --- /dev/null +++ b/database/mistox.sql @@ -0,0 +1,104 @@ +CREATE DATABASE IF NOT EXISTS `mistox`; +USE `mistox`; + +CREATE TABLE IF NOT EXISTS `Account` ( + `ID` int(11) NOT NULL AUTO_INCREMENT, + `UserName` varchar(60) DEFAULT NULL, + `Email` varchar(60) DEFAULT NULL, + `EmailVerified` tinyint(4) DEFAULT NULL, + `PasswordHash` varchar(100) DEFAULT NULL, + PRIMARY KEY (`ID`) +) AUTO_INCREMENT=1; + +CREATE TABLE IF NOT EXISTS `AccountInventory` ( + `AccountID` int(11) NOT NULL, + `ProductID` int(11) NOT NULL, + `Item` varchar(45) NOT NULL, + `Quantity` int(11) DEFAULT NULL, + `Stats` varchar(45) DEFAULT NULL, + PRIMARY KEY (`AccountID`,`ProductID`,`Item`) +); + +CREATE TABLE IF NOT EXISTS `Product` ( + `ID` int(11) NOT NULL AUTO_INCREMENT, + `Name` varchar(45) DEFAULT NULL, + `Description` text DEFAULT NULL, + `Images` longtext DEFAULT NULL, + `Cost` int(11) DEFAULT NULL, + `URL` varchar(200) DEFAULT NULL, + PRIMARY KEY (`ID`), + UNIQUE KEY `ID_UNIQUE` (`ID`) +) AUTO_INCREMENT=1; + +CREATE TABLE IF NOT EXISTS `Cart` ( + `ID` int(11) NOT NULL AUTO_INCREMENT, + `AccountID` int(11) DEFAULT NULL, + `ProductID` int(11) DEFAULT NULL, + PRIMARY KEY (`ID`), + KEY `AccountID` (`AccountID`), + KEY `ProductID` (`ProductID`), + CONSTRAINT `Cart_ibfk_1` FOREIGN KEY (`AccountID`) REFERENCES `Account` (`ID`), + CONSTRAINT `Cart_ibfk_2` FOREIGN KEY (`ProductID`) REFERENCES `Product` (`ID`) +) AUTO_INCREMENT=1; + +CREATE TABLE IF NOT EXISTS `ProjectMistData` ( + `AccountID` int(11) NOT NULL, + `Credits` int(11) DEFAULT NULL, + `OddballTimer` double DEFAULT NULL, + `SessionToken` varchar(45) DEFAULT NULL, + `SessionID` int(11) DEFAULT NULL, + `Kills` int(11) DEFAULT NULL, + `Deaths` int(11) DEFAULT NULL, + PRIMARY KEY (`AccountID`) +); + +CREATE TABLE IF NOT EXISTS `Receipt` ( + `AccountID` int(11) NOT NULL, + `ProductID` int(11) NOT NULL, + `ReceiptID` varchar(45) NOT NULL, + `LineItem` int(11) NOT NULL, + `Time` datetime DEFAULT NULL, + `TaxAmount` int(11) DEFAULT NULL, + `TotalCost` int(11) DEFAULT NULL, + PRIMARY KEY (`AccountID`,`ProductID`,`ReceiptID`,`LineItem`) +); + +CREATE TABLE IF NOT EXISTS `WebsiteData` ( + `AccountID` int(11) NOT NULL, + `FailedPasswordLock` tinyint(4) DEFAULT NULL, + `PasswordAttempts` int(11) DEFAULT NULL, + `CurrentPasswordAttempts` int(11) DEFAULT NULL, + `Role` varchar(45) DEFAULT NULL, + `EmailToken` varchar(45) DEFAULT NULL, + PRIMARY KEY (`AccountID`) +); + +INSERT INTO Account ( + ID, + UserName, + Email, + EmailVerified, + PasswordHash +) VALUES ( + '1', + 'admin', + 'admin@mistox.com', + '1', + '' +); + +INSERT INTO WebsiteData ( + AccountID, + FailedPasswordLock, + PasswordAttempts, + CurrentPasswordAttempts, + Role, + EmailToken +) VALUES ( + '1', + '1', + '5', + '0', + 'Admin', + '' +); \ No newline at end of file diff --git a/docker-build-Internal-Tests.sh b/docker-build-Internal-Tests.sh new file mode 100755 index 0000000..d7592a0 --- /dev/null +++ b/docker-build-Internal-Tests.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Compile the source +docker build -t mistox-sql ./database +docker build -t mistox-website . + +cd ../mistoxnet-tests + +# Start the servers +docker compose up -d --force-recreate --remove-orphans \ No newline at end of file diff --git a/docker-build.sh b/docker-build.sh new file mode 100755 index 0000000..08f2c4f --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Compile the source +docker build -t mistox-sql ./database +docker build -t mistox-website . + +# Start the servers +docker compose up -d --force-recreate --remove-orphans \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100755 index 0000000..29bafef --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +services: + + mistox-server: + container_name: mistox_server + image: mistox-website:latest + restart: always + environment: + - StripeKey=${Stripe_Key} + - MySQLServer=${MySQL_Server} + - MySQLUser=${MySQL_User} + - MySQLPass=${MySQL_Pass} + - MySQLDatabase=${MySQL_Database} + - EmailServer=${Email_Server} + - EmailPort=${Email_Port} + - EmailAddress=${Email_Address} + - EmailPassword=${Email_Password} + ports: + - 5001:5001 + depends_on: + - mistox-database + + mistox-database: + container_name: mistox_database + image: mistox-sql:latest + restart: always + volumes: + - ./data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: ${MySQL_Pass} \ No newline at end of file diff --git a/src/MistoxWebsite.Client b/src/MistoxWebsite.Client new file mode 160000 index 0000000..db1a587 --- /dev/null +++ b/src/MistoxWebsite.Client @@ -0,0 +1 @@ +Subproject commit db1a58746c826ff532ed883fddd5dedf9f6c0320 diff --git a/src/MistoxWebsite.Server/Controllers/AuthenticationController.cs b/src/MistoxWebsite.Server/Controllers/AuthenticationController.cs new file mode 100755 index 0000000..3d2f724 --- /dev/null +++ b/src/MistoxWebsite.Server/Controllers/AuthenticationController.cs @@ -0,0 +1,385 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using MistoxWebsite.Shared; +using System.Security.Claims; +using MistoxWebsite.Server.Services; +using MistoxWebsite.Server.Services.DatabaseService; +using Microsoft.AspNetCore.Authentication.Cookies; + +namespace MistoxWebsite.Server.Controllers { + [ApiController] + public class AuthenticationController : ControllerBase { + + DatabaseService _accountContext; + EmailService _emailContext; + + public AuthenticationController( DatabaseService DatabaseContext, EmailService emailContext ) { + _accountContext = DatabaseContext; + _emailContext = emailContext; + } + + // In Account -> References UserName / PasswordHash + // Out Account + [Route( "api/account/login" )] + [HttpPost] + public async Task> Login( [FromBody] Account request ) { + try { + Account? test = await _accountContext.GetAccount(request.UserName.ToLower()); + if( test != null ) { + if( test.EmailVerified == true ) { + if( test.SiteData.FailedPasswordLock ) { + if( test.SiteData.CurrentPasswordAttempts >= test.SiteData.PasswordAttempts ) { + return new Account() { Error = "Too many failed password attempts. Please reset your password" }; + } + } + if( BCrypt.Net.BCrypt.Verify( request.PasswordHash, test.PasswordHash ) ) { + test.SiteData.CurrentPasswordAttempts = 0; + await _accountContext.SetAccount( test ); + + AccountClaims aClaims = await getClaims(test.ID); + List claims = new List() { + new Claim(ClaimTypes.Name, aClaims.UserName), + new Claim(ClaimTypes.Email, aClaims.Email), + new Claim("emailverified", aClaims.EmailVerified), + new Claim(ClaimTypes.Role, aClaims.Role), + new Claim("LockAccount", aClaims.FailedPasswordLock), + new Claim("ID", test.ID.ToString()) + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal( new ClaimsIdentity( claims, "Auth" ) ), + new AuthenticationProperties { + ExpiresUtc = DateTime.UtcNow.AddYears(30), // Add 30 years with sliding on + IsPersistent = request.EmailVerified, // Is set from the StayLoggedIn + } + ); + return test; + } else { + test.SiteData.CurrentPasswordAttempts += 1; + await _accountContext.SetAccount( test ); + return new Account() { Error = "Wrong password" }; + } + }else{ + await SendVerify(test); + return new Account() { Error = "A new verify email has been sent. \n Note only 1 email send every 5 mintes" }; + } + } + return new Account() { Error = "User doesn't exist" }; + } catch( Exception ex ) { + return new Account() { Error = ex.Message }; + } + } + + // In Account -> References UserName / PasswordHash + // Out Account + [Route( "api/account/session" )] + [HttpPost] + public async Task> LoginSession( [FromBody] Account request ) { + try { + Account? test = await _accountContext.GetAccount(request.UserName.ToLower()); + if( test != null ) { + if( request.PasswordHash == test.PasswordHash ) { + return test; + } else { + test.SiteData.CurrentPasswordAttempts += 1; + await _accountContext.SetAccount( test ); + return new Account() { Error = "Wrong password" }; + } + } + return new Account() { Error = "User doesn't exist" }; + } catch( Exception ex ) { + return new Account() { Error = ex.Message }; + } + } + + // In Account + // Out List + [Route( "api/account/claims" )] + [HttpPost] + public async Task> Claims( [FromBody] Account Account ) { + AccountClaims claims = await getClaims(Account.ID); + return claims; + } + + async Task getClaims( int AccountID ) { + try { + Account? test = await _accountContext.GetAccountByID(AccountID); + if( test != null ) { + AccountClaims aClaims = new AccountClaims() { + UserName = test.UserName, + Email = test.Email, + Role = test.SiteData.Role + }; + aClaims.EmailVerified = test.EmailVerified ? "1" : "0"; + aClaims.FailedPasswordLock = test.SiteData.FailedPasswordLock ? "1" : "0"; + return aClaims; + } + return new AccountClaims(); + } catch { + return new AccountClaims(); + } + } + + + // In Account -> Full account + // Out Account + [Route( "api/account/register" )] + [HttpPost] + public async Task> Register( [FromBody] Account request ) { + try { + if( await _accountContext.GetAccount( request.UserName.ToLower() ) == null ) { + if( await _accountContext.GetAccount( request.Email.ToLower() ) == null ) { + Account? created = new Account(){ + UserName = request.UserName.ToLower(), + Email = request.Email.ToLower(), + EmailVerified = false, + PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.PasswordHash), + }; + await _accountContext.NewAccount( created ); + created = await _accountContext.GetAccount( request.Email.ToLower() ); + if( created != null ) { + AccountClaims aClaims = await getClaims(created.ID); + List claims = new List() { + new Claim(ClaimTypes.Name, aClaims.UserName), + new Claim(ClaimTypes.Email, aClaims.Email), + new Claim("emailverified", aClaims.EmailVerified), + new Claim(ClaimTypes.Role, aClaims.Role), + new Claim("LockAccount", aClaims.FailedPasswordLock) + }; + + await SendVerify(created); + return created; + } + return new Account() { Error = "Unknown Error" }; + } else { + return new Account() { Error = "Email is already in use" }; + } + } else { + return new Account() { Error = "UserName is taken" }; + } + } catch( Exception ex ) { + Console.WriteLine("Error: " + ex.Message); + return new Account() { Error = ex.Message }; + } + + } + + // In Account -> References UserName / PasswordHash( Current Password ) / Error( New Password ) + // Out Bool + [Route( "api/account/changepassword" )] + [HttpPost] + public async Task> ChangePassword( [FromBody] Account request ) { + try { + Account? test = await _accountContext.GetAccount(request.UserName.ToLower()); + if( test != null ) { + if( BCrypt.Net.BCrypt.Verify( request.PasswordHash, test.PasswordHash ) ) { + test.PasswordHash = BCrypt.Net.BCrypt.HashPassword( request.Error ); + test.SiteData.CurrentPasswordAttempts = 0; + await _accountContext.SetAccount( test ); + return true; + } + } + return false; + } catch { + return false; + } + } + + // In Account -> References UserName / SiteData FailedPasswordLock / SiteData.PasswordAttempts + // Out Error String + [Route( "api/account/toggleAccountLock" )] + [HttpPost] + public async Task> ToggleAccountLock( [FromBody] Account request ) { + try { + Account? test = await _accountContext.GetAccount(request.UserName); + if( test != null ) { + test.SiteData.FailedPasswordLock = request.SiteData.FailedPasswordLock; + test.SiteData.CurrentPasswordAttempts = 0; + test.SiteData.PasswordAttempts = request.SiteData.PasswordAttempts; + await _accountContext.SetAccount( test ); + return "Account Lock Status Updated"; + } + return "Unknown Error Occurred"; + } catch( Exception ex ) { + return ex.Message; + } + } + + // Out Account -> only if logged in + [Route( "api/account/get" )] + [HttpPost] + public async Task> Get() { + try { + if( User.Identity != null && User.Identity.IsAuthenticated ) { + string? email = User.FindFirstValue(ClaimTypes.Email); + if( !string.IsNullOrEmpty( email ) ) { + Account? test = await _accountContext.GetAccount(email); + if( test != null ) { + return test; + } + } + } + return Ok(); + } catch { + return Ok(); + } + } + + // In Null + // Out Null + [Route( "api/account/logout" )] + [HttpPost] + public async Task Logout() { + await HttpContext.SignOutAsync(); + } + + string Substitue( string message, string subString, string Replacement ) { + for( int i = 0; i < (message.Length - subString.Length); i++ ) { + if( message.Substring( i, subString.Length ) == subString ) { + string before = message.Substring( 0, i ); + string after = message.Substring(i + subString.Length ); + return before + Replacement + after; + } + } + return message; + } + + + + // In Account -> References UserName + // Out Success bool + [Route( "api/account/sendverifyemail" )] + [HttpPost] + public async Task> SendVerify( [FromBody] Account request ) { + try { + string key = "v" + request.UserName; + // Stop from sending multiple emails quickly + if ( _emailContext._SentEmails.ContainsKey(key) ){ + DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key); + if (PreviousSentTime.AddMinutes(5) > DateTime.Now){ + return "Cannot sent another verify email until 5 minutes has elapsed "; + }else{ + _emailContext._SentEmails.Remove(key); + } + } + Account? test = await _accountContext.GetAccount(request.UserName.ToLower()); + if( test != null ) { + test.SiteData.EmailToken = Guid.NewGuid().ToString(); + await _accountContext.SetAccount( test ); + + string EmailContents = EmailService.VerifyEmailEmail; + EmailContents = Substitue( EmailContents, "@UserName", request.UserName ); + EmailContents = Substitue( EmailContents, "@UserName", request.UserName ); + EmailContents = Substitue( EmailContents, "@VerifyPassword", test.SiteData.EmailToken ); + + string result = _emailContext.Send( test.Email, EmailService.VerifyEmailSubject, EmailContents ); + _emailContext._SentEmails.Add(key, DateTime.Now); + return result; + } + return "Account not found"; + } catch (Exception) { + return "The connection couldn't be established to the email server"; + } + } + + // In Account -> References UserName / Password( EmailToken ) + // Out Success bool + [Route( "api/account/verifyemail" )] + [HttpPost] + public async Task> VerifyEmail( [FromBody] Account request ) { + try { + Account? test = await _accountContext.GetAccount(request.UserName.ToLower()); + if( test != null ) { + if( test.SiteData.EmailToken == request.PasswordHash ) { + test.SiteData.EmailToken = ""; + test.EmailVerified = true; + await _accountContext.SetAccount( test ); + return true; + } + } + return false; + } catch { + return false; + } + } + + // In Account -> References Email + // Out Success bool + [Route( "api/account/sendresetpassword" )] + [HttpPost] + public async Task> ResetPassword( [FromBody] Account request ) { + try { + string key = "p" + request.Email.ToLower(); + // Stop from sending multiple emails quickly + if ( _emailContext._SentEmails.ContainsKey(key) ){ + DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key); + if (PreviousSentTime.AddMinutes(5) > DateTime.Now){ + return "Cannot sent another reset requests until 5 minutes has elapsed"; + }else{ + _emailContext._SentEmails.Remove(key); + } + } + Account? test = await _accountContext.GetAccount(request.Email.ToLower()); + if( test != null ) { + test.SiteData.EmailToken = Guid.NewGuid().ToString(); + await _accountContext.SetAccount( test ); + + string EmailContents = EmailService.ResetPasswordEmail; + EmailContents = Substitue( EmailContents, "@UserName", test.UserName ); + EmailContents = Substitue( EmailContents, "@UserName", test.UserName ); + EmailContents = Substitue( EmailContents, "@ResetPassWord", test.SiteData.EmailToken ); + + string result = _emailContext.Send( test.Email, EmailService.VerifyEmailSubject, EmailContents ); + _emailContext._SentEmails.Add(key, DateTime.Now); + return result; + } + return "Account Not Found"; + } catch (Exception) { + return "The connection couldn't be established to the email server"; + } + + } + + // In Account -> References UserName / Password( NewPassword ) / Error( EmailToken ) + // Out Success bool + [Route( "api/account/resetpassword" )] + [HttpPost] + public async Task> ResetPwdVerify( [FromBody] Account request ) { + try { + Account? test = await _accountContext.GetAccount(request.UserName.ToLower()); + if( test != null && !string.IsNullOrEmpty(test.SiteData.EmailToken) ) { + if( test.SiteData.EmailToken == request.Error ) { + test.SiteData.CurrentPasswordAttempts = 0; + test.PasswordHash = BCrypt.Net.BCrypt.HashPassword( request.PasswordHash ); + await _accountContext.SetAccount( test ); + return true; + } + } + return false; + } catch { + return false; + } + } + + // In Account -> References UserName / Password( Password ) ) + // Out Success bool + [Route( "api/account/delete" )] + [HttpPost] + public async Task> delete( [FromBody] Account request ) { + try { + Account? test = await _accountContext.GetAccount(request.UserName.ToLower()); + if( test != null ) { + if( BCrypt.Net.BCrypt.Verify( request.PasswordHash, test.PasswordHash ) ) { + await _accountContext.DeleteAccount( test ); + return true; + } + } + return false; + } catch { + return false; + } + } + + } +} diff --git a/src/MistoxWebsite.Server/Controllers/PageLoad.cs b/src/MistoxWebsite.Server/Controllers/PageLoad.cs new file mode 100755 index 0000000..16b081a --- /dev/null +++ b/src/MistoxWebsite.Server/Controllers/PageLoad.cs @@ -0,0 +1,41 @@ +using MistoxWebsite.Server.Services.DatabaseService; +using System.Security.Claims; +using MistoxWebsite.Shared; +using Microsoft.AspNetCore.Mvc; + +namespace MistoxWebsite.Server.Controllers { + [ApiController] + public class PageLoad : ControllerBase { + + DatabaseService _databaseService; + + public PageLoad( DatabaseService context ) { + _databaseService = context; + } + + [Route( "api/pageload" )] + [HttpPost] + public async Task> onPageLoad() { + try { + if( User.Identity != null && User.Identity.IsAuthenticated ) { + string? id = User.FindFirstValue( "ID" ); + if( !string.IsNullOrEmpty( id ) ) { + PageLoadObject test = await _databaseService.getPageLoadObject(int.Parse(id)); + if (test.user != null){ + test.Cart = await _databaseService.GetCart( test.user ); + if( test != null ) { + return test; + } + } + } + } + return NotFound(); + } catch (Exception e) { + Console.WriteLine(e.ToString()); + return NotFound(); + } + } + + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Controllers/PaymentController.cs b/src/MistoxWebsite.Server/Controllers/PaymentController.cs new file mode 100755 index 0000000..ba348c1 --- /dev/null +++ b/src/MistoxWebsite.Server/Controllers/PaymentController.cs @@ -0,0 +1,117 @@ +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using MistoxWebsite.Server.Controllers.Payment; +using MistoxWebsite.Server.Services.DatabaseService; +using MistoxWebsite.Shared; +using Newtonsoft.Json; +using Stripe; +using Stripe.Climate; +using Stripe.Tax; + +namespace MistoxWebsite.Server.Controllers { + [ApiController] + public class PaymentController : ControllerBase { + + DatabaseService _databaseService; + + public PaymentController( DatabaseService databaseService ) { + _databaseService = databaseService; + } + + // Charges + [Route( "api/getCheckoutToken" )] + [HttpPost] + public async Task GetPaymentKey( [FromQuery] string userID ) { + + string OrderNumber = Guid.NewGuid().ToString().Substring(0,10); + Shared.Account? acc = await _databaseService.GetAccount(userID); + if (acc != null) { + List cart = await _databaseService.GetCart(acc); + + IPayment PaymentPlugin = new StripeIntent(_databaseService); + + (bool, string) PaymentResponse = await PaymentPlugin.Purchase(OrderNumber, acc, cart); + if (PaymentResponse.Item1) { + return PaymentResponse.Item2; + } + else { + Console.WriteLine("An error has occured in the payment plugin\n\n"); + Console.WriteLine(PaymentResponse.Item2); + Console.WriteLine("\n"); + return "0"; + } + + } + return "0"; + } + + [Route( "/api/payment/response" )] + [HttpPost] + public async Task paymentWebhook() { + try { + const string endpointSecret = "whsec_HCO7uv2BPIPmUPOiSg9tfwLZul8usCGG"; + string body = await new StreamReader(Request.Body).ReadToEndAsync(); + Event e = EventUtility.ConstructEvent( body, Request.Headers["Stripe-Signature"], endpointSecret ); + if( e.Type == "payment_intent.succeeded" ) { + + // Extract Data from payment confirm + PaymentIntent intent = (PaymentIntent)e.Data.Object; + string orderNumber = ""; + int userID = 0; + List productIDs = new List(); + int subtotal = 0; + int total = 0; + + KeyValuePair[] y = intent.Metadata.ToArray(); + foreach( KeyValuePair cur in y ) { + string val = cur.Key; + if( val == "ordernumber" ) { + orderNumber = cur.Value; + } else if( val == "user" ) { + userID = int.Parse( cur.Value ); + } else if( val == "products" ) { + string[] products = cur.Value.Split(','); + foreach( string product in products ) { + if ( !string.IsNullOrEmpty(product) ) { + productIDs.Add( Convert.ToInt32( product ) ); + } + } + } else if( val == "subtotal" ) { + subtotal = int.Parse( cur.Value ); + } else if( val == "total" ) { + total = int.Parse( cur.Value ); + } + } + + // Clear the cart + Shared.Account account = new Shared.Account{ + ID = userID + }; + await _databaseService.ClearCart( account ); + + // Add data to misox receipt + for( int i = 0; i < productIDs.Count; i++ ) { + int product = productIDs[i]; + await _databaseService.NewReceipt( new Receipt { + AccountID = userID, + ProductID = product, + ReceiptID = orderNumber, + Time = DateTime.Now, + TaxAmount = total - subtotal, + TotalCost = total, + LineItem = i + } ); + } + } else { + Console.WriteLine( "Unhandled event type: {0}", e.Type ); + } + return Ok(); + } catch( Exception ex ) { + return Content(ex.ToString()); + } + } + + } + +} + diff --git a/src/MistoxWebsite.Server/Controllers/PaymentMethods/IPayment.cs b/src/MistoxWebsite.Server/Controllers/PaymentMethods/IPayment.cs new file mode 100644 index 0000000..b36973a --- /dev/null +++ b/src/MistoxWebsite.Server/Controllers/PaymentMethods/IPayment.cs @@ -0,0 +1,11 @@ +using MistoxWebsite.Shared; + +namespace MistoxWebsite.Server.Controllers.Payment { + + public interface IPayment { + + public Task<(bool, string)> Purchase(string OrderNumber, Account user, List cart); + + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Controllers/PaymentMethods/StripeIntents.cs b/src/MistoxWebsite.Server/Controllers/PaymentMethods/StripeIntents.cs new file mode 100644 index 0000000..ee7f0f0 --- /dev/null +++ b/src/MistoxWebsite.Server/Controllers/PaymentMethods/StripeIntents.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using MistoxWebsite.Server.Controllers.Payment; +using MistoxWebsite.Server.Services.DatabaseService; +using MistoxWebsite.Shared; +using Stripe; +using Stripe.Tax; + +namespace MistoxWebsite.Server.Controllers { + + public class StripeIntent : IPayment { + + DatabaseService _databaseService; + + public StripeIntent( DatabaseService databaseService ) { + _databaseService = databaseService; + } + + public async Task<(bool, string)> Purchase(string OrderNumber, Shared.Account user, List cart) { + try { + // build Recipt and calculate Tax + var options = new CalculationCreateOptions { + Currency = "usd", + CustomerDetails = new CalculationCustomerDetailsOptions { + AddressSource = "billing", + }, + Expand = new List() { "line_items" }, + LineItems = new List() + }; + + List prods = new List(); + + // Add items to receipt + int subtotal = 0; + foreach (Cart items in cart) { + Shared.Product? product = await _databaseService.GetProduct(items.ProductID); + if (product != null) { + prods.Add(product.ID); + if (product != null) { + subtotal += product.Cost; + options.LineItems.Add(new CalculationLineItemOptions { + Amount = product.Cost, + TaxCode = "txcd_10201000", // Tax code for downloadable digital games + Quantity = 1, + Reference = product.Name, + TaxBehavior = "exclusive" + }); + } + } + + } + + var service = new CalculationService(); + Calculation result = service.Create(options); + + string csv = ""; + foreach (int cur in prods) { + csv = csv + cur + ","; + } + + // Crate Payment Intent + PaymentIntentCreateOptions paymentIntent = new PaymentIntentCreateOptions() { + Amount = result.AmountTotal, + Currency = "usd", + Metadata = new Dictionary { + { "ordernumber", OrderNumber }, + { "user", user.ID.ToString() }, + { "products", csv }, + { "subtotal", subtotal.ToString() }, + { "total", result.AmountTotal.ToString() } + }, + StatementDescriptor = "Mistox.Net #" + OrderNumber + }; + + PaymentIntentService intentService = new PaymentIntentService(); + PaymentIntent x = await intentService.CreateAsync(paymentIntent); + + return (true, x.ClientSecret); + } catch(Exception e) { + return (false, e.ToString()); + } + + } + + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Controllers/ProductController.cs b/src/MistoxWebsite.Server/Controllers/ProductController.cs new file mode 100755 index 0000000..ff4d27a --- /dev/null +++ b/src/MistoxWebsite.Server/Controllers/ProductController.cs @@ -0,0 +1,261 @@ +using Microsoft.AspNetCore.Mvc; +using MistoxWebsite.Server.Services.DatabaseService; +using MistoxWebsite.Shared; +using Newtonsoft.Json; +using System.Security.Claims; +using System.Text; + +namespace MistoxWebsite.Server.Controllers { + [ApiController] + public class ProductController : ControllerBase { + + DatabaseService _databaseService; + + public static List CatalogItems = new List(); + + public ProductController( DatabaseService databaseService ) { + _databaseService = databaseService; + } + + [Route( "api/cart/get" )] + [HttpPost] + public async Task> GetCart( [FromBody] Account acc ) { + try { + List cart = await _databaseService.GetCart( acc ); + return cart; + } catch { + return new List(); + } + } + + [Route( "api/cart/add" )] + [HttpPost] + public async Task AddCart( [FromBody] Cart cart ) { + try { + await _databaseService.AddToCart( cart ); + }catch { + + } + } + + [Route( "api/cart/remove" )] + [HttpPost] + public async Task RemoveCart( [FromBody] Cart cart ) { + try { + await _databaseService.RemoveFromCart( cart ); + } catch { + + } + } + + [Route( "api/cart/clear" )] + [HttpPost] + public async Task ClearCart( [FromBody] Account acc ) { + try { + await _databaseService.ClearCart( acc ); + } catch { + + } + } + + [Route( "api/product/create" )] + [HttpPost] + public async Task> CreateProduct( [FromBody] Product obj ) { + try { + await _databaseService.NewProduct( obj ); + await UpdateStore(); + return "Success"; + } catch { + return "Failed"; + } + } + + [Route( "api/product/update" )] + [HttpPost] + public async Task> UpdateProduct( [FromBody] Product obj ) { + try { + await _databaseService.UpdateProduct( obj ); + await UpdateStore(); + return "Success"; + } catch { + return "Failed"; + } + } + + [Route( "api/product/get" )] + [HttpPost] + public ActionResult GetProduct( [FromBody] Product product ) { + try { + foreach( Product? prod in CatalogItems ) { + if( product.ID == prod.ID ) { + return prod; + } + } + product.ID = -1; + return product; + } catch { + return new Product(); + } + } + + [Route( "api/product/getall" )] + [HttpPost] + public ActionResult> GetAllProducts() { + try { + return CatalogItems; + } catch { + return new List(); + } + } + + [Route( "api/product/getowned" )] + [HttpPost] + public async Task>> GetOwnedProduct() { + try { + if( User.Identity != null && User.Identity.IsAuthenticated ) { + string? email = User.FindFirstValue(ClaimTypes.Email); + if( !string.IsNullOrEmpty( email ) ) { + Account? test = await _databaseService.GetAccount(email); + if( test != null ) { + List returned = await _databaseService.GetAllReceipts(test); + return returned; + } + } + } + return new List(); + } catch { + return new List(); + } + } + + DirObj RecursiveBuild( DirObj DirObj, string workingPath, List purchased ) { + + string[] files = Directory.GetFiles(workingPath); + string[] directories = Directory.GetDirectories(workingPath); + + List building = new List(); + + // Get File Names + Parallel.For( 0, files.Length, ( i ) => { + string fileName = files[i].Substring(workingPath.Length, files[i].Length - (workingPath.Length)); + building.Add( new DirObj { + Type = FileType.File, + Path = fileName + }); + } ); + + // Get Path Names + Parallel.For( 0, directories.Length, ( i ) => { + foreach( ReceiptProduct cur in purchased ) { + string dirName = directories[i].Substring(workingPath.Length, directories[i].Length - (workingPath.Length)); + if( contains( dirName, cur.product.URL ) ) { + DirObj dir = new DirObj { + Type = FileType.Directory, + Path = dirName, + }; + building.Add( dir ); + RecursiveBuild( dir, directories [i], purchased ); + } + } + } ); + + DirObj.Children = building.ToArray(); + + return DirObj; + } + + string _FolderRoot = "/home/downloads/"; + + [Route( "api/product/showdownloads" )] + [HttpPost] + public async Task ShowDownloads() { + try { + if( User.Identity != null && User.Identity.IsAuthenticated ) { + + List userClaims = User.Claims.ToList(); + int UserID = -1; + foreach( Claim claim in userClaims ) { + if( claim.Type == "ID" ) { + UserID = Convert.ToInt32( claim.Value ); + break; + } + } + + List purchased = await _databaseService.GetAllReceiptsJoinedToProduct( new Account{ ID = UserID } ); + + byte[] datapacket = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(RecursiveBuild(new DirObj { + Path = @"\", + Type = FileType.Directory, + }, _FolderRoot, purchased))); + + return new FileContentResult( datapacket, "text/html" ); + } + return Unauthorized(); + } catch { + return NotFound(); + } + } + + bool contains( string outer, string inner ) { + if ( outer.Length >= inner.Length ) { + for ( int i=0; i Download( [FromQuery] string Product ) { + try { + if( User.Identity != null && User.Identity.IsAuthenticated ) { + string? email = User.FindFirstValue(ClaimTypes.Email); + if( !string.IsNullOrEmpty( email ) ) { + Account? user = await _databaseService.GetAccount(email); + if (user != null){ + List? games = await _databaseService.GetAllProducts(); + foreach( Product product in games ) { + if ( contains( Product, product.URL ) ) { + Receipt? receipt = await _databaseService.GetReceipt(user, product); + if( receipt != null ) { + FileStream fileStream = new FileStream(_FolderRoot + Product, FileMode.Open, FileAccess.Read); + return new FileStreamResult( fileStream, "application/octet-stream" ) { + FileDownloadName = fileStream.Name + }; + } + break; + } + } + } + } + return Unauthorized(); + } + return Unauthorized(); + } catch { + return NotFound(); + } + } + + [Route( "api/product/hotreload" )] + [HttpPost] // Not implimented in admin panel + public async Task UpdateStore() { + await HotReload( _databaseService ); + } + + public static async Task HotReload( DatabaseService ds ) { + CatalogItems = new List(); + try { + CatalogItems = await ds.GetAllProducts(); + } catch { + CatalogItems.Add( new Product() { ID = 0, Name = "offline prod1", Cost = 100, Description = "offline desc" } ); + CatalogItems.Add( new Product() { ID = 1, Name = "offline prod2", Cost = 100, Description = "offline desc" } ); + CatalogItems.Add( new Product() { ID = 2, Name = "offline prod3", Cost = 100, Description = "offline desc" } ); + }; + } + + } + +} diff --git a/src/MistoxWebsite.Server/MistoxWebsite.Server.csproj b/src/MistoxWebsite.Server/MistoxWebsite.Server.csproj new file mode 100755 index 0000000..186c89e --- /dev/null +++ b/src/MistoxWebsite.Server/MistoxWebsite.Server.csproj @@ -0,0 +1,40 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MistoxWebsite.Server/Program.cs b/src/MistoxWebsite.Server/Program.cs new file mode 100755 index 0000000..2075e85 --- /dev/null +++ b/src/MistoxWebsite.Server/Program.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using MistoxWebsite.Server.Controllers; +using MistoxWebsite.Server.Services; +using MistoxWebsite.Server.Services.DatabaseService; +using Stripe; + +var builder = WebApplication.CreateBuilder(args); + +// Disable null warnings becuse string.IsNullOrEmpty checks for NULL or Empty +#pragma warning disable CS8600 +#pragma warning disable CS8604 + +// Database Service +string? _dbserver = Environment.GetEnvironmentVariable("MySQLServer"); +string dbserver = !string.IsNullOrEmpty(_dbserver) ? _dbserver : "localhost"; +string? _dbuser = Environment.GetEnvironmentVariable("MySQLUser"); +string dbUser = !string.IsNullOrEmpty(_dbuser) ? _dbuser : "root"; +string? _dbdatabase = Environment.GetEnvironmentVariable("MySQLDatabase"); +string dbdatabase = !string.IsNullOrEmpty(_dbdatabase) ? _dbdatabase : "mistox"; +string? _dbpass = Environment.GetEnvironmentVariable("MySQLPass"); +string dbPass = !string.IsNullOrEmpty(_dbpass) ? _dbpass : ""; +string connStr = "server=" + dbserver + ";user=" + dbUser + ";database=" + dbdatabase + ";password=" + dbPass + ";port=3306;"; +DatabaseService databaseService = new DatabaseService( connectionString: connStr ); +await ProductController.HotReload( databaseService ); +builder.Services.Add( new ServiceDescriptor( typeof( DatabaseService ), databaseService ) ); + +// Email Service +string? _eServer = Environment.GetEnvironmentVariable("EmailServer"); +string EmailServer = !string.IsNullOrEmpty(_eServer) ? _eServer : "smtp.gmail.com"; +string? _ePort = Environment.GetEnvironmentVariable("EmailPort"); +int EmailPort = !string.IsNullOrEmpty(_ePort) ? Convert.ToInt32(_ePort) : 587; +string? _eAddress = Environment.GetEnvironmentVariable("EmailAddress"); +string EmailAddress = !string.IsNullOrEmpty(_eAddress) ? _eAddress : ""; +string? _ePassword = Environment.GetEnvironmentVariable("EmailPassword"); +string EmailPassword = !string.IsNullOrEmpty(_ePassword) ? _ePassword : ""; +EmailService Emailservice = new EmailService( EmailServer, EmailPort, EmailAddress, EmailPassword ); +builder.Services.Add( new ServiceDescriptor( typeof( EmailService ), Emailservice )); + +// Payment Service +string? StripeKey = Environment.GetEnvironmentVariable("StripeKey"); +StripeConfiguration.ApiKey = StripeKey; + +// Authentication Service +builder.Services.AddAuthentication( options => { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; +} ).AddCookie(options => { + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.SameSite = SameSiteMode.Strict; + options.LoginPath = "/account/login"; + options.LogoutPath = "/account/logout"; + options.SlidingExpiration = true; +}); + +builder.Services.AddCors( o => o.AddDefaultPolicy( builder => { + builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); // No CORS +} ) ); + +// Pages Service +builder.Services.AddControllers(); +builder.Services.AddRazorPages(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if( app.Environment.IsDevelopment() ) { + app.UseWebAssemblyDebugging(); +} else { + app.UseHsts(); +} + +app.UseBlazorFrameworkFiles(); +app.UseStaticFiles(); + +app.UseCors(); + +app.UseAuthentication(); +app.MapControllers(); + +app.MapFallbackToFile("index.html"); + +app.Run(); diff --git a/src/MistoxWebsite.Server/Properties/launchSettings.json b/src/MistoxWebsite.Server/Properties/launchSettings.json new file mode 100755 index 0000000..258652b --- /dev/null +++ b/src/MistoxWebsite.Server/Properties/launchSettings.json @@ -0,0 +1,19 @@ +{ + "profiles": { + "IIS Express": { + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:6003", + "sslPort": 6003 + }, + "ProjectName": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:6003", + "sslPort": 6003 + } + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/Account.cs b/src/MistoxWebsite.Server/Services/DatabaseService/Account.cs new file mode 100755 index 0000000..9d0854e --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/Account.cs @@ -0,0 +1,194 @@ +using MistoxWebsite.Shared; +using MySql.Data.MySqlClient; +using System.Data; +using System.Data.Common; + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + + public async Task GetAccount( string UserNameOrEmail ) { + Account? account = null; + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * + FROM Account + Left Join WebsiteData + On Account.ID = WebsiteData.AccountID + WHERE UserName = @UorE OR Email = @UorE; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@UorE", UserNameOrEmail); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + + int _id = reader.GetInt32("ID"); + string _username = reader.GetString("UserName"); + string _email = reader.GetString("Email"); + bool _emailVerified = reader.GetBoolean("EmailVerified"); + string _passwordhash = reader.GetString("PasswordHash"); + + bool _failedpasswordlock = reader.GetBoolean( "FailedPasswordLock" ); + int _passwordattempts = reader.GetInt32( "PasswordAttempts" ); + int _curpasswordattempts = reader.GetInt32( "CurrentPasswordAttempts" ); + string _role = reader.GetString( "Role" ); + string _emailtoken = reader.GetString( "EmailToken" ); + + account = new Account() { + ID = _id, + UserName = _username, + Email = _email, + EmailVerified = _emailVerified, + PasswordHash = _passwordhash, + SiteData = new WebSiteData() { + AccountID = _id, + CurrentPasswordAttempts = _curpasswordattempts, + PasswordAttempts = _passwordattempts, + EmailToken = _emailtoken, + FailedPasswordLock = _failedpasswordlock, + Role = _role, + } + }; + } + } + } + return account; + } + + public async Task GetAccountByID( int ID ) { + Account? account = null; + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * + FROM Account + Left Join WebsiteData + On Account.ID = WebsiteData.AccountID + WHERE ID = @ID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@ID", ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _id = reader.GetInt32("ID"); + string _username = reader.GetString("UserName"); + string _email = reader.GetString("Email"); + bool _emailVerified = reader.GetBoolean("EmailVerified"); + string _passwordhash = reader.GetString("PasswordHash"); + + bool _failedpasswordlock = reader.GetBoolean( "FailedPasswordLock" ); + int _passwordattempts = reader.GetInt32( "PasswordAttempts" ); + int _curpasswordattempts = reader.GetInt32( "CurrentPasswordAttempts" ); + string _role = reader.GetString( "Role" ); + string _emailtoken = reader.GetString( "EmailToken" ); + + account = new Account() { + ID = _id, + UserName = _username, + Email = _email, + EmailVerified = _emailVerified, + PasswordHash = _passwordhash, + SiteData = new WebSiteData() { + AccountID = _id, + CurrentPasswordAttempts = _passwordattempts, + PasswordAttempts = _passwordattempts, + EmailToken = _emailtoken, + FailedPasswordLock = _failedpasswordlock, + Role = _role, + } + }; + } + } + } + return account; + } + + public async Task SetAccount( Account Update ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + UPDATE Account SET + UserName = @UserName, + Email = @Email, + EmailVerified = @EmailVerified, + PasswordHash = @PasswordHash + WHERE ID = @ID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@UserName", Update.UserName); + cmd.Parameters.AddWithValue("@Email", Update.Email); + cmd.Parameters.AddWithValue("@EmailVerified", Update.EmailVerified); + cmd.Parameters.AddWithValue("@PasswordHash", Update.PasswordHash); + cmd.Parameters.AddWithValue("@ID", Update.ID); + + await cmd.ExecuteNonQueryAsync(); + await UpdateWebsiteData( Update, Update.SiteData ); + } + } + + public async Task NewAccount( Account Profile ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + + int EmailVer = Profile.EmailVerified ? 1 : 0; + string command = @" + INSERT INTO Account + (UserName,Email,EmailVerified,PasswordHash) + VALUES + (@UserName,@Email,@EmailVerified,@PasswordHash); + + SELECT ID FROM Account + WHERE UserName = @UserName; + "; + + MySqlCommand cmd = new MySqlCommand( command , connection); + cmd.Parameters.AddWithValue("@UserName", Profile.UserName); + cmd.Parameters.AddWithValue("@Email", Profile.Email); + cmd.Parameters.AddWithValue("@EmailVerified", Profile.EmailVerified); + cmd.Parameters.AddWithValue("@PasswordHash", Profile.PasswordHash); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _id = reader.GetInt32("ID"); + Profile.ID = _id; + } + } + await NewWebsiteData( Profile, Profile.SiteData ); + } + } + + public async Task DeleteAccount( Account Profile ) { + using( MySqlConnection connection = GetConnection() ) { + MySqlCommand cmd; + connection.Open(); + + string command = @" + DELETE FROM Account WHERE ID = @ID; + DELETE FROM AccountInventory WHERE AccountID = @ID; + DELETE FROM ProjectMistData WHERE AccountID = @ID; + DELETE FROM Cart WHERE AccountID = @ID; + DELETE FROM WebsiteData WHERE AccountID = @ID; + "; + cmd = new MySqlCommand( command, connection ); + cmd.Parameters.AddWithValue("@ID", Profile.ID); + + await cmd.ExecuteNonQueryAsync(); + } + } + + } +} diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/AccountInventory.cs b/src/MistoxWebsite.Server/Services/DatabaseService/AccountInventory.cs new file mode 100755 index 0000000..43c57af --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/AccountInventory.cs @@ -0,0 +1,116 @@ +using MistoxWebsite.Shared; +using MySql.Data.MySqlClient; +using System.Data; +using System.Data.Common; + +// Account inventory needs to know whether there is already an object with the specified PK before making a new item +// If item exists already update the one that already exists + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + + public async Task> GetInventory( Account account, Product product ) { + List list = new List(); + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROM AccountInventory + WHERE AccountID = @AccountID AND ProductID = @ProductID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + cmd.Parameters.AddWithValue("@ProductID", product.ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + + string _item = reader.GetString("Item"); + int _quantity = reader.GetInt32("Quantity"); + string _stats = reader.GetString("Stats"); + + list.Add( new UserInventory() { + Item = _item, + Quantity = _quantity, + Stats = _stats + } ); + } + } + } + return list; + } + + async Task UpdateInventory( MySqlConnection connection, AccountInventory item ) { + string command = @" + UPDATE AccountInventory + SET AccountID = @AccountID, + ProductID = @ProductID, + Item = @Item, + Quantity = @Quantity, + Stats = @Stats + WHERE (AccountID = @AccountID AND ProductID = @ProductID AND Item = @Item); + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", item.AccountID); + cmd.Parameters.AddWithValue("@ProductID", item.ProductID); + cmd.Parameters.AddWithValue("@Item", item.Item); + cmd.Parameters.AddWithValue("@Quantity", item.Quantity); + cmd.Parameters.AddWithValue("@Stats", item.Stats); + + await cmd.ExecuteNonQueryAsync(); + } + + async Task NewInventory( MySqlConnection connection, AccountInventory item ) { + string command = @" + INSERT INTO AccountInventory (AccountID, ProductID, Item, Quantity, Stats) + VALUES + (@AccountID, @ProductID, @Item, @Quantity, @Stats); + "; + + MySqlCommand cmd = new MySqlCommand( command , connection); + cmd.Parameters.AddWithValue("@AccountID", item.AccountID); + cmd.Parameters.AddWithValue("@ProductID", item.ProductID); + cmd.Parameters.AddWithValue("@Item", item.Item); + cmd.Parameters.AddWithValue("@Quantity", item.Quantity); + cmd.Parameters.AddWithValue("@Stats", item.Stats); + + await cmd.ExecuteNonQueryAsync(); + } + + // Test to see if reader read does what its supposed to + // Not fully implimented + public async Task SetInventory( Account account, Product game, List Item ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + foreach( UserInventory item in Item ) { + bool exists = false; + MySqlCommand cmd = new MySqlCommand("SELECT * FROM AccountInventory WHERE AccountID = '" + account.ID + "' AND ProductID = '" + game.ID + "' AND Item = '" + item.Item.ToLower() + "'", connection); + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + exists = reader.HasRows; + } + if( exists ) { + await UpdateInventory( connection, new AccountInventory() { + AccountID = account.ID, + ProductID = game.ID, + Item = item.Item, + Quantity = item.Quantity, + Stats = item.Stats + } ); + } else { + await NewInventory( connection, new AccountInventory() { + AccountID = account.ID, + ProductID = game.ID, + Item = item.Item, + Quantity = item.Quantity, + Stats = item.Stats + } ); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/Cart.cs b/src/MistoxWebsite.Server/Services/DatabaseService/Cart.cs new file mode 100755 index 0000000..e932436 --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/Cart.cs @@ -0,0 +1,82 @@ +using MistoxWebsite.Shared; +using MySql.Data.MySqlClient; +using System.Data; +using System.Data.Common; + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + + public async Task> GetCart( Account account ) { + List list = new List(); + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROM Cart + WHERE AccountID = @AccountID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _id = reader.GetInt32("ID"); + int _accountid = reader.GetInt32("AccountID"); + int _productid = reader.GetInt32("ProductID"); + list.Add( new Cart() { + ID = _id, + AccountID = _accountid, + ProductID = _productid + } ); + } + } + } + return list; + } + + public async Task AddToCart( Cart item ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + INSERT INTO Cart + (AccountID, ProductID) + VALUES + (@AccountID, @ProductID); + "; + + MySqlCommand cmd = new MySqlCommand( command , connection); + cmd.Parameters.AddWithValue("@AccountID", item.AccountID); + cmd.Parameters.AddWithValue("@ProductID", item.ProductID); + + await cmd.ExecuteNonQueryAsync(); + } + } + + public async Task RemoveFromCart( Cart item ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = "DELETE FROM Cart WHERE AccountID=" + item.AccountID + " AND ProductID=" + item.ProductID + ";"; + MySqlCommand cmd = new MySqlCommand( command , connection); + await cmd.ExecuteNonQueryAsync(); + } + } + + public async Task ClearCart( Account account ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + DELETE FROM Cart + WHERE AccountID = @AccountID; + "; + + MySqlCommand cmd = new MySqlCommand( command , connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + + await cmd.ExecuteNonQueryAsync(); + } + } + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/DatabaseService.cs b/src/MistoxWebsite.Server/Services/DatabaseService/DatabaseService.cs new file mode 100755 index 0000000..4168370 --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/DatabaseService.cs @@ -0,0 +1,15 @@ +using MySql.Data.MySqlClient; + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + public string ConnectionString { + get; set; + } + public DatabaseService( string connectionString ) { + ConnectionString = connectionString; + } + MySqlConnection GetConnection() { + return new MySqlConnection( ConnectionString ); + } + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/PageLoad.cs b/src/MistoxWebsite.Server/Services/DatabaseService/PageLoad.cs new file mode 100755 index 0000000..d7c1d06 --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/PageLoad.cs @@ -0,0 +1,122 @@ +using MistoxWebsite.Shared; +using MySql.Data.MySqlClient; +using System.Data; +using System.Data.Common; + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + + public async Task getPageLoadObject( int AccountID ) { + PageLoadObject account = new PageLoadObject(); + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROM Account + INNER JOIN WebsiteData + ON Account.ID = WebsiteData.AccountID + WHERE ID = @AccountID + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", AccountID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _id = reader.GetInt32(0); + string _username = reader.GetString(1); + string _email = reader.GetString(2); + bool _emailVerified = reader.GetBoolean(3); + string _passwordhash = reader.GetString(4); + bool _failedPasswordLock = reader.GetBoolean(6); + int _passwordAttempts = reader.GetInt32(7); + int _currentPasswordAttempts = reader.GetInt32(8); + string _role = reader.GetString(9); + string _emailToken = reader.GetString(10); + + account.claims = new AccountClaims() { + Email = _email, + EmailVerified = _emailVerified.ToString(), + FailedPasswordLock = _failedPasswordLock.ToString(), + Role = _role, + UserName = _username, + }; + + account.user = new Account() { + ID = _id, + UserName = _username, + Email = _email, + EmailVerified = _emailVerified, + PasswordHash = _passwordhash, + SiteData = new WebSiteData() { + AccountID = _id, + CurrentPasswordAttempts = _currentPasswordAttempts, + PasswordAttempts = _passwordAttempts, + EmailToken = _emailToken, + FailedPasswordLock = _failedPasswordLock, + Role = _role, + } + + }; + } + } + + account.products = new List(); + account.receipts = new List(); + + command = @" + SELECT * FROM Product + LEFT JOIN Receipt + ON ID = Receipt.ProductID + WHERE AccountID is Null or AccountID = @AccountID; + "; + + MySqlCommand cmd2 = new MySqlCommand(command, connection); + cmd2.Parameters.AddWithValue("@AccountID", AccountID); + + using( DbDataReader reader = await cmd2.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + + int _productID = !await reader.IsDBNullAsync(0) ? reader.GetInt32(0) : -1; + string _gameName = !await reader.IsDBNullAsync(1) ? reader.GetString(1) : ""; + string _gameDesc = !await reader.IsDBNullAsync(2) ? reader.GetString(2) : ""; + string _gameImg = !await reader.IsDBNullAsync(3) ? reader.GetString(3) : ""; + int _gameCost = !await reader.IsDBNullAsync(4) ? reader.GetInt32(4) : 37707; + string _gameURL = !await reader.IsDBNullAsync(5) ? reader.IsDBNull(5) ? "" : reader.GetString(5) : "Something not common"; + int _receiptAccountID = !await reader.IsDBNullAsync(6) ? reader.IsDBNull(6) ? -1 : reader.GetInt32(6) : -1; + string _receiptID = !await reader.IsDBNullAsync(8) ? reader.IsDBNull(8) ? "" : reader.GetString(8) : ""; + DateTime _receiptTime = !await reader.IsDBNullAsync(10) ? reader.GetDateTime(10) : DateTime.Now; + + string[] _imageList = _gameImg.Split('|', StringSplitOptions.RemoveEmptyEntries); + + account.products.Add( new Product { + ID = _productID, + Cost = _gameCost, + Description = _gameDesc, + Name = _gameName, + URL = _gameURL, + Images = _imageList.ToList() + } ); + + if( _receiptAccountID != -1 ) { + account.receipts.Add( new Receipt { + AccountID = _receiptAccountID, + ProductID = _productID, + ReceiptID = _receiptID, + Time = _receiptTime + } ); + } + + } + } + } + return account; + } + + } +} diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/Product.cs b/src/MistoxWebsite.Server/Services/DatabaseService/Product.cs new file mode 100755 index 0000000..ce5d357 --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/Product.cs @@ -0,0 +1,140 @@ +using MistoxWebsite.Shared; +using MySql.Data.MySqlClient; +using System.Data; +using System.Data.Common; + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + + public async Task GetProduct( int ID ) { + Product? items = null; + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROM Product + WHERE ID = @ID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@ID", ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _id = reader.GetInt32("ID"); + string _name = reader.GetString("Name"); + string _description = reader.GetString("Description"); + string _images = reader.GetString("Images"); + int _cost = reader.GetInt32("Cost"); + string _url = reader.GetString("URL"); + + string[] _imageList = _images.Split('|', StringSplitOptions.RemoveEmptyEntries); + + items = new Product() { + ID = _id, + Name = _name, + Description = _description, + Cost = _cost, + Images = _imageList.ToList(), + URL = _url + }; + } + } + } + return items; + } + + public async Task> GetAllProducts() { + List items = new List(); + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + MySqlCommand cmd = new MySqlCommand("SELECT * FROM Product", connection); + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _id = reader.GetInt32("ID"); + string _name = reader.GetString("Name"); + string _description = reader.GetString("Description"); + string _images = reader.GetString("Images"); + int _cost = reader.GetInt32("Cost"); + string _url = reader.GetString("URL"); + + string[] _imageList = _images.Split('|', StringSplitOptions.RemoveEmptyEntries); + + items.Add( new Product() { + ID = _id, + Name = _name, + Description = _description, + Cost = _cost, + Images = _imageList.ToList(), + URL = _url + } ); + } + } + } + return items; + } + + public async Task NewProduct( Product Item ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + + string buildingImages = ""; + foreach( string cur in Item.Images ) { + buildingImages = buildingImages + "|" + cur; + } + + string command = @" + INSERT INTO Product + (Name, Description, Images, Cost, URL) + VALUES + (@Name, @Description, @Images, @Cost, @URL); + "; + + MySqlCommand cmd = new MySqlCommand( command , connection); + cmd.Parameters.AddWithValue("@Name", Item.Name); + cmd.Parameters.AddWithValue("@Description", Item.Description); + cmd.Parameters.AddWithValue("@Images", buildingImages); + cmd.Parameters.AddWithValue("@Cost", Item.Cost); + cmd.Parameters.AddWithValue("@URL", Item.URL); + + await cmd.ExecuteNonQueryAsync(); + } + } + + public async Task UpdateProduct( Product Item ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + + string buildingImages = ""; + foreach( string cur in Item.Images ) { + buildingImages = buildingImages + "|" + cur; + } + + string command = @"UPDATE Product SET + Name = @Name, + Description = @Description, + Images = @Images, + Cost = @Cost, + URL = @URL + WHERE ID = @ID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@Name", Item.Name); + cmd.Parameters.AddWithValue("@Description", Item.Description); + cmd.Parameters.AddWithValue("@Images", Item.Images); + cmd.Parameters.AddWithValue("@Cost", Item.Cost); + cmd.Parameters.AddWithValue("@URL", Item.URL); + cmd.Parameters.AddWithValue("@ID", Item.ID); + + await cmd.ExecuteNonQueryAsync(); + } + } + + } +} diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/ProjectMistData.cs b/src/MistoxWebsite.Server/Services/DatabaseService/ProjectMistData.cs new file mode 100755 index 0000000..c33ec0a --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/ProjectMistData.cs @@ -0,0 +1,71 @@ +using MistoxWebsite.Shared; +using MySql.Data.MySqlClient; +using System.Data; +using System.Data.Common; + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + + public async Task GetProjectMistData( int ID ) { + ProjectMistData? items = null; + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROM ProjectMistData + WHERE AccountID = @AccountID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _id = reader.GetInt32("AccountID"); + + items = new ProjectMistData() { + AccountID = _id, + }; + } + } + } + return items; + } + + public async Task NewProjectMistData( ProjectMistData data ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + INSERT INTO ProjectMistData + (AccountID) + VALUES + (@AccountID); + "; + + MySqlCommand cmd = new MySqlCommand( command , connection); + cmd.Parameters.AddWithValue("@AccountID", data.AccountID); + + await cmd.ExecuteNonQueryAsync(); + } + } + + public async Task UpdateProjectMistData( ProjectMistData data ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + UPDATE ProjectMistData SET + AccountID = @AccountID + WHERE AccountID = @AccountID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", data.AccountID); + + await cmd.ExecuteReaderAsync(); + } + } + + } +} diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/Receipt.cs b/src/MistoxWebsite.Server/Services/DatabaseService/Receipt.cs new file mode 100755 index 0000000..af4186f --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/Receipt.cs @@ -0,0 +1,170 @@ +using MistoxWebsite.Shared; +using MySql.Data.MySqlClient; +using System.Data; +using System.Data.Common; + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + + public async Task> GetAllReceipts( Account account ) { + List receipts = new List (); + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROM Receipt + WHERE AccountID = @AccountID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _accountid = reader.GetInt32("AccountID"); + int _gameid = reader.GetInt32("ProductID"); + string _receiptid = reader.GetString("ReceiptID"); + int _lineitem = reader.GetInt32("LineItem"); + DateTime _receiptdate = reader.GetDateTime("Time"); + int _taxamount = reader.GetInt32("TaxAmount"); + int _totalcost = reader.GetInt32("TotalCost"); + + receipts.Add( new Receipt() { + AccountID = _accountid, + ProductID = _gameid, + ReceiptID = _receiptid, + Time = _receiptdate, + TotalCost = _totalcost, + TaxAmount = _taxamount, + LineItem = _lineitem + } ); + } + } + } + return receipts; + } + + public async Task> GetAllReceiptsJoinedToProduct( Account account ) { + List join = new List (); + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROM Receipt + LEFT JOIN Product + ON Receipt.ProductID = Product.ID + WHERE AccountID = @AccountID + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _accountid = !reader.IsDBNull( "AccountID" ) ? reader.GetInt32("AccountID") : -1; + int _gameid = !reader.IsDBNull( "ProductID" ) ? reader.GetInt32("ProductID") : 0; + string _receiptid = !reader.IsDBNull( "ReceiptID" ) ? reader.GetString("ReceiptID") : ""; + int _lineitem = !reader.IsDBNull( "LineItem" ) ? reader.GetInt32("LineItem") : 0; + DateTime _receiptdate = !reader.IsDBNull( "Time" ) ? reader.GetDateTime("Time") : DateTime.Now; + int _taxamount = !reader.IsDBNull( "TaxAmount" ) ? reader.GetInt32("TaxAmount") : 0; + int _totalcost = !reader.IsDBNull( "TotalCost" ) ? reader.GetInt32("TotalCost") : 0; + int _id = !reader.IsDBNull( "ID" ) ? reader.GetInt32("ID") : 0; + string _name = !reader.IsDBNull( "Name" ) ? reader.GetString("Name") : ""; + string _desc = !reader.IsDBNull( "Description" ) ? reader.GetString("Description") : ""; + int _cost = !reader.IsDBNull( "Cost" ) ? reader.GetInt32("Cost") : 0; + string _url = !reader.IsDBNull( "URL" ) ? reader.GetString("URL") : "Something Random That Wont Ever Be In A URL"; + + join.Add( new ReceiptProduct() { + receipt = new Receipt { + AccountID = _accountid, + ProductID = _gameid, + ReceiptID = _receiptid, + Time = _receiptdate, + TotalCost = _totalcost, + TaxAmount = _taxamount, + LineItem = _lineitem + }, + product = new Product() { + ID = _id, + Cost = _cost, + Description = _desc, + Name = _name, + URL = _url + } + } ); + } + } + } + return join; + } + + public async Task GetReceipt( Account account, Product game ) { + Receipt? receipt = null; + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROMReceipt + WHERE AccountID = @AccountID AND ProductID = @ProductID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + cmd.Parameters.AddWithValue("@ProductID", game.ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + int _accountid = reader.GetInt32("AccountID"); + int _gameid = reader.GetInt32("ProductID"); + string _receiptid = reader.GetString("ReceiptID"); + int _lineitem = reader.GetInt32("LineItem"); + DateTime _receiptdate = reader.GetDateTime("Time"); + int _taxamount = reader.GetInt32("TaxAmount"); + int _totalcost = reader.GetInt32("TotalCost"); + + receipt = new Receipt() { + AccountID = _accountid, + ProductID = _gameid, + ReceiptID = _receiptid, + Time = _receiptdate, + TotalCost = _totalcost, + TaxAmount = _taxamount, + LineItem = _lineitem + }; + } + } + } + return receipt; + } + + public async Task NewReceipt( Receipt receipt ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + INSERT INTO Receipt + (AccountID, ProductID, ReceiptID, LineItem, TaxAmount, TotalCost, Time) + VALUES + (@AccountID, @ProductID, @ReceiptID, @LineItem, @TaxAmount, @TotalCost, @Time) + "; + + MySqlCommand cmd = new MySqlCommand( command , connection); + cmd.Parameters.AddWithValue("@AccountID", receipt.AccountID); + cmd.Parameters.AddWithValue("@ProductID", receipt.ProductID); + cmd.Parameters.AddWithValue("@ReceiptID", receipt.ReceiptID); + cmd.Parameters.AddWithValue("@LineItem", receipt.LineItem); + cmd.Parameters.AddWithValue("@TaxAmount", receipt.TaxAmount); + cmd.Parameters.AddWithValue("@TotalCost", receipt.TotalCost); + cmd.Parameters.AddWithValue("@Time", receipt.Time); // Just incase i need this in the future | receipt.Time.ToString( "yyyy-MM-dd hh:mm:ss" ) + + await cmd.ExecuteNonQueryAsync(); + } + } + + } +} diff --git a/src/MistoxWebsite.Server/Services/DatabaseService/WebsiteData.cs b/src/MistoxWebsite.Server/Services/DatabaseService/WebsiteData.cs new file mode 100755 index 0000000..5911559 --- /dev/null +++ b/src/MistoxWebsite.Server/Services/DatabaseService/WebsiteData.cs @@ -0,0 +1,115 @@ +using MistoxWebsite.Shared; +using MySql.Data.MySqlClient; +using System.Data; +using System.Data.Common; + +namespace MistoxWebsite.Server.Services.DatabaseService { + public partial class DatabaseService { + + public async Task GetWebsiteData( Account account ) { + WebSiteData? webSiteData = null; + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + SELECT * FROM WebsiteData + WHERE AccountID = @AccountID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + + using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { + while( await reader.ReadAsync() ) { + if( reader == null ) { + break; + } + + int _id = 0; + bool _failedpasswordlock = false; + int _passwordattempts = 5; + int _curpasswordattempts = 0; + string _role = ""; + string _emailtoken = ""; + + if( !reader.IsDBNull( "AccountID" ) ) { + _id = reader.GetInt32( "AccountID" ); + } + if( !reader.IsDBNull( "FailedPasswordLock" ) ) { + _failedpasswordlock = reader.GetBoolean( "FailedPasswordLock" ); + } + if( !reader.IsDBNull( "PasswordAttempts" ) ) { + _passwordattempts = reader.GetInt32( "PasswordAttempts" ); + } + if( !reader.IsDBNull( "CurrentPasswordAttempts" ) ) { + _curpasswordattempts = reader.GetInt32( "CurrentPasswordAttempts" ); + } + if( !reader.IsDBNull( "Role" ) ) { + _role = reader.GetString( "Role" ); + } + if( !reader.IsDBNull( "EmailToken" ) ) { + _emailtoken = reader.GetString( "EmailToken" ); + } + + webSiteData = new WebSiteData() { + AccountID = _id, + FailedPasswordLock = _failedpasswordlock, + CurrentPasswordAttempts = _curpasswordattempts, + PasswordAttempts = _passwordattempts, + EmailToken = _emailtoken, + Role = _role, + }; + } + } + } + return webSiteData; + } + + public async Task NewWebsiteData( Account account, WebSiteData data ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + INSERT INTO WebsiteData + (AccountID, FailedPasswordLock, PasswordAttempts, CurrentPasswordAttempts, Role, EmailToken) + VALUES + (@AccountID, @FailedPasswordLock, @PasswordAttempts, @CurrentPasswordAttempts, @Role, @EmailToken); + "; + + MySqlCommand cmd = new MySqlCommand( command , connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + cmd.Parameters.AddWithValue("@FailedPasswordLock", data.FailedPasswordLock); + cmd.Parameters.AddWithValue("@PasswordAttempts", data.PasswordAttempts); + cmd.Parameters.AddWithValue("@CurrentPasswordAttempts", data.CurrentPasswordAttempts); + cmd.Parameters.AddWithValue("@Role", data.Role); + cmd.Parameters.AddWithValue("@EmailToken", data.EmailToken); + + await cmd.ExecuteNonQueryAsync(); + } + } + + public async Task UpdateWebsiteData( Account account, WebSiteData data ) { + using( MySqlConnection connection = GetConnection() ) { + connection.Open(); + string command = @" + UPDATE WebsiteData SET + FailedPasswordLock = @FailedPasswordLock, + PasswordAttempts = @PasswordAttempts, + CurrentPasswordAttempts = @CurrentPasswordAttempts, + Role = @Role, + EmailToken = @EmailToken + WHERE AccountID = @AccountID; + "; + + MySqlCommand cmd = new MySqlCommand(command, connection); + cmd.Parameters.AddWithValue("@AccountID", account.ID); + cmd.Parameters.AddWithValue("@FailedPasswordLock", data.FailedPasswordLock); + cmd.Parameters.AddWithValue("@PasswordAttempts", data.PasswordAttempts); + cmd.Parameters.AddWithValue("@CurrentPasswordAttempts", data.CurrentPasswordAttempts); + cmd.Parameters.AddWithValue("@Role", data.Role); + cmd.Parameters.AddWithValue("@EmailToken", data.EmailToken); + + await cmd.ExecuteNonQueryAsync(); + } + } + + } +} diff --git a/src/MistoxWebsite.Server/Services/EmailService/EmailService.cs b/src/MistoxWebsite.Server/Services/EmailService/EmailService.cs new file mode 100755 index 0000000..77ba15b --- /dev/null +++ b/src/MistoxWebsite.Server/Services/EmailService/EmailService.cs @@ -0,0 +1,41 @@ +using System.Net.Mail; + +namespace MistoxWebsite.Server.Services { + public partial class EmailService { + + public Dictionary _SentEmails = new Dictionary(); + + public string EmailServer = ""; + public string EmailAddress = ""; + public string EmailPassword = ""; + public int EmailPort; + + public EmailService( string _EmailServer, int _EmailPort, string _EmailAddress, string _EmailPassword ) { + EmailServer = _EmailServer; + EmailPort = _EmailPort; + EmailAddress = _EmailAddress; + EmailPassword = _EmailPassword; + } + + public string Send( string Destination, string Subject, string Body ) { + using (SmtpClient client = new SmtpClient( EmailServer, EmailPort )){ + client.EnableSsl = true; + client.Credentials = new System.Net.NetworkCredential( EmailAddress, EmailPassword ); + try { + MailMessage msg = new MailMessage(){ + IsBodyHtml = true, + Subject = Subject, + Body = Body + }; + msg.From = new MailAddress( EmailAddress, "no-reply" ); + msg.To.Add( new MailAddress( Destination ) ); + client.Send( msg ); + return "Success"; + } catch( Exception e ) { + return "An Error Has Occurred Sending Email : " + e.ToString(); + } + } + } + + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Services/EmailService/ResetPasswordEmail.cs b/src/MistoxWebsite.Server/Services/EmailService/ResetPasswordEmail.cs new file mode 100755 index 0000000..8d075bc --- /dev/null +++ b/src/MistoxWebsite.Server/Services/EmailService/ResetPasswordEmail.cs @@ -0,0 +1,53 @@ +using System.Net.Mail; + +namespace MistoxWebsite.Server.Services { + public partial class EmailService { + +// @UserName +// @ResetPassWord +// https://mistox.com/account/resetpassword?UserName=@UserName&ResetPwd=@ResetPassWord + + public static string ResetPasswordSubject = "Password Reset Request"; + public static string ResetPasswordEmail = @" + + + + + + Password Reset + + + + + + +
+ + + + + + + + + + +
+

Password Reset Request

+
+

Hi @UserName,

+

We received a request to reset your password. You can reset your password by clicking the button below:

+

+ Reset Password +

+

If you didn't request a password reset, you can safely ignore this email.

+

Best regards

+
+

If you have any questions, feel free to contact support.

+
+
+ +"; + + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Services/EmailService/VerifyEmail.cs b/src/MistoxWebsite.Server/Services/EmailService/VerifyEmail.cs new file mode 100755 index 0000000..a22b4a6 --- /dev/null +++ b/src/MistoxWebsite.Server/Services/EmailService/VerifyEmail.cs @@ -0,0 +1,54 @@ +using System.Net.Mail; + +namespace MistoxWebsite.Server.Services { + public partial class EmailService { + +// @UserName +// @VerifyPassword +// https://mistox.com/api/account/verifyemail?UserName=@UserName&Guid=@VerifyPassword + + public static string VerifyEmailSubject = "Verify Your Email Address"; + public static string VerifyEmailEmail = @" + + + + + + Verify Your Email + + + + + + +
+ + + + + + + + + + +
+

Verify Email Request

+
+

Hi @UserName,

+

Thank you for making an account with us:

+

In order to start using your account we need to verify your email address by clicking the link below:

+

+ Verify Email +

+

If you didn't create an account please ignore this email.

+

Best regards

+
+

If you have any questions, feel free to contact support.

+
+
+ +"; + + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/wwwroot/PaymentFrame.html b/src/MistoxWebsite.Server/wwwroot/PaymentFrame.html new file mode 100755 index 0000000..1b53f63 --- /dev/null +++ b/src/MistoxWebsite.Server/wwwroot/PaymentFrame.html @@ -0,0 +1,179 @@ + + + + + Stripe-Payments + + + + + +
+ +
+ +
+ + +
+ + + + \ No newline at end of file diff --git a/src/MistoxWebsite.Server/wwwroot/Snake.html b/src/MistoxWebsite.Server/wwwroot/Snake.html new file mode 100755 index 0000000..f3eac73 --- /dev/null +++ b/src/MistoxWebsite.Server/wwwroot/Snake.html @@ -0,0 +1,192 @@ + + + HTML_Snake + + + +

Score : 0

+
+ +
+ +
+

LEADERBOARD

+
+
+
+

Designed by Derek in California

+ + \ No newline at end of file diff --git a/src/MistoxWebsite.Server/wwwroot/css/app.css b/src/MistoxWebsite.Server/wwwroot/css/app.css new file mode 100755 index 0000000..eacfdce --- /dev/null +++ b/src/MistoxWebsite.Server/wwwroot/css/app.css @@ -0,0 +1,178 @@ +:root { + --Mistox-Dark: #2C0703; + --Mistox-Medium: #890620; + --Mistox-Light: #B6465F; + --Mistox-Bright: #FC440F; + --Mistox-Offset: #443B75; + --Mistox-Background: #320000; + --Mistox-White: #FFF; + --Mistox-Black: #000; +} + +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +main { + background-color: #000000; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='hexagons' fill='%23ff0000' fill-opacity='0.2' fill-rule='nonzero'%3E%3Cpath d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +/* CSS used for the Account Activity Pages */ + +.center { + position: relative; + left: 50%; + top: 50vh; + transform: translateY(-50%) translateX(-50%); +} + +.horizontal-center { + position: relative; + left: 50%; + transform: translateX(-50%); +} + +.vertical-center { + position: relative; + top: 50vh; + transform: translateY(-50%); +} + +.background-border { + border: var(--Mistox-Background) 2px solid; + border-radius: 6px; +} + +.big-frame { + background-color: var(--Mistox-Black); + padding: 4px; + max-width: 400px; + color: var(--Mistox-White); + transition-duration: 1s; +} + +.big-frame h3{ + margin: 15px 0 30px 0; + color: var(--Mistox-White); + text-align: center; +} + +.big-frame h2{ + text-align: center; + position: relative; + margin: 0; + top: -20px; + font-size: 15px; + color: var(--Mistox-Bright); +} + +.big-frame .frame-item label{ + position: relative; + padding: 10px 0; + font-size: 16px; + color: var(--Mistox-White); + pointer-events: none; + transition: .5s; + top: -70px; + left: 20px; +} + + .big-frame .frame-item input:autofill, + .big-frame .frame-item input:-webkit-autofill, + .big-frame .frame-item input { + position: relative; + width: calc(100% - 40px); + margin: 0 20px; + padding: 10px 0; + font-size: 15px; + color: var(--Mistox-White); + margin-bottom: 30px; + border: none; + border-bottom: 1px solid var(--Mistox-White); + outline: none; + background: transparent; + } + + .big-frame .frame-item input:focus ~ label, + .big-frame .frame-item input:not(:placeholder-shown) ~ label { + top: -95px; + left: 10px; + color: var(--Mistox-Light); + font-size: 12px; + } + +.flex-row{ + display: flex; + flex-direction: row; + justify-content: space-around; +} + +.sub-frame { + text-align: center; + padding: 1px 0; +} + +.submit{ + position: relative; + padding: 10px 20px; + color: var(--Mistox-Black); + background-color: var(--Mistox-Light); + font-size: 16px; + text-decoration: none; + text-transform: uppercase; + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.5s ease; + letter-spacing: 4px; + border: 1px solid var(--Mistox-Light); + margin: auto; + border-radius: 5px; +} + + .submit:hover { + background-color: var(--Mistox-Light); + color: var(--Mistox-White); + box-shadow: 4px 3px 6px var(--Mistox-Dark); + } + + .submit:active { + transform: translate( 4px, 2px ); + background-color: var(--Mistox-Dark); + border: none; + color: var(--Mistox-White); + box-shadow: none; + } + +ul { + list-style: none; + color: var(--Mistox-Bright); +} \ No newline at end of file diff --git a/src/MistoxWebsite.Server/wwwroot/favicon.ico b/src/MistoxWebsite.Server/wwwroot/favicon.ico new file mode 100755 index 0000000..63e859b Binary files /dev/null and b/src/MistoxWebsite.Server/wwwroot/favicon.ico differ diff --git a/src/MistoxWebsite.Server/wwwroot/icon-192.png b/src/MistoxWebsite.Server/wwwroot/icon-192.png new file mode 100755 index 0000000..166f56d Binary files /dev/null and b/src/MistoxWebsite.Server/wwwroot/icon-192.png differ diff --git a/src/MistoxWebsite.Server/wwwroot/img/MistoxLogo.png b/src/MistoxWebsite.Server/wwwroot/img/MistoxLogo.png new file mode 100755 index 0000000..7e7e44c Binary files /dev/null and b/src/MistoxWebsite.Server/wwwroot/img/MistoxLogo.png differ diff --git a/src/MistoxWebsite.Server/wwwroot/img/ResumeFace.jpg b/src/MistoxWebsite.Server/wwwroot/img/ResumeFace.jpg new file mode 100755 index 0000000..66546cd Binary files /dev/null and b/src/MistoxWebsite.Server/wwwroot/img/ResumeFace.jpg differ diff --git a/src/MistoxWebsite.Server/wwwroot/img/cart.png b/src/MistoxWebsite.Server/wwwroot/img/cart.png new file mode 100755 index 0000000..1e52dd2 Binary files /dev/null and b/src/MistoxWebsite.Server/wwwroot/img/cart.png differ diff --git a/src/MistoxWebsite.Server/wwwroot/index.html b/src/MistoxWebsite.Server/wwwroot/index.html new file mode 100755 index 0000000..48547f9 --- /dev/null +++ b/src/MistoxWebsite.Server/wwwroot/index.html @@ -0,0 +1,46 @@ + + + + + + + MistoxNet + + + + + + + + +
+
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + diff --git a/src/MistoxWebsite.Server/wwwroot/js/screenwidth.js b/src/MistoxWebsite.Server/wwwroot/js/screenwidth.js new file mode 100755 index 0000000..180e127 --- /dev/null +++ b/src/MistoxWebsite.Server/wwwroot/js/screenwidth.js @@ -0,0 +1,6 @@ +window.getWindowSize = function() { + return { + width: window.innerWidth, + height: window.innerHeight + }; +}; \ No newline at end of file