Skip to content

Commit b1dd92c

Browse files
committed
Web App
1 parent bda89c0 commit b1dd92c

44 files changed

Lines changed: 845 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.dll
2+
*.obj
3+
*.sig.txt
4+
*.json
5+
bin/
6+
obj/
7+
properties/
8+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="System.Data.SqlClient" Version="4.8.3" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
// This code is designed to be as simple as possible, not pulling in lots of libraries and frameworks such as EF and MVC.
2+
using System.Data;
3+
using System.Data.SqlClient;
4+
using System.Text;
5+
using System.Text.Json;
6+
using System.Security.Cryptography;
7+
8+
var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
9+
Args = args,
10+
ApplicationName = typeof(Program).Assembly.FullName,
11+
ContentRootPath = Directory.GetCurrentDirectory(),
12+
WebRootPath = Directory.GetCurrentDirectory()
13+
});
14+
15+
var app = builder.Build();
16+
app.UseRouting();
17+
app.UseDefaultFiles();
18+
app.UseStaticFiles();
19+
20+
string htmlIndexStart = File.ReadAllText("indexStart.html");
21+
string htmlError = File.ReadAllText("error.html");
22+
string htmlSuccess = File.ReadAllText("success.html");
23+
24+
// this code connects to SQL as the user of the web app
25+
const string connString = @"Server=.\SQL2022;Database=WorldCup;Trusted_Connection=True;";
26+
27+
// root folder
28+
app.MapGet("/", async context => {
29+
Console.WriteLine($"Connection from {context.Connection.RemoteIpAddress}");
30+
31+
// get list of moneylines
32+
using var con = new SqlConnection(connString);
33+
con.Open();
34+
35+
using var cmd = new SqlCommand("SELECT * from [dbo].[MoneyLine]", con);
36+
var rows = cmd.ExecuteReader();
37+
38+
// create array of games from the moneyline data
39+
List<Game> games = new List<Game>();
40+
while(rows.Read()) {
41+
var game = new Game((int)rows.GetValue(0),
42+
(string)rows.GetValue(1),
43+
(int)rows.GetValue(2),
44+
(int)rows.GetValue(3),
45+
(string)rows.GetValue(4),
46+
(int)rows.GetValue(5),
47+
(DateTime)rows.GetValue(6));
48+
games.Add(game);
49+
}
50+
51+
// Build the resulting HTML on the fly!
52+
// One <TR> per game, multiple <TD>s
53+
var sbHtml = new StringBuilder(htmlIndexStart);
54+
const string TR=@"<TR>", TD = @"<TD>", SpanTD=@"<TD colspan=3>";
55+
const string EndTR = @"</TR>", EndTD = @"</TD>";
56+
const string Radio = @"<input type='radio' name='bet' value='{0}|{1}|{2}|{3}'>";
57+
foreach (var g in games) {
58+
59+
// Country vs Country heading
60+
sbHtml.Append(TR);
61+
sbHtml.Append(SpanTD);
62+
var imgHomeFlag = @"<img src='img/" + g.HomeCountry.Replace(" ", "") + @".png' width=14>&nbsp;";
63+
var imgVisitFlag = @"<img src='img/" + g.VisitCountry.Replace(" ", "") + @".png' width=14>&nbsp;";
64+
sbHtml.Append(imgHomeFlag + g.HomeCountry + " vs " + imgVisitFlag + g.VisitCountry); // + " on " + g.GameDateTime);
65+
sbHtml.Append(EndTD);
66+
sbHtml.Append(EndTR);
67+
68+
// The three sets of odds
69+
sbHtml.Append(TR);
70+
// Win
71+
sbHtml.Append(TD);
72+
sbHtml.Append(string.Format(Radio, g.MoneyLineID, g.HomeCountry, "W", g.HomeCountryOdds));
73+
sbHtml.Append(g.HomeCountry + " " + g.HomeCountryOdds);
74+
sbHtml.Append(EndTD);
75+
76+
// Draw
77+
sbHtml.Append(TD);
78+
sbHtml.Append(string.Format(Radio, g.MoneyLineID, g.HomeCountry, "D", g.DrawOdds));
79+
sbHtml.Append("Draw " + g.DrawOdds);
80+
sbHtml.Append(EndTD);
81+
82+
// Loss
83+
sbHtml.Append(TD);
84+
sbHtml.Append(string.Format(Radio, g.MoneyLineID, g.HomeCountry, "L", g.VisitCountryOdds));
85+
sbHtml.Append(g.VisitCountry + " " + g.VisitCountryOdds);
86+
sbHtml.Append(EndTD);
87+
sbHtml.Append(EndTR);
88+
89+
sbHtml.Append("\n");
90+
}
91+
92+
// close off the HTML page
93+
sbHtml.Append("</table><p></p><input type='submit' value='Place Bet' style='height:70px; width:200px; font-size:1.5em; color:#FFFFFF; background-color:#808080'></form></body></html>");
94+
95+
96+
await context.Response.WriteAsync(sbHtml.ToString());
97+
});
98+
99+
// place a bet and insert into SQL
100+
101+
app.MapGet("/placebet", async context => {
102+
//string name = context.Request.Query["flname"];
103+
string amount = context.Request.Query["betamount"];
104+
string moneyline = context.Request.Query["bet"];
105+
string name = "Pieter Vanhove";
106+
Console.WriteLine($"Request for bet from {name} for {amount} on {moneyline}");
107+
108+
string fName = "", lName = "";
109+
string Country = "", result = "";
110+
var odds = 0;
111+
var moneylineId = 0;
112+
var amount2 = 0;
113+
114+
// this validates the three input args and if there're no errors, returns them in the ref args
115+
// moneyline is a string of four values separated by a '|'
116+
if (!ValidateRequest(name,
117+
amount,
118+
moneyline,
119+
ref fName,
120+
ref lName,
121+
ref moneylineId,
122+
ref Country,
123+
ref result,
124+
ref odds,
125+
ref amount2)) {
126+
await context.Response.WriteAsync(htmlError);
127+
} else {
128+
var cs = connString;
129+
130+
using var con = new SqlConnection(cs);
131+
con.Open();
132+
133+
using (var cmd = new SqlCommand("usp_PlaceBet", con)) {
134+
cmd.Parameters.AddWithValue("@MoneylineID", moneylineId);
135+
cmd.Parameters.AddWithValue("@FirstName", fName);
136+
cmd.Parameters.AddWithValue("@LastName", lName);
137+
cmd.Parameters.AddWithValue("@Country", Country);
138+
cmd.Parameters.AddWithValue("@Bet", amount2);
139+
cmd.Parameters.AddWithValue("@Odds", odds);
140+
141+
cmd.CommandType = CommandType.StoredProcedure;
142+
cmd.ExecuteNonQuery();
143+
}
144+
145+
// add the receipt tp the resulting confirmation page
146+
string? digest = GetLedgerDigest(con);
147+
if (!string.IsNullOrEmpty(digest)) {
148+
string sigFilename = CreateDownloadableSigBlock(digest);
149+
htmlSuccess = htmlSuccess.Replace("%F%", sigFilename);
150+
151+
await context.Response.WriteAsync(htmlSuccess);
152+
} else {
153+
await context.Response.WriteAsync("Unable to download receipt.");
154+
}
155+
}
156+
});
157+
158+
// Get SQL Server version
159+
app.MapGet("/version", async context => {
160+
string? v = GetSqlVersion();
161+
await context.Response.WriteAsync(string.IsNullOrEmpty(v) ? "Unable to get SQL Server version" : v);
162+
});
163+
164+
bool ValidateRequest(string name,
165+
string amount,
166+
string moneyline,
167+
ref string fName,
168+
ref string lName,
169+
ref int moneylineId,
170+
ref string Country,
171+
ref string result,
172+
ref int odds,
173+
ref int amount2) {
174+
175+
if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(amount) && !string.IsNullOrEmpty(moneyline)) {
176+
// Get user name
177+
string[] flname = name.Split(' ', 2);
178+
if (flname.Count() != 2)
179+
return false;
180+
181+
// get bet amount
182+
UInt32 intAmount = 0;
183+
if (!UInt32.TryParse(amount, out intAmount))
184+
return false;
185+
186+
// moneyline is four fields:
187+
// FirstName|LastName|Wager|Odds
188+
const int NUM_FIELDS = 4;
189+
string[] moneylineItems = moneyline.Split('|', NUM_FIELDS);
190+
if (moneylineItems.Count() != NUM_FIELDS)
191+
return false;
192+
193+
if (!Int32.TryParse(amount, out amount2))
194+
return false;
195+
196+
// get user's name
197+
fName = flname[0];
198+
lName = flname[1];
199+
200+
// get bet details
201+
if (!Int32.TryParse(moneylineItems[0], out moneylineId))
202+
return false;
203+
204+
// Home country and Win, Draw or Loss
205+
Country = moneylineItems[1];
206+
result = moneylineItems[2];
207+
208+
// odds
209+
if (!Int32.TryParse(moneylineItems[3], out odds))
210+
return false;
211+
212+
return true;
213+
}
214+
215+
return false;
216+
}
217+
218+
// SQL query to get SQL Server version
219+
string? GetSqlVersion() {
220+
using var con = new SqlConnection(connString);
221+
con.Open();
222+
223+
using var cmd = new SqlCommand("SELECT @@VERSION", con);
224+
return cmd.ExecuteScalar()?.ToString();
225+
}
226+
227+
// Get the last tx digest
228+
string? GetLedgerDigest(SqlConnection con) {
229+
using var cmd = new SqlCommand("sp_generate_database_ledger_digest", con);
230+
return cmd.ExecuteScalar()?.ToString();
231+
}
232+
233+
/* NOT USED
234+
// sign some data, this creates real signatures,
235+
// but the keys are ephemeral for demo purposes only
236+
string SignBlob(string text) {
237+
const string CRYPTO_VERSION = "01";
238+
239+
using SHA256 alg = SHA256.Create(); // in future add cryptoagility
240+
byte[] data = Encoding.ASCII.GetBytes(text);
241+
byte[] hash = alg.ComputeHash(data);
242+
243+
// for demo only, we should really load a cert/key from the cert store
244+
using (RSA rsa = RSA.Create()) {
245+
RSAPKCS1SignatureFormatter rsaForm = new(rsa);
246+
rsaForm.SetHashAlgorithm(nameof(SHA256));
247+
byte[] sig = rsaForm.CreateSignature(hash);
248+
var sigBase64 = Convert.ToBase64String(sig);
249+
return CRYPTO_VERSION + "|" + sigBase64;
250+
}
251+
}
252+
*/
253+
// creates a file with a random name
254+
string CreateDownloadableSigBlock(string sig) {
255+
var filename = Guid.NewGuid() + ".json";
256+
using (var fs = File.Create(filename)) {
257+
byte[] b = new UTF8Encoding(true).GetBytes(sig);
258+
fs.Write(b,0,b.Length);
259+
}
260+
261+
return filename;
262+
}
263+
264+
// Start the web app
265+
app.Run();
266+
267+
// struct to hold game details
268+
public struct Game {
269+
public Game(int MoneyLineID, string HomeCountry, int HomeCountryOdds, int DrawOdds, string VisitCountry, int VisitCountryOdds, DateTime GameDateTime) {
270+
this.MoneyLineID = MoneyLineID;
271+
this.HomeCountry = HomeCountry;
272+
this.HomeCountryOdds = HomeCountryOdds;
273+
this.DrawOdds = DrawOdds;
274+
this.VisitCountry = VisitCountry;
275+
this.VisitCountryOdds = VisitCountryOdds;
276+
this.GameDateTime = GameDateTime;
277+
}
278+
public int MoneyLineID;
279+
public string HomeCountry;
280+
public int HomeCountryOdds;
281+
public int DrawOdds;
282+
public string VisitCountry;
283+
public int VisitCountryOdds;
284+
public DateTime GameDateTime;
285+
}

0 commit comments

Comments
 (0)