diff --git a/Dockerfile b/Dockerfile new file mode 100644 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 100644 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 100644 index 0000000..6eda85f --- /dev/null +++ b/ToDo.txt @@ -0,0 +1,24 @@ +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 + +account/resetpassword + make the account your resetting password for visible in ui + Passwords aren't obscured + After sucess and 3 seconds pass, change location to login frame + +ForgotPassword + No notice something is happening when button pressed + +ForgotPassword Email / Resetpassword Email + Needs styles that match the theme of the website \ No newline at end of file diff --git a/database/Dockerfile b/database/Dockerfile new file mode 100644 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 100644 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 100644 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/App.razor b/src/MistoxWebsite.Client/App.razor new file mode 100644 index 0000000..e7f61e6 --- /dev/null +++ b/src/MistoxWebsite.Client/App.razor @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MistoxWebsite.Client/App.razor.css b/src/MistoxWebsite.Client/App.razor.css new file mode 100644 index 0000000..68bdb82 --- /dev/null +++ b/src/MistoxWebsite.Client/App.razor.css @@ -0,0 +1,2 @@ +/* This file is needed for compile for some reason */ +/* Dont delete this file */ \ No newline at end of file diff --git a/src/MistoxWebsite.Client/MistoxWebsite.Client.csproj b/src/MistoxWebsite.Client/MistoxWebsite.Client.csproj new file mode 100644 index 0000000..8cf112e --- /dev/null +++ b/src/MistoxWebsite.Client/MistoxWebsite.Client.csproj @@ -0,0 +1,31 @@ + + + + net9.0 + enable + enable + + + + + + + + + true + PreserveNewest + + + + + + + + + + + + + + + diff --git a/src/MistoxWebsite.Client/Pages/About.razor b/src/MistoxWebsite.Client/Pages/About.razor new file mode 100644 index 0000000..c2787af --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/About.razor @@ -0,0 +1,38 @@ +@page "/About" + +About + + + +
+

Welcome to Mistox LLC. A project and hobby of Derek Holloway.

+
+

I am an indi-developer who has been making small projects since I was 13. I originally learned lua and spent 4 years mastering it. Then I moved onto C# which is my preferred language

+

My programming catalog consist of C#, Lua, SQL, C++, C, and JavaScript in the order of knowledge from best to passiable.

+

Im currently in college for computer sciences and should honestly be doing that instead of this but I find working on this website and hobby games to be way more enjoyable.

+
+

I would love to learn how to use Blender in order to make all the models for my games but with the amount of work ive already made for myself im going to hold off for now.

+

This website and everything on it are the long countless hours of my time and motivation to create something that I can be proud of and share that with the world.

+

So if you would like to support me as a small creator please feel free to leave a donation from on the store page. It would means a lot to me.

+
+

For the nerds out there, this website is a blazor webassembly app, hosted on an ubuntu webserver, with a mysql backend.

+

All the passwords are encrypted using bcrypt for your safety and all the data is only allowed through SSL.

+

After you make your account. All the data in the database is easily accessable through the account settings and

+

you can delete your account at any time. Including all your data with it so there is no risk.

+

I wont show ads and never will and I refuse to use trackers on this site.

+
+
+

If you have any questions, concerns, or would like to suggest a feature, bug-fix, or request to help. Please feel

+

free to reach out to me at derek@mistox.net

+ + + Buy Me a Coffee at ko-fi.com + + +
\ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Account/ActivityPages/ForgotPassword.razor b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/ForgotPassword.razor new file mode 100644 index 0000000..10c3714 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/ForgotPassword.razor @@ -0,0 +1,43 @@ +@page "/account/forgotpassword" + +Forgot + +
+

Forgot Password

+
+ + +
+
+
+
+ +
+
+
+ +
+ +@code { + + public string Email{ get; set; } = ""; + public string Result{ get; set; } = ""; + + public async Task OnKeyDown(KeyboardEventArgs e) { + if (e.Key == "Enter") { + await TrySendCode(); + } + } + + public async Task TrySendCode() { + + HttpResponseMessage TestLogin = await Http.PostAsJsonAsync("api/account/sendresetpassword", new Account(){ Email = Email }); + Result = await TestLogin.Content.ReadAsStringAsync(); + + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/ActivityPages/Login.razor b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/Login.razor new file mode 100644 index 0000000..ffda1eb --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/Login.razor @@ -0,0 +1,89 @@ +@page "/account/login" + +Login + +
+

Login

+
+ + +
+
+ + +
+
+
+ +
+
+
+ Stay Logged In + +
+ +
+
+ +
+ +@code { + [Parameter] + [SupplyParameterFromQuery] + public string? ReturnURL { get; set; } + string UserName { get; set; } = ""; + string Password { get; set; } = ""; + string Loading { get; set; } = ""; + bool StayLoggedIn{ get; set; } + List ErrorMsgs = new List(); + + public async Task OnKeyDown(KeyboardEventArgs e){ + if (e.Key == "Enter") { + await TryLogin(); + } + } + + public async Task TryLogin() { + Loading = "Waiting for login response from server"; + ReturnURL = string.IsNullOrEmpty(ReturnURL) ? "/" : ReturnURL; + ErrorMsgs = new List(); + + if(UserName != null ) { + if(Password != null ) { + if (Password.Length >= 6 ) { + HttpResponseMessage TestLogin = await Http.PostAsJsonAsync("api/account/login", new MistoxWebsite.Shared.Account(){ UserName = UserName, PasswordHash = Password, EmailVerified = StayLoggedIn }); + string result = await TestLogin.Content.ReadAsStringAsync(); + Account? user = JsonConvert.DeserializeObject(result); + if (user == null ) { + ErrorMsgs.Add("No response from the server"); + base.StateHasChanged(); + return; + } + if ( string.IsNullOrEmpty(user.Error) ) { + ErrorMsgs.Add("Login Success"); + Nav.NavigateTo("/", true); + } else { + ErrorMsgs.Add(user.Error); + } + Loading = ""; + } else { + ErrorMsgs.Add("Password must be at least 6 Characters long"); + } + } else { + ErrorMsgs.Add("The 'password' field is required"); + } + } else{ + ErrorMsgs.Add("The 'username' field is required"); + } + Loading = ""; + base.StateHasChanged(); + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/ActivityPages/Register.razor b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/Register.razor new file mode 100644 index 0000000..9b78446 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/Register.razor @@ -0,0 +1,116 @@ +@page "/account/register" + +Register + +
+

Register

+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ +
+
+
+
    +

    @Loading

    + @foreach(string msg in ErrorMsgs ) { +
  • @msg
  • + } +
+
+ +@code { + [Parameter] + [SupplyParameterFromQuery] + public string? ReturnURL { get; set; } + + string Email { get; set; } = ""; + string UserName { get; set; } = ""; + string Password { get; set; } = ""; + string Loading { get; set; } = ""; + List ErrorMsgs = new List(); + + public async Task OnKeyDown(KeyboardEventArgs e){ + if (e.Key == "Enter") { + if (string.IsNullOrEmpty(Email) || string.IsNullOrEmpty(UserName) || string.IsNullOrEmpty( Password )) { + await Task.Delay( 100 ); + } + await TryRegister(); + } + } + + public bool CheckEmail(string email ) { + int ATcount = 0; + int DOTcount = 0; + char[] cmail = email.ToArray(); + foreach(char cur in cmail ) { + if(cur == '@' ) { + ATcount += 1; + }else if(cur == '.' ) { + DOTcount += 1; + } + } + if (ATcount == 1 && DOTcount >= 1 ) { + return true; + } + return false; + } + + public async Task TryRegister() { + Loading = "Waiting for a response from the server"; + ReturnURL = string.IsNullOrEmpty(ReturnURL) ? "/" : ReturnURL; + + ErrorMsgs = new List(); + + if (Email != null){ + if( CheckEmail( Email ) ) { + if(UserName != null ) { + if(Password != null ) { + if (Password.Length >= 6 ) { + HttpResponseMessage TestRegister = await Http.PostAsJsonAsync("api/account/register", new Account(){ + UserName = UserName, + Email = Email, + PasswordHash = Password, + EmailVerified = false, + }); + Account? user = await TestRegister.Content.ReadFromJsonAsync(); + if ( string.IsNullOrEmpty(user?.Error) ) { + ErrorMsgs.Add("Register Success"); + Nav.NavigateTo("/", true); + } else { + ErrorMsgs.Add( user.Error ); + } + Loading = ""; + }else{ + ErrorMsgs.Add("Password must be at least 6 Characters long"); + } + }else{ + ErrorMsgs.Add("The 'password' field is required"); + } + }else{ + ErrorMsgs.Add("The 'username' field is required"); + } + }else{ + ErrorMsgs.Add("Please check your email address"); + } + }else{ + ErrorMsgs.Add("The 'email' field is required"); + } + Loading = ""; + base.StateHasChanged(); + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/ActivityPages/ResetPassword.razor b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/ResetPassword.razor new file mode 100644 index 0000000..a33c272 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/ResetPassword.razor @@ -0,0 +1,57 @@ +@page "/account/resetpassword" + +Reset Password + +
+

Reset Password

+
+ + +
+
+ + +
+
+
+
+ +
+
+
+
    + @if (!string.IsNullOrEmpty(Result)){ +
  • @Result
  • + } +
+
+ +@code { + + [Parameter] + [SupplyParameterFromQuery] + public string UserName { get; set; } = ""; + + [Parameter] + [SupplyParameterFromQuery] + public string ResetPwd { get; set; } = ""; + + public string NewPassword{ get; set; } = ""; + public string RepeatPassword{ get; set; } = ""; + + public string Result{ get; set; } = ""; + + public async Task OnKeyDown(KeyboardEventArgs e ) { + if (e.Key == "Enter") { + await TryChange(); + } + } + + protected async Task TryChange() { + + HttpResponseMessage TestLogin = await Http.PostAsJsonAsync("api/account/resetpassword", new Account(){ UserName = UserName, PasswordHash = NewPassword, Error = ResetPwd }); + string result = await TestLogin.Content.ReadAsStringAsync(); + Result = result == "true" ? "Password changed successfully" : "Something is wrong"; + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/ActivityPages/VerifyEmail.razor b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/VerifyEmail.razor new file mode 100644 index 0000000..4011561 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/ActivityPages/VerifyEmail.razor @@ -0,0 +1,43 @@ +@page "/account/verifyemail" + +Verifying Email + +
+

@Result

+
+
+
+ +
+
+
+
+ +@code { + + [Parameter] + [SupplyParameterFromQuery] + public string Guid { get; set; } = ""; + + [Parameter] + [SupplyParameterFromQuery] + public string UserName{ get; set; } = ""; + + public string Result{ get; set; } = ""; + + protected override async Task OnInitializedAsync() { + HttpResponseMessage Query = await Http.PostAsJsonAsync("api/account/verifyemail", new Account(){ UserName = UserName, PasswordHash = Guid }); + bool Answer = await Query.Content.ReadFromJsonAsync(); + if (Answer == true ) { + Result = "Verified Email Successfully"; + } else { + Result = "Email was not able to be verified please resend email"; + } + base.StateHasChanged(); + } + + public void ReturnToLogin(){ + this.Nav.NavigateTo("/account/login"); + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/Manage/Account.razor b/src/MistoxWebsite.Client/Pages/Account/Manage/Account.razor new file mode 100644 index 0000000..8f2c0ee --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/Manage/Account.razor @@ -0,0 +1,123 @@ + +
+
+ @if(context.User.FindFirst("EmailVerified")?.Value == "True" ) { +

Email Is Verified Successfully

+ } else { + + } +
+

@EmailSentResult

+
+ +
+
+

Reset Your Password

+
+ + + + +

@PasswordErrorText

+
+ +
+
+

Login Counter

+
+

This will lock the account after the specified number of failed logins

+

You will have to reset the password using your email aftwards

+ @if( _account != null && _account.EmailVerified && FailedLoginToggle ) { +

Failed login attempts

+ } else if (_account != null && !_account.EmailVerified) { +

This feature cannot be enabled until you verify your email

+ } +

Enabled

+ +

@LoginCounterResult

+
+ +
+ +@code { + + public MistoxWebsite.Shared.Account? _account = null; + + public int MaxFailedLogin = 0; + public bool FailedLoginToggle = false; + public string LoginCounterResult = ""; + + public async Task SubmitLoginLock() { + if (_account != null ) { + _account.SiteData.FailedPasswordLock = FailedLoginToggle; + _account.SiteData.PasswordAttempts = MaxFailedLogin; + _account.PasswordHash = ""; + HttpResponseMessage SendVerifyEmail = await Http.PostAsJsonAsync("api/account/toggleAccountLock", _account); + LoginCounterResult = await SendVerifyEmail.Content.ReadAsStringAsync(); + } + } + + protected override async Task OnInitializedAsync() { + HttpResponseMessage x = await Http.PostAsync("api/account/get", new StringContent("")); + string body = await x.Content.ReadAsStringAsync(); + _account = JsonConvert.DeserializeObject(body); + if (_account != null){ + FailedLoginToggle = _account.SiteData.FailedPasswordLock; + MaxFailedLogin = _account.SiteData.PasswordAttempts; + } + } + + /* Verify Email Code */ + + public string EmailSentResult = ""; + + public async Task SendVerifyEmail() { + if (_account != null){ + HttpResponseMessage SendVerifyEmail = await Http.PostAsJsonAsync("api/account/sendverifyemail", new MistoxWebsite.Shared.Account(){ UserName = _account.UserName }); + bool result = await SendVerifyEmail.Content.ReadFromJsonAsync(); + if (result == true ) { + EmailSentResult = "Email Sent"; + } else { + EmailSentResult = "Problem Sending Email"; + } + } + } + + /* New Password Code */ + + public string CurPass { get; set; } = ""; + public string NewPass1 { get; set; } = ""; + public string NewPass2 { get; set; } = ""; + public string PasswordErrorText { get; set; } = ""; + + protected async Task TestNewPass() { + if ( string.IsNullOrEmpty(CurPass) ) { + PasswordErrorText = "The Current Password Is Required"; + return; + } + if ( string.IsNullOrEmpty(NewPass1) ) { + PasswordErrorText = "The New Password Is Required"; + return; + } + if ( string.IsNullOrEmpty(NewPass2) ) { + PasswordErrorText = "The New Repeated Password Is Required"; + return; + } + if (NewPass1 != NewPass2 ) { + PasswordErrorText = "The New Passwords Dont Match"; + return; + } + if (_account != null){ + HttpResponseMessage TryChangePassword = await Http.PostAsJsonAsync("api/account/changepassword", new MistoxWebsite.Shared.Account(){ UserName = _account.UserName, PasswordHash = CurPass, Error = NewPass1 }); + bool resultText = await TryChangePassword.Content.ReadFromJsonAsync(); + if (resultText == true ) { + PasswordErrorText = "Password changed successfully"; + } else { + PasswordErrorText = "Current password is wrong"; + } + } + + + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/Manage/Account.razor.css b/src/MistoxWebsite.Client/Pages/Account/Manage/Account.razor.css new file mode 100644 index 0000000..db5ba43 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/Manage/Account.razor.css @@ -0,0 +1,15 @@ +.subject-frame{ + background-color: rgba(255,255,255,.1); + border-radius: 4px; + padding: 5px; + margin-bottom: 1.4rem; +} + +.title-frame{ + +} + +.title-frame h1 input{ + padding-left: 1.4rem; + padding-top: 0.4rem; +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Account/Manage/Manage.razor b/src/MistoxWebsite.Client/Pages/Account/Manage/Manage.razor new file mode 100644 index 0000000..b55c601 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/Manage/Manage.razor @@ -0,0 +1,38 @@ +@page "/account/manage" +@attribute [Authorize] + +@using MistoxWebsite.Client.Pages.Account.Manage + +Manage + + + +
+ @if (Visible == ShowingFrame.Account) { + + } else if (Visible == ShowingFrame.Data) { + + } +
+ + +@code { + TitleBarDiv[] tbd = new TitleBarDiv[2]; + + protected override void OnInitialized() { + + tbd[0] = new TitleBarDiv{ Name = "Data" }; + tbd[0].onClicked += ( object? o, EventArgs e ) => { Visible = ShowingFrame.Data; base.StateHasChanged(); }; + + tbd[1] = new TitleBarDiv{ Name = "Account" }; + tbd[1].onClicked += ( object? o, EventArgs e ) => { Visible = ShowingFrame.Account; base.StateHasChanged(); }; + + } + + public ShowingFrame Visible = ShowingFrame.Account; + public enum ShowingFrame { + Account, + Data, + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/Manage/Manage.razor.css b/src/MistoxWebsite.Client/Pages/Account/Manage/Manage.razor.css new file mode 100644 index 0000000..4574756 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/Manage/Manage.razor.css @@ -0,0 +1,6 @@ +body { +} + +.lower-frame{ + padding: 1.4rem; +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Account/Manage/ProjectMistData.razor b/src/MistoxWebsite.Client/Pages/Account/Manage/ProjectMistData.razor new file mode 100644 index 0000000..a194195 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/Manage/ProjectMistData.razor @@ -0,0 +1,10 @@ + +
+

MistDataJSON

+ +
+
+ +@code { + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/Manage/WebsiteData.razor b/src/MistoxWebsite.Client/Pages/Account/Manage/WebsiteData.razor new file mode 100644 index 0000000..310cbfe --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/Manage/WebsiteData.razor @@ -0,0 +1,183 @@ + + @if (showDeleteConfirm){ +
+
+

Are you sure?

+

There is no way to recover your account when its deleted. All your purchases and stats will be lost forever.

+

Password

+ +
+ + +
+

@ErrorPlaceholder

+ } +
+

Website Data

+ We dont keep what we collect on you private. Have a look for yourself what is on the server. +
+ +
+
+

User Data

+ @foreach(KeyValuePare cur in UserData ) { +
+
+

@cur.Key

+
+
+

@cur.Value

+
+
+ } +
+
+ +
+
+

Site Data

+ @foreach( KeyValuePare cur in SiteData ) { +
+
+

@cur.Key

+
+
+

@cur.Value

+
+
+ } +
+
+ +
+
+

Purchases

+ @foreach( Receipt cur in Statics.Owned ) { +
+
+
+

Receipt ID

+
+
+

@cur.ReceiptID

+
+
+
+
+

Game

+
+
+

@ProductName( cur.ProductID )

+
+
+
+
+

Time

+
+
+

@cur.Time

+
+
+
+ } +
+
+ +
+ +
+
+
+ +@code { + + bool showDeleteConfirm = false; + string Password = ""; + string ErrorPlaceholder = ""; + + class KeyValuePare { + public string Key { get; set; } = ""; + public string Value { get; set; } = ""; + } + + string ProductName(int ProductID ) { + foreach(Product cur in Statics.Products ) { + if( cur.ID == ProductID ) { + return cur.Name + " -> ( ID : " + ProductID + " )"; + } + } + return "Unknown Product -> ( ID : " + ProductID + " )"; + } + + void showDeleteAccount() { + showDeleteConfirm = true; + } + + void cancelDeleteAccount() { + showDeleteConfirm = false; + Password = ""; + } + + async Task confirmDeleteAccount() { + HttpResponseMessage Delete = await Http.PostAsJsonAsync( "api/account/delete", new MistoxWebsite.Shared.Account(){ + ID = Statics.User.ID, + UserName = Statics.User.Email, + PasswordHash = Password + }); + string result = await Delete.Content.ReadAsStringAsync(); + bool status = result == "true" ? true : false; + if (status){ + await Http.PostAsync("api/account/logout", new StringContent("")); + Nav.NavigateTo("/", true); + } + ErrorPlaceholder = status.ToString(); + } + + List UserData = new List(); + List SiteData = new List(); + protected override void OnInitialized() { + + UserData.Add(new KeyValuePare { + Key = "ID", + Value = Statics.User.ID.ToString() + } ); + UserData.Add( new KeyValuePare { + Key = "UserName", + Value = Statics.User.UserName + } ); + UserData.Add( new KeyValuePare { + Key = "Email", + Value = Statics.User.Email + } ); + UserData.Add( new KeyValuePare { + Key = "EmailVerified", + Value = Statics.User.EmailVerified ? "true" : "false" + } ); + UserData.Add( new KeyValuePare { + Key = "PasswordHash", + Value = Statics.User.PasswordHash + } ); + SiteData.Add( new KeyValuePare { + Key = "FailedPasswordLockEnabled", + Value = Statics.User.SiteData.FailedPasswordLock ? "true" : "false" + } ); + SiteData.Add(new KeyValuePare { + Key = "MaxPasswordAttempts", + Value = Statics.User.SiteData.PasswordAttempts.ToString() + } ); + SiteData.Add( new KeyValuePare { + Key = "CurrentPasswordAttempts", + Value = Statics.User.SiteData.CurrentPasswordAttempts.ToString() + } ); + SiteData.Add( new KeyValuePare { + Key = "Role", + Value = Statics.User.SiteData.Role + } ); + SiteData.Add( new KeyValuePare { + Key = "EmailToken", + Value = Statics.User.SiteData.EmailToken + } ); + + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Account/Manage/WebsiteData.razor.css b/src/MistoxWebsite.Client/Pages/Account/Manage/WebsiteData.razor.css new file mode 100644 index 0000000..15ddd41 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Account/Manage/WebsiteData.razor.css @@ -0,0 +1,115 @@ +.data-table { + column-count: 1; + background-color: rgba(255,255,255,.1); + margin-bottom: 1.4rem; + border-radius: 5px; +} + +.padder{ + padding: 1rem; +} + +.data{ + float: left; + width: 100%; + border-bottom: solid 1px #808080; +} + +.data:last-child{ + border: none; +} + +.data-left { + float: left; + width: 50%; +} + +.data-right { + float: right; + width: 50%; +} + +.data-right h2 { + font-size: 14px; + word-wrap: break-word; +} + +.data-left h1 { + font-size: 14px; +} + +.Confirm-Frame{ + position: absolute; + width: 400px; + height: 200px; + background-color: #202020; + left: calc( 50% - 200px ); + top: calc( 50% - 100px ); + border-radius: 5px; + border-color: red; + border-width: 2px; + border-style: double; +} + +.Blackout-Frame { + position: absolute; + width: 100%; + height: 100%; + background-color: #00000088; + top: 0px; + left: 0px; +} + +.Confirm-Title { + width: 100%; + text-align: center; + margin: 10px 0; +} + +.Confirm-Content { + width: 100%; + text-align: center; + font-size: 15px; + margin-top: 0; +} + +.Confirm-Password { + margin: 0 5%; + float: left; + height: 24px; +} + +.Confirm-Input { + width: 62%; + float: left; +} + +.Confirm-Padding { + height: 18px; + width: 80px; + float: left; +} + +.delete-button { + color: green; + padding: 5px 15px; + background-color: #fff; + border: none; + margin: 10px; + border-radius: 4px; +} + +.delete-button :hvoer { + background-color: #aaa; + transition-duration: 1s; +} + +.Delete-Error { + position: absolute; + font-size: 20px; + left: calc(50% - 200px); + top: calc(50% + 100px); + width: 400px; + text-align: center; + color: red; +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Admin/CreateProduct.razor b/src/MistoxWebsite.Client/Pages/Admin/CreateProduct.razor new file mode 100644 index 0000000..6e36a50 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Admin/CreateProduct.razor @@ -0,0 +1,74 @@ +@page "/admin/product/create" +@attribute [Authorize(Roles = "Admin")] + +

CreateProduct

+ +
+
+ + +
+
+ + +
+
+ + @foreach(string img in obj.Images ) { +
+ + +
+ } + +
+
+ + +
+
+ + +
+ +
+ +
+ @if( !string.IsNullOrEmpty( Result ) ) { +

Results : @Result

+ } +
+ +@code { + + public string Result = ""; + public Product obj = new Product(); + + async void OnFileUpload(InputFileChangeEventArgs e ) { + List files = e.GetMultipleFiles( 10 ).ToList(); + foreach(IBrowserFile cur in files){ + // Open Network Stream and read to memory + Stream ns = cur.OpenReadStream(maxAllowedSize:500000000); // 500 Mb + MemoryStream ms = new MemoryStream(); + await ns.CopyToAsync( ms ); + byte[] data = ms.ToArray(); + + string base64Image = "data:" + cur.ContentType + ";base64, "; + base64Image += Convert.ToBase64String( data ); + + obj.Images.Add( base64Image ); + StateHasChanged(); + }; + } + + public async Task Submit() { + HttpResponseMessage response = await Http.PostAsJsonAsync( "api/product/create", obj ); + Result = await response.Content.ReadAsStringAsync(); + } + + public void RemoveImage(int index) { + Console.WriteLine( index ); + obj.Images.RemoveAt( index ); + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Admin/EditProduct.razor b/src/MistoxWebsite.Client/Pages/Admin/EditProduct.razor new file mode 100644 index 0000000..e154bf5 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Admin/EditProduct.razor @@ -0,0 +1,80 @@ +@page "/admin/product/edit" +@attribute [Authorize(Roles = "Admin")] + +

EditProduct

+ +@if( product != null ) { +
+ Name + +
+
+ Description + +
+
+ Cost + +
+
+ PurchaseURL + +
+
+ @foreach( string img in product.Images ) { +
+ not found + +
+ } + +
+
+ +
+
+ @Result +
+}else{ +

Product Doesnt Exist

+} + +@code { + + [Parameter] + [SupplyParameterFromQuery] + public int ProductID { get; set; } + + Product? product = null; + + string Result = ""; + + protected override async Task OnInitializedAsync() { + HttpResponseMessage response = await Http.PostAsJsonAsync( "api/product/get", new Product(){ ID = ProductID } ); + string Result = await response.Content.ReadAsStringAsync(); + product = JsonConvert.DeserializeObject( Result ); + } + + async void OnFileUpload(InputFileChangeEventArgs e ) { + List files = e.GetMultipleFiles( 10 ).ToList(); + foreach(IBrowserFile cur in files){ + // Open Network Stream and read to memory + Stream ns = cur.OpenReadStream(maxAllowedSize:500000000); // 500 Mb + MemoryStream ms = new MemoryStream(); + await ns.CopyToAsync( ms ); + byte[] data = ms.ToArray(); + + string base64Image = "data:" + cur.ContentType + ";base64, "; + base64Image += Convert.ToBase64String( data ); + + product?.Images.Add( base64Image ); + StateHasChanged(); + }; + } + + protected async void onSubmit() { + HttpResponseMessage TestLogin = await Http.PostAsJsonAsync("api/product/update", product); + Result = await TestLogin.Content.ReadAsStringAsync(); + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Index.razor b/src/MistoxWebsite.Client/Pages/Index.razor new file mode 100644 index 0000000..8d9bfbf --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Index.razor @@ -0,0 +1,11 @@ +@page "/" + +Mistox + +
+ +
+ +@code{ + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Projects/Downloads.razor b/src/MistoxWebsite.Client/Pages/Projects/Downloads.razor new file mode 100644 index 0000000..b5322a1 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Projects/Downloads.razor @@ -0,0 +1,33 @@ +@page "/project/downloads" +@attribute [Authorize] + + + +
+ +
+ @if (output != null ) { + + } +
+ + @ErrorTxt +
+ +@code{ + + public string ErrorTxt = ""; + public DirObj? output = null; + + protected override async void OnInitialized() { + try { + byte[] resultBody = await (await Http.PostAsync( "api/product/showdownloads", new StringContent("") )).Content.ReadAsByteArrayAsync(); + string JsonData = Encoding.UTF8.GetString(resultBody); + output = JsonConvert.DeserializeObject( JsonData ); + base.StateHasChanged(); + } catch( Exception e ) { + ErrorTxt = "Error : " + e.ToString(); + } + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Projects/Downloads.razor.css b/src/MistoxWebsite.Client/Pages/Projects/Downloads.razor.css new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Projects/Downloads.razor.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Projects/Mist.razor b/src/MistoxWebsite.Client/Pages/Projects/Mist.razor new file mode 100644 index 0000000..03c9ae3 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Projects/Mist.razor @@ -0,0 +1,29 @@ +@page "/project/mist" + +Project-Mist + + + +
+

What is the game

+

Project-Mist is a survival game. Kind of like a battle royal in a sense but, think of it backwards. And no I know what your thinking. Its not the first person to die wins. No instead its a never ending survival game where you can free roam and build structures. The catch is, the person who has the highest stats [i.e A combination of kills, survival time] has a marker placed on their forhead.

+

How will the game play

+

When you join the game you will be able to customize your character. There you can set a default loadout for your player. This will be the spawn weapon and gear. After that you will drop into the map with other players to fend for your life. The kill-leader will be marked loosely on the mini-map. You can choose to go after the kill leader or you can choose to loot first. The choice is yours. But be aware that if you survive long enough you will become the new kill leader.

+

Current Idea Board *SUBJECT TO CHANGE*

+

Survival Game
look at item to pick up 'e' for third person and click for third [No nearby]
normal weapons with bullet drop bullet travel time
snipers but rare [Maybe special]

+

Abilities selectable at spawn
a max 20 credit slider where you can spend them on traits
Stamina -> run for longer distances
Strength -> carry more weight
Vitality -> Have more base health
Stealth -> Approximate location on map is bigger

+

More weight slows player some
Backpacks -> Add slots but not weight

+

Oddball style game
Map that shows the relitive area of the top player

+

spawn with classes
2 mags
no attachments
unlock guns with experience

+

no health regen
final hit headshots = 20 credits
final hit bodyshots = 10 credits

+

classes require credits to spawn with better stuff
inventory and credits are transferrable between servers and sessions
combat loggging - if leave in combat start from scratch
one dynamicly roaming entity of the night ( Impossible to kill, when near heart starts pumping and vinegrette )
goes after people possible to get away
Dyanmic day and night cycle
Dynamic weather ( rain, fog, thunder, lightning )
floods that cause roaring rivers to fill that cannot be swam
Fires that char trees(no leaves), regrows in 3ish days
Master leaderboard in the main menu of top players per rank
Ranked lobby ( Disabled until player base )
small towns around a main centralized area( ie city or temple )
large servers
random spawned skin boxes that require credits to open
purchasable skins
bullet penatration on certain materials
bullet reflection on certain materials
No kill leader until you get at least 2 kills minimum

+

Tournament mode
all players spawn at the same time
hold oddball for 30mins total

+

server quits introducing people into game after 5 hrs. (last man standing mode)
last person in server wins
on death quit to new server
everyone becomes oddball
less players alive equal less oddball area

+ +
\ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Projects/Mist.razor.css b/src/MistoxWebsite.Client/Pages/Projects/Mist.razor.css new file mode 100644 index 0000000..2eba7d8 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Projects/Mist.razor.css @@ -0,0 +1,13 @@ +.Big-Div { + position: relative; + left: calc(50% - 400px); + max-width: 800px; +} + +@media (max-width: 1150px) { + .Big-Div { + position: relative; + left: calc(50% - 200px); + max-width: 400px; + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Resume/Resume.razor b/src/MistoxWebsite.Client/Pages/Resume/Resume.razor new file mode 100644 index 0000000..041867e --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Resume/Resume.razor @@ -0,0 +1,283 @@ +@page "/resume/derek" + +
+
+
+ +
+
+

Derek Holloway

+

Owner and sole developer

+
+
+

derek@mistox.net

+
+
+
+
+ +
+
+

Work Experience

+ +
+
+
+

NAVWAR Assistant Contract Tech. Rep.

+

Redhorse corp. - San Diego, CA

+

888-445-8010

+

February 2022 - Today

+ +
+
+
+
+
    +
  • The first line of support for end users
  • +
  • Fix or escalate issues as required to the correct authority for resolution
  • +
  • The ACTR performs routine office IT functions managing mechanical and printer supplies
  • +
  • Assisting with connectivity to and troubleshooting networked systems and Video Teleconferences
  • +
  • This includes setting up new accounts
  • +
  • Managing users accesses
  • +
  • Uses the building badging system to grant access to appropriate personnel Assist with transition to Office 365 and Navy Flank Speed as required.
  • +
  • Leading role on Junior level tasks/projects
  • +
  • Supports the customer performing moderately complex tasks on a routine basis.
  • +
    +
  • Project manager for NAVWAR tech refresh
  • +
  • Replacing 4010+ computers within a 1 year time-frame
  • +
  • Manage warranties with HP Federal
  • +
  • Coordinating orders and returns for old leased hardware
  • +
+
+
+
+ +
+
+
+

NMCI Field Service Technitian

+

Super Systems Inc - San Diego, CA

+

757-399-3000 - info@supersystemsinc.com

+

June 2021 - Feburary 2022

+
+

Ohm Systems, Inc - San Diego, CA

+

215-675-2766 - info@ohmsysinc.com

+

February 2021 - June 2021

+ +
+
+
+
+
    +
  • Performed layer 1 network troubleshooting; windows netsh firewalls, 802.1x Authentication issues, and cable and port issues.
  • +
  • Performed hardware troubleshooting and replacements; replacing laptop motherboards, displays, cpus, ram, and peripherals.
  • +
  • Performed software troubleshooting using event viewer, task manager, and command prompt. By uninstalling and reinstalling or reconfiguring.
  • +
  • Worked with network printers, voip's, and video telecommunication devices.
  • +
  • Worked face to face with customers, managing my time between calls and work orders.
  • +
+
+
+
+ +
+
+
+

NMCI Help Desk Technitian

+

Apex Systems - Coronado, CA

+

619-757-1646

+

September 2019 - September 2020

+ +
+
+
+
+
    +
  • Performed remote troubleshooting: windows cmd, winrs, rdc, winrs. Fixing software issues
  • +
  • Performed administrative Tasks: create active directory accounts and verifying identity to unlock accounts.
  • +
  • Walked users through diagnosing network issues over the phone or escalated issue to field services
  • +
  • Fixed users email issues; server mappings, proxy email addresses, shared emails, and outlook related issues.
  • +
  • Moved users profiles from one domain to another and moved users files along with it.
  • +
  • Fixed account related issues; id to user mismatch and wrong display name.
  • +
+
+
+
+ +
+
+
+

Warehouse receiver

+

Ababa Bolt - El Cajon, CA

+

619-440-1781

+

May 2019 - August 2019

+ +
+
+
+
+
    +
  • Verified that all parts came in off the packing slip
  • +
  • Rejected parts that were damaged and sent back to manufacturer
  • +
  • Sort and add parts into the tracking system
  • +
  • Put parts away in appropriate areas
  • +
+
+
+
+ +
+
+
+

Warehouse packer

+

Ababa Bolt - El Cajon, CA

+

619-440-1781

+

October 2017 - September 2018

+ +
+
+
+
+
    +
  • Find which parts need more stock on shelves
  • +
  • Split out parts by count or weight
  • +
  • Verified and marked appropriate compliance such as RoHS
  • +
  • Operated forklifts and scissor lifts
  • +
+
+
+
+ +
+
+
+

Motor Vehicle Trasport Operator

+

California Army National Guard

+

760-607-8574

+

September 2018 - Today

+

Rank/Grade - Specialist / E-4

+ +
+
+
+
+
    +
  • 88M - Motor Transport Operator
  • +
  • Neccesarry to hold secret clearence and keep all cyber security certifications up to date.
  • +
  • June 2020 - Protected and defended the Los Alamitos police department and Six Flags Magic Mountain during the rios cause by BLM movement that caused rioting in the streets.
  • +
  • September 2020 - Worked with Cal-Fire in Chico, CA and Alderpoint, CA to cut fire lines to prevent the spread of fires during the August Fire Complex fires.
  • +
+
+
+
+
+
+
+ +
+
+
+

Development

+
    +
  • C#
  • +
  • Mono
  • +
  • ASP.NET
  • +
  • ASP Core
  • +
  • Blazor Web Assembly
  • +
  • Windows Forms Apps
  • +
  • C++
  • +
  • Arm Embedded
  • +
  • Raspberry Pi
  • +
  • Database
  • +
  • MySql
  • +
  • MsSql
  • +
  • LINQ
  • +
  • Game Engines
  • +
  • Godot
  • +
  • Unity 3D
  • +
  • Solar 2D
  • +
  • Web Development
  • +
  • Front End
  • +
  • Back End
  • +
  • Interfaces
  • +
  • Square Payment API
  • +
  • Rest Clients
  • +
+
+
+
+ LUADNS-DDNS : https://github.com/reverseslayer/LUADNS-DDNS
+ This is a service that I made for luadns.com. This allows free ddns by simply changing the dns name servers to luadns nameservers and running this program as a service. This was made for linux but shoud easily run on windows.

+
+
+ Mistox-Server : https://github.com/reverseslayer/MistoxServer
+ This is a UDP Hole Punched server; that allows clients to direct connect over the wan without opening ports. It works by having a dedicated TCP server that routes the UDP connections directly to each client. Based on this Idea map. +
+
+ Mistox.net : https://mistox.net
+ Mistox.net is all done by me. The DNS is hosted by domains.google.com and pushed through luadns.com name server so that I can run LUA-DDNS a service I made to reach other places that behind ddns. The webservers are hosted by vultr.com and the entire stack is built on asp.net. The payment services are run through stripe-payments. I built this as a side project but its slowly becoming more and more something that I would like to be able to share things that I make; paid or free. +
+
+
+
+
+ + +@code { + string ContentStyle1 = ""; + string ContentStyle2 = ""; + string ContentStyle3 = ""; + string ContentStyle4 = ""; + string ContentStyle5 = ""; + string ContentStyle6 = ""; + string ContentStyle7 = ""; + + void MouseEnter( int frameNumber ) { + if (frameNumber == 1){ + ContentStyle1 = "right: 0px;"; + base.StateHasChanged(); + }else if (frameNumber == 2){ + ContentStyle2 = "right: 0px;"; + base.StateHasChanged(); + }else if (frameNumber == 3){ + ContentStyle3 = "right: 0px;"; + base.StateHasChanged(); + }else if (frameNumber == 4){ + ContentStyle4 = "right: 0px;"; + base.StateHasChanged(); + }else if (frameNumber == 5){ + ContentStyle5 = "right: 0px;"; + base.StateHasChanged(); + }else if (frameNumber == 6){ + ContentStyle6 = "right: 0px;"; + base.StateHasChanged(); + }else if (frameNumber == 7){ + ContentStyle7 = "right: 0px;"; + base.StateHasChanged(); + } + } + + void MouseLeave( int frameNumber ){ + if (frameNumber == 1){ + ContentStyle1 = "right: -700px;"; + base.StateHasChanged(); + }else if (frameNumber == 2){ + ContentStyle2 = "right: -700px;"; + base.StateHasChanged(); + }else if (frameNumber == 3){ + ContentStyle3 = "right: -700px;"; + base.StateHasChanged(); + }else if (frameNumber == 4){ + ContentStyle4 = "right: -700px;"; + base.StateHasChanged(); + }else if (frameNumber == 5){ + ContentStyle5 = "right: -700px;"; + base.StateHasChanged(); + }else if (frameNumber == 6){ + ContentStyle6 = "right: -700px;"; + base.StateHasChanged(); + }else if (frameNumber == 7){ + ContentStyle7 = "right: -700px;"; + base.StateHasChanged(); + } + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Resume/Resume.razor.css b/src/MistoxWebsite.Client/Pages/Resume/Resume.razor.css new file mode 100644 index 0000000..998210f --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Resume/Resume.razor.css @@ -0,0 +1,191 @@ +* { + --popout-shadow-color-left: #8c8c8c; + --popout-shadow-color-bottom: #595959; + --popout-background-color: #004262; + --frame-background-color: #005662; + --frame-title-color: #ffffff; + --job-title-color: #f35100; + --job-sub-color: #c85c00; + --text-color: #dddddd; + --link-color: #4cff00; + --link-visited-color: #73ac5b; + --skills-bg-color: #c85c00; + --skills-text-color: #000; + --skills-text-shadow-color: #f35100; + --skills-prime-background-color: #972500; + --skills-prime-text-color: #fff; +} + +body { + position: relative; +} + +h1 { + color: var(--frame-title-color); +} + +span { + color: var(--text-color); +} + +.PopOutFrame { + position: relative; + background-color: white; + width: 100%; + max-width: 1080px; + margin: 15px auto 0 auto; + content: ""; +} + + .PopOutFrame::before { + position: absolute; + width: 6px; + left: -6px; + margin-top: 6px; + background: var(--popout-shadow-color-left); + content: ""; + display: block; + transform: skew(0deg, -61deg); + height: 100%; + } + +.PopOutHr { + width: 100%; + float: left; +} + + .PopOutHr::after { + position: absolute; + width: 100%; + margin-top: 0px; + bottom: -11px; + left: -3px; + background: var(--popout-shadow-color-bottom); + content: ""; + display: block; + transform: skew(-31deg, 0deg); + height: 11px; + } + +.Content{ + position: relative; + padding: 15px; + overflow: hidden; + background-color: var(--popout-background-color); +} + +.ImgFrame { + float: left; +} + +.Picture{ + width: 140px; + height: 140px; +} + +.NameFrame { + float: left; + width: calc(100% - 510px); + margin-left: 20px; +} + +.ContactFrame { + float: left; + width: 300px; + height: 140px; +} + +.ContactFrame h1{ + font-size: 30px; +} + +.SkillsContent { + position: relative; + float: left; + height: 100%; + width: 60%; + transition-duration: 2s; + right: -700px; +} + +.SkillsContent :link { + color: var(--link-color); +} + +.SkillsContent :visited{ + color: var(--link-visited-color) !important; +} + +.paddedcell { + padding: 10px 5px; + padding-bottom: 0; + margin-bottom: 5px; +} + +.colored { + background-color: var(--frame-background-color); +} + +.paddedcell br{ + display: block; + margin: 10px 0; + content: ""; +} + +.Skills { + float: left; + height: 100%; + width: 40%; + transition-duration: 2s; +} + +.Skills h1{ + margin: 5px; +} + +.Skills ul{ + font-size: 14px; + list-style: none; + margin: 0; + padding: 0 5px; +} + + .Skills li { + float: left; + padding: 4px 6px; + margin: 0 4px 4px 0; + background-color: var(--skills-bg-color); + text-shadow: 0 1px 1px var(--skills-text-shadow-color); + color: var(--skills-text-color); + } + +.LIPrime{ + text-shadow: none; + background-color: var(--skills-prime-background-color) !important; + color: var(--skills-prime-text-color) !important; + clear: left; +} + +.capsule { + margin-top: 15px; + overflow: hidden; + border: solid var(--frame-background-color); + border-radius: 5px; +} + +.jobTitle { + margin: 0; + color: var(--job-title-color); +} + +.jobSub { + margin: 0; + color: var(--job-sub-color); + font-size: 15px; +} + +.textSection { + margin-top: 15px; + background-color: var(--frame-background-color); + border-radius: 5px; +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Shared/Authorizing.razor b/src/MistoxWebsite.Client/Pages/Shared/Authorizing.razor new file mode 100644 index 0000000..b7ed9d0 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/Authorizing.razor @@ -0,0 +1,5 @@ +Checking Session + +@code { + +} diff --git a/src/MistoxWebsite.Client/Pages/Shared/ExplorerChild.razor b/src/MistoxWebsite.Client/Pages/Shared/ExplorerChild.razor new file mode 100644 index 0000000..25e016d --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/ExplorerChild.razor @@ -0,0 +1,38 @@ + + + + +@code{ + [Parameter] + public string Title{ get; set; } = ""; + [Parameter] + public DirObj[]? Children{ get; set; } + [Parameter] + public string PartialPath{ get; set; } = ""; + + public string collapseStyle = "none"; + + void OpenCollapseable() { + if (collapseStyle == "block") { + collapseStyle = "none"; + } else { + collapseStyle = "block"; + } + } + + void Download(string Path) { + Nav.NavigateTo( "api/product/download?Product=" + PartialPath + Path, true ); + } +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Shared/MainLayout.razor b/src/MistoxWebsite.Client/Pages/Shared/MainLayout.razor new file mode 100644 index 0000000..272c6ae --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/MainLayout.razor @@ -0,0 +1,19 @@ +@inherits LayoutComponentBase + +
+ + +
+
+ @Body +
+
+
+ +@code{ + + + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Shared/MainLayout.razor.css b/src/MistoxWebsite.Client/Pages/Shared/MainLayout.razor.css new file mode 100644 index 0000000..c29890a --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/MainLayout.razor.css @@ -0,0 +1,88 @@ +article{ + padding: 0 !important; +} + +.page { + position: relative; + display: flex; + flex-direction: column; + background-color: var(--Mistox-Black); +} + +body{ + background-color: var(--Mistox-Black); +} + +main { + flex: 1; + background-color: var(--Mistox-Black); + color: var(--Mistox-White); +} + +.sidebar { + border-right: var(--Mistox-Background) 2px solid; +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 45px; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row:not(.auth) { + display: none; + } + + .top-row.auth { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + min-width: 250px; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } +} diff --git a/src/MistoxWebsite.Client/Pages/Shared/NavMenu.razor b/src/MistoxWebsite.Client/Pages/Shared/NavMenu.razor new file mode 100644 index 0000000..53c4b27 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/NavMenu.razor @@ -0,0 +1,96 @@ +
+
+ + + +
+ +
+ +
+ + +
+ +
+
+ +@code { + private bool collapseNavMenu = true; + + private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; + private string? MainHeight => collapseNavMenu ? "calc(100vh - 56px)" : "calc(100vh - 279.2px)"; + + private void ToggleNavMenu(){ + collapseNavMenu = !collapseNavMenu; + } + + private void ManageProf() { + Nav.NavigateTo("/account/manage"); + } + + async void ClearSession() { + await Http.PostAsync("api/account/logout", new StringContent("")); + Nav.NavigateTo("/", true); + } + + void NavToLogin() { + Nav.NavigateTo("/account/login"); + } + + void NavToRegister() { + Nav.NavigateTo("/account/register"); + } +} diff --git a/src/MistoxWebsite.Client/Pages/Shared/NavMenu.razor.css b/src/MistoxWebsite.Client/Pages/Shared/NavMenu.razor.css new file mode 100644 index 0000000..dc8dfe9 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/NavMenu.razor.css @@ -0,0 +1,132 @@ +.navbar-toggler { + background-color: var(--Mistox-Dark); + color: var(--Mistox-White); + width: 100%; + height: 40px; + border: none; + font-size: 20px; + transition-duration: 0.5s; +} + + .navbar-toggler:hover { + background-color: #410a04; + } + +.top-row { + background-color: var(--Mistox-Offset); + height: 200px; +} + +.bottom-row { + height: calc(100% - 200px); + background: linear-gradient(0deg, var(--Mistox-Dark), var(--Mistox-Offset) ); +} + +.navbar-brand img { + width: 200px; + height: 200px; + padding: 0 25px; +} + +.oi { + width: 2rem; + font-size: 1.1rem; + color: var(--Mistox-White); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; + text-decoration: none; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep a { + color: var(--Mistox-White); + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + transition-duration: 0.5s; + text-decoration: none; + padding-left: 20px; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.25); + color: var(--Mistox-White); +} + +.nav-item ::deep a:hover { + background-color: rgba(255,255,255,0.1); + color: var(--Mistox-White); +} + +.nav-login { + position: relative; + bottom: 10px; + left: 10px; + width: calc(100% - 20px); + padding-top: 10px; +} + +.collapse { + display: none; +} + +.nav-login-button { + width: calc(50% - 2.5px); + background-color: transparent; + border-radius: 5px; + border-color: transparent; + color: var(--Mistox-White); + transition-duration: 0.5s; + padding: 5px 0; +} + + .nav-login-button:hover { + background-color: #FFFFFF50; + } + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .collapse { + /* Never collapse the sidebar for wide screens */ + display: block !important; + } + + .nav-login { + position: absolute; + } + +} + +@media (max-width: 640px){ + + .navbar-brand img { + position: relative !important; + padding-left: calc(50% - 80px) !important; + height: 160px; + width: 160px; + } + + .top-gradient { + background: linear-gradient(0deg, var(--Mistox-Dark), var(--Mistox-Offset) ); + } + + .bottom-row { + background: var(--Mistox-Medium); + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Shared/NotAuthroized.razor b/src/MistoxWebsite.Client/Pages/Shared/NotAuthroized.razor new file mode 100644 index 0000000..a9a554b --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/NotAuthroized.razor @@ -0,0 +1,13 @@ +@if(Statics.User.ID != -1 ) { + + + +} + +@code { + protected override void OnInitialized() { + if (Statics.User.ID == -1 ) { + Nav.NavigateTo( "/account/login?ReturnURL=Null" ); + } + } +} diff --git a/src/MistoxWebsite.Client/Pages/Shared/PageDoesntExist.razor b/src/MistoxWebsite.Client/Pages/Shared/PageDoesntExist.razor new file mode 100644 index 0000000..950d25a --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/PageDoesntExist.razor @@ -0,0 +1,7 @@ + Not found + +

Nothing at this page

+ +@code { + +} diff --git a/src/MistoxWebsite.Client/Pages/Shared/TitleBar.razor b/src/MistoxWebsite.Client/Pages/Shared/TitleBar.razor new file mode 100644 index 0000000..7205852 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/TitleBar.razor @@ -0,0 +1,19 @@ +
+
+

@Title

+
+ @foreach(TitleBarDiv cur in ButtonList ) { + + } +
+ +@code { + [Parameter] + public string Title { get; set; } = string.Empty; + + [Parameter] + public TitleBarDiv[] ButtonList { get; set; } = {}; +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Shared/TitleBar.razor.css b/src/MistoxWebsite.Client/Pages/Shared/TitleBar.razor.css new file mode 100644 index 0000000..5f5eeed --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Shared/TitleBar.razor.css @@ -0,0 +1,43 @@ +.title-div { + width: 100%; + height: 45px; + background-color: #16212c; + font-size: 15px; +} + + .title-div div { + float: left; + } + + .title-div h1 { + font-size: 30px; + padding: 5px; + padding-left: 1rem; + margin: 0; + } + + .title-div img{ + height: 20px; + } + + .title-div span { + position: relative; + top: calc(50% - 15px); + } + + .title-div button { + padding: 5px; + margin: 5px; + margin-right: 10px; + float: right; + width: 120px; + border: none; + background-color: #16212c; + color: #d7d7d7; + border-radius: 4px; + transition-duration: 0.5s; + } + + .title-div button:hover { + background-color: rgba(255,255,255,0.2); + } diff --git a/src/MistoxWebsite.Client/Pages/Snake.razor b/src/MistoxWebsite.Client/Pages/Snake.razor new file mode 100644 index 0000000..17338bc --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Snake.razor @@ -0,0 +1,164 @@ +@page "/snake" + +
+

Score : 0

+
+ + + @foreach(SnakePart part in SnakeParts ) { +
+ } +
+ + +
+ +
+

LEADERBOARD

+
+
+
+

Designed by Derek in California

+
+ + + +@code { + + struct SnakePart { + public int X{ get; set; } + public int Y{ get; set; } + public int Hue{ get; set; } + + public string getX() { + return X * 10 + "px"; + } + public string getY() { + return Y * 10 + "px"; + } + } + enum Direction { + Up, + Down, + Left, + Right + } + + List SnakeParts = new List(){ + new SnakePart{ X = 5, Y = 10 }, // Tail + new SnakePart{ X = 6, Y = 10 }, + new SnakePart{ X = 7, Y = 10 }, + new SnakePart{ X = 8, Y = 10 } // Head + }; + int Score = 0; + bool Paused = false; + Direction SnakeDirection = Direction.Right; + int FrameRate = 1000 / 10; + int HueRate = 5; + + SnakePart Collectable = new SnakePart{ X = 15, Y = 15 }; + + + void OnKeyDown(KeyboardEventArgs e) { + Console.WriteLine(e.Key); + if (e.Key.ToLower() == "w" ) { + SnakeDirection = Direction.Up; + }else if (e.Key.ToLower() == "a" ) { + SnakeDirection = Direction.Left; + }else if (e.Key.ToLower() == "s" ) { + SnakeDirection = Direction.Down; + }else if (e.Key.ToLower() == "d" ) { + SnakeDirection = Direction.Right; + } + } + + bool CheckSelfHit() { + foreach(SnakePart cur in SnakeParts ) { + if (SnakeParts[SnakeParts.Count-1].X == cur.X ) { + if (SnakeParts[SnakeParts.Count-1].Y == cur.Y ) { + return false; + } + } + } + return true; + } + + bool CheckBounds() { + if (SnakeParts[SnakeParts.Count-1].X > 0 && SnakeParts[SnakeParts.Count-1].X < 50 ) { + if (SnakeParts[SnakeParts.Count-1].Y > 0 && SnakeParts[SnakeParts.Count-1].Y < 50 ) { + return true; + } + } + return false; + } + + bool CheckCollectibleHit() { // return true if hit + if (SnakeParts[SnakeParts.Count-1].X == Collectable.X ) { + if (SnakeParts[SnakeParts.Count-1].Y == Collectable.Y ) { + return true; + } + } + return false; + } + + void ResetCollectable() { + Collectable.X = new Random().Next(1, 40); + Collectable.Y = new Random().Next(1, 40); + } + + void Die() { + + } + + void Update() { + // Get Next Position + if( SnakeDirection == Direction.Up ) { + SnakeParts.Add( new SnakePart { + X = SnakeParts[SnakeParts.Count-1].X, + Y = SnakeParts[SnakeParts.Count-1].Y - 1, + Hue = SnakeParts[SnakeParts.Count-1].Hue + HueRate + }); + }else if (SnakeDirection == Direction.Right ) { + SnakeParts.Add( new SnakePart { + X = SnakeParts[SnakeParts.Count-1].X + 1, + Y = SnakeParts[SnakeParts.Count-1].Y, + Hue = SnakeParts[SnakeParts.Count-1].Hue + HueRate + }); + }else if (SnakeDirection == Direction.Down ) { + SnakeParts.Add( new SnakePart { + X = SnakeParts[SnakeParts.Count-1].X, + Y = SnakeParts[SnakeParts.Count-1].Y + 1, + Hue = SnakeParts[SnakeParts.Count-1].Hue + HueRate + }); + }else if (SnakeDirection == Direction.Left ) { + SnakeParts.Add( new SnakePart { + X = SnakeParts[SnakeParts.Count-1].X - 1, + Y = SnakeParts[SnakeParts.Count-1].Y, + Hue = SnakeParts[SnakeParts.Count-1].Hue + HueRate + }); + } + if (CheckSelfHit() && CheckBounds() ) { + if( CheckCollectibleHit() ) { + Score += 1; + ResetCollectable(); + } else { + SnakeParts.RemoveAt( 0 ); + } + } else { + Die(); + } + StateHasChanged(); + } + + protected override void OnInitialized() { + ResetCollectable(); + var timer = new System.Threading.Timer((e) => { + if (!Paused){ + Update(); + } + }, null, 0, FrameRate ); + } + +} diff --git a/src/MistoxWebsite.Client/Pages/Store/Cart.razor b/src/MistoxWebsite.Client/Pages/Store/Cart.razor new file mode 100644 index 0000000..3bbeee8 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Store/Cart.razor @@ -0,0 +1,82 @@ +@page "/store/cart" +@attribute [Authorize] + +

Cart

+ +
+ @foreach( MistoxWebsite.Shared.Cart obj in Statics.Carts ) { +
+

@getItem(obj.ProductID)?.Name

+ @foreach( string cur in getItem( obj.ProductID )?.Images ) { + Not found + } +

$@((Convert.ToSingle( getItem( obj.ProductID )?.Cost ) / 100f).ToString( "0.00" )) USD

+ +
+ } +
+ +
+ @if( Statics.Carts.Count > 0 ) { +

Total : $@total.ToString( "0.00" )

+ + } else { +

The cart is empty

+ } +
+ +@code { + + [Parameter] public Func? ShouldReRender { get; set; } + [Parameter] public Catalog? parent { get; set; } = null; + + float total = 0; + + Product? getItem(int productID ) { + foreach(Product cur in Statics.Products ) { + if (cur.ID == productID ) { + return cur; + } + } + return null; + } + + protected override void OnInitialized() { + total = 0; + foreach( MistoxWebsite.Shared.Cart obj in Statics.Carts ) { + foreach( Product item in Statics.Products ) { + if( obj.ProductID == item.ID ) { + total = total + (item.Cost / 100f); + break; + } + } + } + base.StateHasChanged(); + } + + public void RecalcTotal() { + OnInitialized(); + } + + public async void removeFromCart(int ID) { + for(int i=0; i<=Statics.Carts.Count; i++ ) { + if(ID == Statics.Carts[i].ProductID ) { + await Http.PostAsJsonAsync( "api/cart/remove", Statics.Carts [i] ); + Statics.Carts.RemoveAt(i); + total = 0; + OnInitialized(); + break; + } + } + if (parent != null ) { + parent?.Refresh(); + } else { + base.StateHasChanged(); + } + } + + public void Chekout() { + Nav.NavigateTo("/store/payment/checkout"); + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Store/Cart.razor.css b/src/MistoxWebsite.Client/Pages/Store/Cart.razor.css new file mode 100644 index 0000000..11380e4 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Store/Cart.razor.css @@ -0,0 +1,70 @@ +.Spacer{ + padding: 10px; +} + +.cart-title{ + width: 100%; + text-align: center; +} + +.cart-item { + background-color: #222; + border: 1px solid #f00; + border-radius: 4px; +} + +.cart-item h1 { + text-align: center; + padding-top: 5px; +} + +.cart-item img { + width: calc(100% - 10px); + margin: 5px; +} + +.cart-item h2 { + float: left; + width: 50%; + text-align: center; +} + +.cart-item button{ + width: calc( 50% - 40px); + height: 28px; + margin: 20px; + background-color: #ff0000; + border: none; + border-radius: 4px; + transition-duration: 0.5s; +} + + .cart-item button:hover { + background-color: #500; + color: #fff; + } + +.cart-total{ + width: 100%; +} + +.cart-total h3 { + width: 100%; + text-align: center; +} + +.cart-total button { + width: 200px; + height: 50px; + margin-left: calc( 50% - 100px ); + margin-bottom: 30px; + background-color: lawngreen; + border: none; + border-radius: 4px; + transition-duration: 0.5s; +} + + .cart-total button:hover { + background-color: #040; + color: #fff; + } \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Store/Catalog.razor b/src/MistoxWebsite.Client/Pages/Store/Catalog.razor new file mode 100644 index 0000000..e267e36 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Store/Catalog.razor @@ -0,0 +1,139 @@ +@page "/store/catalog" +@attribute [Authorize] + +Catalog + + + +
+@foreach(Product obj in Statics.Products ) { +
+
+ @if( obj.Images.Count > 1 ) { + + } + @if( obj.Images.Count > 0 ) { + Not found + } + @if( obj.Images.Count > 1 ) { + + } +
+

@obj.Name

+

@obj.Description

+

$@((Convert.ToSingle(obj.Cost)/100f).ToString("0.00"))

+ @if ( isOwned(obj.ID) ) { + + } else { + @if( isInCart( obj.ID ) ) { + + } else { + + } + } + + + +
+} + + + +
+ +
+ +
+ +@code { + Cart? thisCart; + bool CartOpen = false; + string? CartCssClass => CartOpen ? "cartopen" : "cartclosed"; + + TitleBarDiv[] tbd = new TitleBarDiv[2]; + protected override void OnInitialized() { + tbd [0] = new TitleBarDiv { Name = "Cart", Image="/img/cart.png" }; + tbd [0].onClicked += async ( object? o, EventArgs e ) => { + V2 dim = await JS.InvokeAsync("getWindowSize"); + Console.WriteLine("Screen width : " + dim.width); + if (dim.width <= 640 ) { + Nav.NavigateTo( "/store/cart" ); + } + CartOpen = !CartOpen; + base.StateHasChanged(); + }; + + tbd[1] = new TitleBarDiv { Name = "Downloads" }; + tbd[1].onClicked += (object? o, EventArgs e) => { + Nav.NavigateTo("/project/downloads"); + }; + } + + public void Download() { + Nav.NavigateTo( "/project/downloads" ); + } + + public void Refresh() { + base.StateHasChanged(); + } + + public void Previous( Product obj ) { + obj.CurShowingIMG--; + if (obj.CurShowingIMG < 0 ) { + obj.CurShowingIMG = obj.Images.Count - 1; + } + } + + public void Next( Product obj ) { + obj.CurShowingIMG++; + if (obj.CurShowingIMG > obj.Images.Count - 1 ) { + obj.CurShowingIMG = 0; + } + } + + public bool isOwned(int ID ) { + if(Statics.Owned.Count > 0 ) { + foreach( MistoxWebsite.Shared.Receipt cur in Statics.Owned ) { + if( cur.ProductID == ID ) { + return true; + } + } + } + return false; + } + + public bool isInCart(int id) { + foreach( MistoxWebsite.Shared.Cart cur in Statics.Carts ) { + if (cur.ProductID == id ) { + return true; + } + } + return false; + } + + public async void onAddToCart( int ID ) { + MistoxWebsite.Shared.Cart item = new MistoxWebsite.Shared.Cart { + ProductID = ID, + AccountID = Statics.User.ID + }; + await Http.PostAsJsonAsync( "api/cart/add", item ); + Statics.Carts.Add(item); + base.StateHasChanged(); + thisCart?.RecalcTotal(); + } + + void gotoNew() { + Nav.NavigateTo( "/admin/product/create" ); + } + + void gotoEdit(int objId ) { + Nav.NavigateTo( "/admin/product/edit?ProductID=" + objId ); + } + + class V2 { + public int width{ get; set; } + public int height{ get; set; } + } +} diff --git a/src/MistoxWebsite.Client/Pages/Store/Catalog.razor.css b/src/MistoxWebsite.Client/Pages/Store/Catalog.razor.css new file mode 100644 index 0000000..40214e7 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Store/Catalog.razor.css @@ -0,0 +1,123 @@ +.gameCard { + position: relative; + background-color: var(--Secondary-Button-Color); + float: left; + box-sizing: border-box; + margin: 0; + padding: 0; + width: 100%; + border-radius: 10px; + break-inside: avoid; + margin-bottom: 2rem; +} + +.gameCard-Name { + width: 100%; + text-align: left; + font-size: 25px; + padding: 5px 0 0 5px; + background-color: rgba(0,0,0,.1); +} + +.gameCard-Grid { + column-count: 4; + column-gap: 2rem; + padding-top: 20px; + width: calc(100% - 40px); + margin-left: 20px; +} + +@media (max-width: 1400px) { + .gameCard-Grid { + column-count: 3; + padding-top: 20px; + width: calc(100% - 40px); + margin-left: 20px; + } +} + +@media (max-width: 1100px) { + .gameCard-Grid { + column-count: 2; + padding-top: 20px; + width: calc(100% - 40px); + margin-left: 20px; + } +} + +@media (max-width: 900px) { + .gameCard-Grid { + column-count: 1; + padding-top: 20px; + width: calc(100% - 40px); + margin-left: 20px; + } +} + +.gameCard-Img { + width: 100%; + border-radius: 10px 10px 0 0; +} + +.gameCard-Next, +.gameCard-Prev { + background-color: transparent; + color: #fff; + padding: 16px; + margin-top: -22px; + font-size: 18px; + font-weight: bold; + border: none; + transition: background-color 0.6s ease; +} + + .gameCard-Next:hover, + .gameCard-Prev:hover { + background-color: rgba(0, 0, 0, 0.5); + } + +.gameCard-Prev { + position: absolute; + top: 50%; +} + +.gameCard-Next { + position: absolute; + top: 50%; + right: 0; +} + +.gameCard-Desc { + font-size: 13px; + margin: 5px; + color: #ccc; +} + +.gameCard-Price { + width: calc(50% - 10px); + float: left; + margin: 5px; + text-align: center; + margin-bottom: 10px; +} + +.gameCard-Button { + width: 40%; + margin: 5px 5%; + height: 38.4px; +} + +.cartopen { + position: absolute; + background: #2c2946AA; + right: 10px; + top: 55px; + width: 400px; + border-radius: 5px; + backdrop-filter: blur(3px); + border: 1px solid #00f; +} + +.cartclosed { + display: none; +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Store/Payment/Checkout.razor b/src/MistoxWebsite.Client/Pages/Store/Payment/Checkout.razor new file mode 100644 index 0000000..2252358 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Store/Payment/Checkout.razor @@ -0,0 +1,36 @@ +@page "/store/payment/checkout" +@attribute [Authorize] + +@if( loaded ) { + +} + +@code { + public string PaymentIFrame { get; set; } = ""; + public bool loaded = false; + + protected override async Task OnInitializedAsync() { + if (Statics.Carts.Count == 0 ) { + Nav.NavigateTo("/store/cart"); + } + AuthenticationState user = await Auth.GetAuthenticationStateAsync(); + string email = string.Empty; + foreach(Claim cur in user.User.Claims ) { + if (cur.Type == ClaimTypes.Email ) { + email = cur.Value; + break; + } + } + string buildingURL = "https://mistox.net/PaymentFrame.html?userID=" + email; + PaymentIFrame = buildingURL; + loaded = true; + await base.OnInitializedAsync(); + } + + protected override async void OnAfterRender( bool firstRender ) { + if( firstRender ) { + await JS.InvokeVoidAsync( "PaymentLoaded" ); + } + base.OnAfterRender( firstRender ); + } +} diff --git a/src/MistoxWebsite.Client/Pages/Store/Payment/Checkout.razor.css b/src/MistoxWebsite.Client/Pages/Store/Payment/Checkout.razor.css new file mode 100644 index 0000000..01e9258 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Store/Payment/Checkout.razor.css @@ -0,0 +1,7 @@ +.PaymentFrame{ + position: relative; + left: 50px; + width: calc(100% - 100px); + margin-top: 50px; + background-color: #808080; +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Pages/Store/Payment/Success.razor b/src/MistoxWebsite.Client/Pages/Store/Payment/Success.razor new file mode 100644 index 0000000..9191a90 --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Store/Payment/Success.razor @@ -0,0 +1,25 @@ +@page "/store/payment/success" + +

Payment Success

+ +@code { + + [Parameter] + [SupplyParameterFromQuery] + public string? ReceiptID { get; set; } + /* + protected override async Task OnInitializedAsync() { + + string resultBody = await (await Http.PostAsync( "/api/stripe/getreceipt", new StringContent(ReceiptID) )).Content.ReadAsStringAsync(); + Receipt receipt = JsonConvert.DeserializeObject( resultBody ); + + if (receipt.Payment.Status == "succeeded" ) { + + // Add the product passed to the database + + } + + Console.WriteLine( "test" ); + } + */ +} diff --git a/src/MistoxWebsite.Client/Pages/Store/Receipt.razor b/src/MistoxWebsite.Client/Pages/Store/Receipt.razor new file mode 100644 index 0000000..8d71b8a --- /dev/null +++ b/src/MistoxWebsite.Client/Pages/Store/Receipt.razor @@ -0,0 +1,71 @@ +@page "/store/receipt" + +

Receipt

+ +
+
+

Mistox

+

https://mistox.net

+

For Support or questions please email

+

derek@mistox.net

+
+
+
+

Payment Method: @PayMethod

+

Card Used: @Last4

+
+
+
+

@ItemTitle:

$@ItemCost

+
+

Subtotal:

$@ItemCost

+

Amount Charged:

$@ItemCost

+
+
+
+

Receipt ID: @ReceiptID

+

Purchase Time: @PurchaseDateTime

+

Customer: @CustomerID

+
+
+
+

Thank you

+
+
+ + + +@code { + [Parameter] + [SupplyParameterFromQuery] + public string ReceiptID { get; set; } = ""; + + public string PurchaseDateTime{ get; set; } = ""; + public string CustomerID { get; set; } = ""; + public string ItemTitle { get; set; } = ""; + public string ItemCost { get; set; } = ""; + public string PayMethod { get; set; } = ""; + public string Last4 { get; set; } = ""; + + protected override void OnInitialized() { + /* + string resultBody = await (await Http.PostAsync( "/api/stripe/getreceipt", new StringContent(ReceiptID) )).Content.ReadAsStringAsync(); + ReceiptResponse receipt = JsonConvert.DeserializeObject( resultBody ); + if (receipt != null ) { + if( receipt.Succeeded == "succeeded" ) { + PurchaseDateTime = receipt.PurchaseDateTime; + CustomerID = receipt.CustomerID; + ItemTitle = receipt.ItemTitle; + ItemCost = receipt.ItemCost; + PayMethod = receipt.PayMethod; + Last4 = receipt.Last4; + } else { + Nav.NavigateTo( "/" ); + } + } else { + Nav.NavigateTo( "/?ReceiptNotFound" ); + } + */ + } + +} diff --git a/src/MistoxWebsite.Client/Program.cs b/src/MistoxWebsite.Client/Program.cs new file mode 100644 index 0000000..b40b5b7 --- /dev/null +++ b/src/MistoxWebsite.Client/Program.cs @@ -0,0 +1,23 @@ +using MistoxWebsite.Client; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.AspNetCore.Components.Authorization; +using MistoxWebsite.Client.AuthState; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add( "#app" ); +builder.RootComponents.Add( "head::after" ); + +builder.Services.AddScoped( + sp => new HttpClient { + BaseAddress = new Uri( + builder.HostEnvironment.BaseAddress + ) + } +); + +builder.Services.AddOptions(); +builder.Services.AddAuthorizationCore(); +builder.Services.AddScoped(); + +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/src/MistoxWebsite.Client/Services/AuthStateProvider.cs b/src/MistoxWebsite.Client/Services/AuthStateProvider.cs new file mode 100644 index 0000000..9b9df50 --- /dev/null +++ b/src/MistoxWebsite.Client/Services/AuthStateProvider.cs @@ -0,0 +1,49 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; +using MistoxWebsite.Shared; +using Newtonsoft.Json; + +namespace MistoxWebsite.Client.AuthState { + public class AuthStateProvider : AuthenticationStateProvider { + + private readonly HttpClient _httpClient; + + public AuthStateProvider( HttpClient httpClient ) { + _httpClient = httpClient; + } + + // Called initially on website load + public override async Task GetAuthenticationStateAsync() { + + HttpResponseMessage test = await _httpClient.PostAsync("api/pageload", new StringContent("") ); + if( test.IsSuccessStatusCode ) { + string outtest = await test.Content.ReadAsStringAsync(); + PageLoadObject? t = JsonConvert.DeserializeObject(outtest); + if( t != null && t.products != null && t.receipts != null && t.user != null && t.Cart != null && t.claims != null ) { + Statics.Statics.Products = t.products; + Statics.Statics.Owned = t.receipts; + Statics.Statics.User = t.user; + Statics.Statics.Carts = t.Cart; + + List claims = new List() { + new Claim(ClaimTypes.Name, t.claims.UserName), + new Claim(ClaimTypes.Email, t.claims.Email), + new Claim("emailverified", t.claims.EmailVerified), + new Claim(ClaimTypes.Role, t.claims.Role), + new Claim("LockAccount", t.claims.FailedPasswordLock), + }; + AuthenticationState temp1 = + new AuthenticationState( + new ClaimsPrincipal( + new ClaimsIdentity(claims, "serverAuth" + ) + ) + ); + return temp1; + } + } + AuthenticationState temp2 = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); + return temp2; + } + } +} diff --git a/src/MistoxWebsite.Client/Services/Statics.cs b/src/MistoxWebsite.Client/Services/Statics.cs new file mode 100644 index 0000000..83c873c --- /dev/null +++ b/src/MistoxWebsite.Client/Services/Statics.cs @@ -0,0 +1,21 @@ +using MistoxWebsite.Shared; + +namespace MistoxWebsite.Client.Statics { + public class Statics { + public static Account User = new Account(){ ID=-1 }; + public static List Products = new List(); + public static List Owned = new List(); + public static List Carts = new List(); + } + + public class TitleBarDiv { + public string Name { get; set; } = string.Empty; + public string Image { get; set; } = string.Empty; + public event EventHandler? onClicked = null; + + public void invokeClicked() { + onClicked?.Invoke( new object(), new EventArgs() ); + } + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Client/_Imports.razor b/src/MistoxWebsite.Client/_Imports.razor new file mode 100644 index 0000000..a5712ea --- /dev/null +++ b/src/MistoxWebsite.Client/_Imports.razor @@ -0,0 +1,26 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using MistoxWebsite.Client + +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@using System.Security.Claims +@using MistoxWebsite.Client.AuthState +@using MistoxWebsite.Client.Statics +@using System.Security.Cryptography +@using System.Text +@using MistoxWebsite.Shared +@using Newtonsoft.Json + +@using MistoxWebsite.Client.Pages.Shared + +@inject IJSRuntime JS +@inject NavigationManager Nav +@inject HttpClient Http +@inject AuthenticationStateProvider Auth \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Controllers/AuthenticationController.cs b/src/MistoxWebsite.Server/Controllers/AuthenticationController.cs new file mode 100644 index 0000000..407c8d4 --- /dev/null +++ b/src/MistoxWebsite.Server/Controllers/AuthenticationController.cs @@ -0,0 +1,387 @@ +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, "serverAuth" ) ), + new AuthenticationProperties { + AllowRefresh = true, + IssuedUtc = DateTime.UtcNow, + ExpiresUtc = DateTime.UtcNow.AddDays( 32 ), + IsPersistent = true, + } + ); + 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.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 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 100644 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 100644 index 0000000..df87f62 --- /dev/null +++ b/src/MistoxWebsite.Server/Controllers/PaymentController.cs @@ -0,0 +1,165 @@ +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +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 ) { + + // Stored Variables + string OrderNumber = Guid.NewGuid().ToString().Substring(0,10); + int subtotal = 0; + + // Get the user purchasing the items + string UserID = ""; + Shared.Account? acc = await _databaseService.GetAccount(userID); + if( acc != null ) { + UserID = acc.ID.ToString(); + List cart = await _databaseService.GetCart( acc ); + + // 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 + 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", UserID }, + { "products", csv }, + { "subtotal", subtotal.ToString() }, + { "total", result.AmountTotal.ToString() } + }, + StatementDescriptor = "Mistox.Net #" + OrderNumber + }; + + PaymentIntentService intentService = new PaymentIntentService(); + PaymentIntent x = await intentService.CreateAsync( paymentIntent ); + return x.ClientSecret; + } + 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/ProductController.cs b/src/MistoxWebsite.Server/Controllers/ProductController.cs new file mode 100644 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 100644 index 0000000..2466e20 --- /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 100644 index 0000000..39e2edc --- /dev/null +++ b/src/MistoxWebsite.Server/Program.cs @@ -0,0 +1,75 @@ +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 : "oasv34$8gpv023dd"; +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 : "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 : "no-reply@mistox.com"; +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(); + +builder.Services.AddCors( o => o.AddDefaultPolicy( builder => { + builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); +} ) ); + +// 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 index 0000000..7cc0a86 --- /dev/null +++ b/src/MistoxWebsite.Server/Services/EmailService/EmailService.cs @@ -0,0 +1,42 @@ +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 100644 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 100644 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 100644 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 100644 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 100644 index 0000000..9978cd3 --- /dev/null +++ b/src/MistoxWebsite.Server/wwwroot/css/app.css @@ -0,0 +1,169 @@ +: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 .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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 diff --git a/src/MistoxWebsite.Shared/DatabaseObjects.cs b/src/MistoxWebsite.Shared/DatabaseObjects.cs new file mode 100644 index 0000000..5988e6c --- /dev/null +++ b/src/MistoxWebsite.Shared/DatabaseObjects.cs @@ -0,0 +1,119 @@ +using System.Diagnostics; + +// Reflections of SQL Database objects + +namespace MistoxWebsite.Shared { + + public class PageLoadObject { + public Account? user { get; set; } + public AccountClaims? claims { get; set; } + public List? receipts { get; set; } + public List? products { get; set; } + public List? Cart { get; set; } + } + + public class DirObj { + public FileType? Type { get; set; } + public string Path { get; set; } = ""; + public DirObj? [] Children { get; set; } = new DirObj?[0]; + } + + public enum FileType { + File, + Directory + } + + public class Account { + public int ID { get; set; } // PK + public string UserName { get; set; } = ""; + public string Email { get; set; } = ""; + public bool EmailVerified { get; set; } = false; + public string PasswordHash { get; set; } = ""; + public WebSiteData SiteData { get; set; } = new WebSiteData(); + public string Error { get; set; } = ""; + } + + public class Product { + public int ID { get; set; } // PK + public string Name { get; set; } = ""; + public string Description { get; set; } = ""; + public int CurShowingIMG = 0; + public List Images { get; set; } = new List(); + public int Cost { get; set; } + public string URL { get; set; } = ""; + } + + public class WebSiteData { + public int AccountID { get; set; } // PK + public bool FailedPasswordLock { get; set; } = false; + public int PasswordAttempts { get; set; } = 5; + public int CurrentPasswordAttempts { get; set; } = 0; + public string Role { get; set; } = "Generic"; + public string EmailToken { get; set; } = ""; + } + + public class AccountInventory { + public int AccountID { get; set; } // PK + public int ProductID { get; set; } // PK + public string Item { get; set; } = string.Empty; // PK + public int Quantity { get; set; } + public string Stats { get; set; } = string.Empty; + } + + public class UserInventory { + public string Item { get; set; } = string.Empty; + public int Quantity { get; set; } + public string Stats { get; set; } = string.Empty; + } + + public class Receipt { + public int AccountID { get; set; } // PK + public int ProductID { get; set; } // PK + public string ReceiptID { get; set; } = string.Empty; + public int LineItem { get; set; } + public int TaxAmount { get; set; } + public int TotalCost { get; set; } + public DateTime Time { get; set; } + } + + public class ReceiptProduct { + public Receipt receipt { get; set; } = new Receipt(); + public Product product { get; set; } = new Product(); + } + + public class Cart { + public int ID { get; set; } + public int AccountID { get; set; } + public int ProductID { get; set; } + } + + public class ProjectMistData { + public int AccountID { get; set; } // PK + public int Credits { get; set; } + public int OddballTimer { get; set; } + public string SessionToken { get; set; } = ""; + public int SessionID { get; set; } + public int Kills { get; set; } + public int Deaths { get; set; } + } + + public class AccountClaims { + public string UserName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string EmailVerified { get; set; } = string.Empty; + public string Role { get; set; } = string.Empty; + public string FailedPasswordLock { get; set; } = string.Empty; + } + + public class PaymentObject { + public string CardNumber { get; set; } = string.Empty; + public long ExperationMonth { get; set; } + public long ExperationYear { get; set; } + public string CVC { get; set; } = string.Empty; + public string FullName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Zip { get; set; } = string.Empty; + public List productIDs { get; set; } = new List(); + } + +} \ No newline at end of file diff --git a/src/MistoxWebsite.Shared/MistoxWebsite.Shared.csproj b/src/MistoxWebsite.Shared/MistoxWebsite.Shared.csproj new file mode 100644 index 0000000..cdc91ea --- /dev/null +++ b/src/MistoxWebsite.Shared/MistoxWebsite.Shared.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + +