From 207edf0de38fb634a6f608017aad296625c39288 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 26 Feb 2022 22:23:30 +1000 Subject: [PATCH] Migration system added. Install scripts for database schema and dummy data too. --- Authenticate/Model.fs | 28 +- Core/Database.module.fs | 68 +++- Core/DredgeFramework.module.fs | 14 +- Core/GenericEntities.module.fs | 13 +- Core/Types.fs | 64 ++-- DredgePos.fsproj | 5 + Floorplan/Controller.fs | 24 +- Floorplan/Model.fs | 24 +- Floorplan/View.fs | 4 +- Installer/Controller.fs | 32 ++ Installer/Model.fs | 2 + Installer/Router.fs | 10 + Migrations/CreateDatabaseSchema.fs | 131 +++++++ Migrations/PopulateTestData.fs | 345 ++++++++++++++++++ OrderScreen/Model.fs | 8 +- Program.fs | 1 + Reservations/Model.fs | 4 +- config.json | 9 + sql.log | 10 + typescript/dredgepos.floorplan.ts | 66 ++-- typescript/dredgepos.orderScreen.ts | 24 +- typescript/types.ts | 36 +- .../items/{8bit.png => Beer/8 Bit IPA.png} | Bin .../items/{balter.png => Beer/Balter IPA.png} | Bin .../Black Hops Hornet.png} | Bin .../{brooklyn.png => Beer/Brooklyn Lager.png} | Bin .../{brouhaha.png => Beer/Brouhaha IPA.png} | Bin .../items/{budvar.png => Beer/Budvar.png} | Bin .../{budweiser.png => Beer/Budweiser.png} | Bin .../{casablanca.png => Beer/Casablanca.png} | Bin .../Colonial Hazy IPA.png} | Bin .../Coopers Session Ale.png} | Bin .../items/{corona.png => Beer/Corona.png} | Bin .../Crankshaft IPA.png} | Bin .../items/{cusquena.png => Beer/Cusquena.png} | Bin .../items/{elvis.png => Beer/Elvis Juice.png} | Bin .../{estrella.png => Beer/Estrella Damm.png} | Bin .../items/{fosters.png => Beer/Fosters.png} | Bin .../items/{goat.png => Beer/GOAT VEB.png} | Bin .../{galactopus.png => Beer/Galactopus.png} | Bin .../items/{guinness.png => Beer/Guinness.png} | Bin .../Hemingway XPA.png} | Bin .../items/{boags.png => Beer/James Boags.png} | Bin .../James Squire Hop Thief.png} | Bin .../items/{jedi.png => Beer/Jedi Juice.png} | Bin .../{kaiju.png => Beer/Kaijiu Krush.png} | Bin .../{kronenbourg.png => Beer/Kronenbourg.png} | Bin .../Matso's Ginger Beer.png} | Bin .../{mb.png => Beer/Melbourne Bitter.png} | Bin .../Modus Pale Ale.png} | Bin .../{moosehead.png => Beer/Moosehead.png} | Bin .../items/{nail.png => Beer/Nail Stout.png} | Bin .../Nomad Salt & Pepper Gose.png} | Bin .../{perthlocal.png => Beer/Perth Local.png} | Bin .../{pickledan.png => Beer/Pickle Dan.png} | Bin .../Pirate Life IPA.png} | Bin .../items/{quilmes.png => Beer/Quilmes.png} | Bin .../Slipstream Rye Pale Ale.png} | Bin .../{sludgebeast.png => Beer/Sludgebeast.png} | Bin .../Stone & Wood Pacific Ale.png} | Bin .../{pinkening.png => Beer/The Pinkening.png} | Bin .../{tooheysold.png => Beer/Toohey's Old.png} | Bin .../items/{tusker.png => Beer/Tusker.png} | Bin .../{taco.png => Beer/Two Birds Taco.png} | Bin .../images/items/{viru.png => Beer/Viru.png} | Bin .../items/{windhoek.png => Beer/Windhoek.png} | Bin .../{xxxxgold.png => Beer/XXXX Gold.png} | Bin .../Your Mates - Larry Pale Ale.png} | Bin .../items/{zambezi.png => Beer/Zambezi.png} | Bin .../items/{zulia.png => Beer/Zulia.png} | Bin .../{brulee.png => Dessert/Creme Brulee.png} | Bin .../Neopolitan.png} | Bin .../{pudding.png => Dessert/Rice Pudding.png} | Bin .../{tiramisu.png => Dessert/Tiramisu.png} | Bin .../Beetroot Cream Cheese.png} | Bin .../French Onion.png} | Bin .../items/{salsa.png => Dips/Mango Salsa.png} | Bin .../items/{tapenade.png => Dips/Tapenade.png} | Bin .../Bruschetta.png} | Bin .../{kofta.png => Entrees/Goat Kofta.png} | Bin .../{oysters.png => Entrees/Oysters.png} | Bin .../items/{pita.png => Entrees/Pita.png} | Bin .../{scallops.png => Entrees/Scallops.png} | Bin .../{ribs.png => Mains/Alabama BBQ Ribs.png} | Bin .../items/{burger.png => Mains/Burger.png} | Bin .../Laotian Green Curry.png} | Bin .../{squab.png => Mains/Squab Spatchcock.png} | Bin .../Venison Wellington.png} | Bin .../Whole Snapper.png} | Bin wwwroot/images/items/Not Beer/bacon.png | Bin 55135 -> 0 bytes wwwroot/images/items/Not Beer/hotdog.png | Bin 46727 -> 0 bytes wwwroot/images/items/Not Beer/no.png | Bin 38680 -> 0 bytes wwwroot/images/items/Not Beer/pineapple.png | Bin 45136 -> 0 bytes .../Blue.png} | Bin .../Medium Rare.png} | Bin .../Medium Well.png} | Bin .../Medium.png} | Bin .../Rare.png} | Bin .../Well Done.png} | Bin .../Chaffey Brothers GSM.png} | Bin .../paradigm.png => Wine/Chalmers Rosato.png} | Bin .../moet.png => Wine/Moet & Chandon.png} | Bin .../Pencarrow Sauvignon Blanc.png} | Bin wwwroot/images/items/bacon.png | Bin 55135 -> 0 bytes wwwroot/images/items/chaffey.png | Bin 17102 -> 0 bytes wwwroot/images/items/hotdog.png | Bin 46727 -> 0 bytes wwwroot/images/items/moet.png | Bin 20824 -> 0 bytes wwwroot/images/items/no.png | Bin 38680 -> 0 bytes wwwroot/images/items/paradigm.png | Bin 22053 -> 0 bytes wwwroot/images/items/pencarrow.png | Bin 17931 -> 0 bytes wwwroot/images/items/pineapple.png | Bin 45136 -> 0 bytes wwwroot/images/items/steakblue.png | Bin 27891 -> 0 bytes wwwroot/images/items/steakmedium.png | Bin 31813 -> 0 bytes wwwroot/images/items/steakmidrare.png | Bin 28766 -> 0 bytes wwwroot/images/items/steakmidwell.png | Bin 30047 -> 0 bytes wwwroot/images/items/steakrare.png | Bin 29785 -> 0 bytes wwwroot/images/items/steakwelldone.png | Bin 31066 -> 0 bytes ...ardbackground.jpg => Deck & Courtyard.jpg} | Bin .../{functionroom.jpg => Function Room.jpg} | Bin .../rooms/{barbackground.jpg => Inside.jpg} | Bin wwwroot/images/rooms/PineWoodGrain.jpg | Bin 7131004 -> 0 bytes wwwroot/images/rooms/barbackground.png | Bin 671913 -> 0 bytes 122 files changed, 774 insertions(+), 148 deletions(-) create mode 100644 Installer/Controller.fs create mode 100644 Installer/Model.fs create mode 100644 Installer/Router.fs create mode 100644 Migrations/CreateDatabaseSchema.fs create mode 100644 Migrations/PopulateTestData.fs create mode 100644 config.json create mode 100644 sql.log rename wwwroot/images/items/{8bit.png => Beer/8 Bit IPA.png} (100%) rename wwwroot/images/items/{balter.png => Beer/Balter IPA.png} (100%) rename wwwroot/images/items/{hornet.png => Beer/Black Hops Hornet.png} (100%) rename wwwroot/images/items/{brooklyn.png => Beer/Brooklyn Lager.png} (100%) rename wwwroot/images/items/{brouhaha.png => Beer/Brouhaha IPA.png} (100%) rename wwwroot/images/items/{budvar.png => Beer/Budvar.png} (100%) rename wwwroot/images/items/{budweiser.png => Beer/Budweiser.png} (100%) rename wwwroot/images/items/{casablanca.png => Beer/Casablanca.png} (100%) rename wwwroot/images/items/{colonialhazy.png => Beer/Colonial Hazy IPA.png} (100%) rename wwwroot/images/items/{cooperssession.png => Beer/Coopers Session Ale.png} (100%) rename wwwroot/images/items/{corona.png => Beer/Corona.png} (100%) rename wwwroot/images/items/{bentspoke.png => Beer/Crankshaft IPA.png} (100%) rename wwwroot/images/items/{cusquena.png => Beer/Cusquena.png} (100%) rename wwwroot/images/items/{elvis.png => Beer/Elvis Juice.png} (100%) rename wwwroot/images/items/{estrella.png => Beer/Estrella Damm.png} (100%) rename wwwroot/images/items/{fosters.png => Beer/Fosters.png} (100%) rename wwwroot/images/items/{goat.png => Beer/GOAT VEB.png} (100%) rename wwwroot/images/items/{galactopus.png => Beer/Galactopus.png} (100%) rename wwwroot/images/items/{guinness.png => Beer/Guinness.png} (100%) rename wwwroot/images/items/{hemingwayxpa.png => Beer/Hemingway XPA.png} (100%) rename wwwroot/images/items/{boags.png => Beer/James Boags.png} (100%) rename wwwroot/images/items/{hopthief.png => Beer/James Squire Hop Thief.png} (100%) rename wwwroot/images/items/{jedi.png => Beer/Jedi Juice.png} (100%) rename wwwroot/images/items/{kaiju.png => Beer/Kaijiu Krush.png} (100%) rename wwwroot/images/items/{kronenbourg.png => Beer/Kronenbourg.png} (100%) rename wwwroot/images/items/{matsos.png => Beer/Matso's Ginger Beer.png} (100%) rename wwwroot/images/items/{mb.png => Beer/Melbourne Bitter.png} (100%) rename wwwroot/images/items/{moduspale.png => Beer/Modus Pale Ale.png} (100%) rename wwwroot/images/items/{moosehead.png => Beer/Moosehead.png} (100%) rename wwwroot/images/items/{nail.png => Beer/Nail Stout.png} (100%) rename wwwroot/images/items/{nomad.png => Beer/Nomad Salt & Pepper Gose.png} (100%) rename wwwroot/images/items/{perthlocal.png => Beer/Perth Local.png} (100%) rename wwwroot/images/items/{pickledan.png => Beer/Pickle Dan.png} (100%) rename wwwroot/images/items/{piratelife.png => Beer/Pirate Life IPA.png} (100%) rename wwwroot/images/items/{quilmes.png => Beer/Quilmes.png} (100%) rename wwwroot/images/items/{slipstream.png => Beer/Slipstream Rye Pale Ale.png} (100%) rename wwwroot/images/items/{sludgebeast.png => Beer/Sludgebeast.png} (100%) rename wwwroot/images/items/{stonewood.png => Beer/Stone & Wood Pacific Ale.png} (100%) rename wwwroot/images/items/{pinkening.png => Beer/The Pinkening.png} (100%) rename wwwroot/images/items/{tooheysold.png => Beer/Toohey's Old.png} (100%) rename wwwroot/images/items/{tusker.png => Beer/Tusker.png} (100%) rename wwwroot/images/items/{taco.png => Beer/Two Birds Taco.png} (100%) rename wwwroot/images/items/{viru.png => Beer/Viru.png} (100%) rename wwwroot/images/items/{windhoek.png => Beer/Windhoek.png} (100%) rename wwwroot/images/items/{xxxxgold.png => Beer/XXXX Gold.png} (100%) rename wwwroot/images/items/{larry.png => Beer/Your Mates - Larry Pale Ale.png} (100%) rename wwwroot/images/items/{zambezi.png => Beer/Zambezi.png} (100%) rename wwwroot/images/items/{zulia.png => Beer/Zulia.png} (100%) rename wwwroot/images/items/{brulee.png => Dessert/Creme Brulee.png} (100%) rename wwwroot/images/items/{neopolitan.png => Dessert/Neopolitan.png} (100%) rename wwwroot/images/items/{pudding.png => Dessert/Rice Pudding.png} (100%) rename wwwroot/images/items/{tiramisu.png => Dessert/Tiramisu.png} (100%) rename wwwroot/images/items/{beetrootdip.png => Dips/Beetroot Cream Cheese.png} (100%) rename wwwroot/images/items/{frenchonion.png => Dips/French Onion.png} (100%) rename wwwroot/images/items/{salsa.png => Dips/Mango Salsa.png} (100%) rename wwwroot/images/items/{tapenade.png => Dips/Tapenade.png} (100%) rename wwwroot/images/items/{bruschetta.png => Entrees/Bruschetta.png} (100%) rename wwwroot/images/items/{kofta.png => Entrees/Goat Kofta.png} (100%) rename wwwroot/images/items/{oysters.png => Entrees/Oysters.png} (100%) rename wwwroot/images/items/{pita.png => Entrees/Pita.png} (100%) rename wwwroot/images/items/{scallops.png => Entrees/Scallops.png} (100%) rename wwwroot/images/items/{ribs.png => Mains/Alabama BBQ Ribs.png} (100%) rename wwwroot/images/items/{burger.png => Mains/Burger.png} (100%) rename wwwroot/images/items/{curry.png => Mains/Laotian Green Curry.png} (100%) rename wwwroot/images/items/{squab.png => Mains/Squab Spatchcock.png} (100%) rename wwwroot/images/items/{wellington.png => Mains/Venison Wellington.png} (100%) rename wwwroot/images/items/{wholefish.png => Mains/Whole Snapper.png} (100%) delete mode 100644 wwwroot/images/items/Not Beer/bacon.png delete mode 100644 wwwroot/images/items/Not Beer/hotdog.png delete mode 100644 wwwroot/images/items/Not Beer/no.png delete mode 100644 wwwroot/images/items/Not Beer/pineapple.png rename wwwroot/images/items/{Not Beer/steakblue.png => Steak Temperatures/Blue.png} (100%) rename wwwroot/images/items/{Not Beer/steakmidrare.png => Steak Temperatures/Medium Rare.png} (100%) rename wwwroot/images/items/{Not Beer/steakmidwell.png => Steak Temperatures/Medium Well.png} (100%) rename wwwroot/images/items/{Not Beer/steakmedium.png => Steak Temperatures/Medium.png} (100%) rename wwwroot/images/items/{Not Beer/steakrare.png => Steak Temperatures/Rare.png} (100%) rename wwwroot/images/items/{Not Beer/steakwelldone.png => Steak Temperatures/Well Done.png} (100%) rename wwwroot/images/items/{Not Beer/chaffey.png => Wine/Chaffey Brothers GSM.png} (100%) rename wwwroot/images/items/{Not Beer/paradigm.png => Wine/Chalmers Rosato.png} (100%) rename wwwroot/images/items/{Not Beer/moet.png => Wine/Moet & Chandon.png} (100%) rename wwwroot/images/items/{Not Beer/pencarrow.png => Wine/Pencarrow Sauvignon Blanc.png} (100%) delete mode 100644 wwwroot/images/items/bacon.png delete mode 100644 wwwroot/images/items/chaffey.png delete mode 100644 wwwroot/images/items/hotdog.png delete mode 100644 wwwroot/images/items/moet.png delete mode 100644 wwwroot/images/items/no.png delete mode 100644 wwwroot/images/items/paradigm.png delete mode 100644 wwwroot/images/items/pencarrow.png delete mode 100644 wwwroot/images/items/pineapple.png delete mode 100644 wwwroot/images/items/steakblue.png delete mode 100644 wwwroot/images/items/steakmedium.png delete mode 100644 wwwroot/images/items/steakmidrare.png delete mode 100644 wwwroot/images/items/steakmidwell.png delete mode 100644 wwwroot/images/items/steakrare.png delete mode 100644 wwwroot/images/items/steakwelldone.png rename wwwroot/images/rooms/{courtyardbackground.jpg => Deck & Courtyard.jpg} (100%) rename wwwroot/images/rooms/{functionroom.jpg => Function Room.jpg} (100%) rename wwwroot/images/rooms/{barbackground.jpg => Inside.jpg} (100%) delete mode 100644 wwwroot/images/rooms/PineWoodGrain.jpg delete mode 100644 wwwroot/images/rooms/barbackground.png diff --git a/Authenticate/Model.fs b/Authenticate/Model.fs index ddd65aa..919a772 100644 --- a/Authenticate/Model.fs +++ b/Authenticate/Model.fs @@ -11,10 +11,10 @@ let getClerkByLoginCode (loginCode: int) = let clerk = select { table "clerks" - where (eq "clerk_login_code" loginCode) + where (eq "login_code" loginCode) take 1 } - |> db.Select + |> Database.Select |> EnumerableToArray if (clerk |> length) > 0 then @@ -26,19 +26,19 @@ let deleteSession sessionId context = delete { table "sessions" where (eq "session_id" sessionId) - } |> db.Delete |> ignore + } |> Database.Delete |> ignore Browser.deleteCookie "dredgepos_clerk_logged_in" context let deleteSessionByClerkId clerk_id context = delete { table "sessions" where (eq "clerk_id" clerk_id) - } |> db.Delete |> ignore + } |> Database.Delete |> ignore Browser.deleteCookie "dredgepos_clerk_logged_in" context let createNewSession (clerk: clerk) context = - if (getClerkByLoginCode clerk.clerk_login_code).IsSome then + if (getClerkByLoginCode clerk.login_code).IsSome then deleteSessionByClerkId clerk.id context let newSessionId = (Guid.NewGuid().ToString "N") + (Guid.NewGuid().ToString "N") @@ -53,18 +53,14 @@ let createNewSession (clerk: clerk) context = table "sessions" value newSession } - |> db.Insert + |> Database.Insert |> ignore Browser.setCookie "dredgepos_clerk_logged_in" newSessionId (DateTimeOffset.UtcNow.AddHours(24.0)) context let sessionExists (sessionId: string) context = - let sessions = - select { - table "sessions" - where (eq "session_id" sessionId) - } |> db.Select + let sessions = Entity.GetAllByColumn "session_id" sessionId match sessions |> length with | 0 -> false @@ -78,11 +74,11 @@ let sessionExists (sessionId: string) context = false let checkAuthentication clerk = - let existingClerk = getClerkByLoginCode clerk.clerk_login_code + let existingClerk = getClerkByLoginCode clerk.login_code existingClerk.IsSome && existingClerk.Value.id = clerk.id - && existingClerk.Value.clerk_name = clerk.clerk_name - && existingClerk.Value.clerk_login_code = clerk.clerk_login_code + && existingClerk.Value.name = clerk.name + && existingClerk.Value.login_code = clerk.login_code let getLoginCookie context = Browser.getCookie "dredgepos_clerk_logged_in" context @@ -91,7 +87,7 @@ let getSession (sessionId: string) = select { table "sessions" where (eq "session_id" sessionId) - } |> db.Select + } |> Database.Select match sessions |> length with | 0 -> {session_id = ""; clerk_json = ""; clerk_id= 0; expires= 0; id=0} @@ -99,7 +95,7 @@ let getSession (sessionId: string) = let getCurrentClerk context = let cookie = getLoginCookie context - let emptyClerk = {id=0; clerk_login_code=0; clerk_usergroup=0; clerk_name=""} + let emptyClerk = {id=0; login_code=0; user_group_id=0; name=""} match cookie with | "" -> Browser.redirect "/login" context diff --git a/Core/Database.module.fs b/Core/Database.module.fs index 68ba70e..76fb922 100644 --- a/Core/Database.module.fs +++ b/Core/Database.module.fs @@ -1,46 +1,98 @@ -module db +module Database open Dapper -open Dapper.FSharp open Dapper.FSharp.PostgreSQL - - open DredgeFramework +open DredgePos.Types +open Npgsql -let connString = "Server=localhost;Port=5432;User Id=postgres;Password=root;Database=dredgepos;Include Error Detail=true" -//let connString = "server=localhost;uid=root;pwd=;database=dredgepos;table cache = false" -let connection = new Npgsql.NpgsqlConnection(connString) + +let connect connectionString = new NpgsqlConnection(connectionString) + +let getDatabaseSettings () = (getConfig ()).database + +let getConnectionString () = + let db = getDatabaseSettings () + $"Server={db.host};Port={db.port};User Id={db.username};Password={db.password};Database={db.db_name};Include Error Detail=true" + +let connectToDatabase () = connect (getConnectionString ()) + +let closeAndReturn (connection: NpgsqlConnection) (result: 'a) = + connection.Dispose() + result let Select<'a> asyncQuery = + let connection = connectToDatabase () asyncQuery |> connection.SelectAsync<'a> |> RunSynchronously |> EnumerableToArray + |> closeAndReturn connection let SelectJoin<'a, 'b> asyncQuery = + let connection = connectToDatabase () asyncQuery |> connection.SelectAsync<'a, 'b> |> RunSynchronously |> EnumerableToArray + |> closeAndReturn connection + let Insert<'a> asyncQuery = + let connection = connectToDatabase () asyncQuery |> connection.InsertAsync<'a> |> RunSynchronously + |> closeAndReturn connection + let InsertOutput<'a> asyncQuery = + let connection = connectToDatabase () asyncQuery |> connection.InsertOutputAsync<'a, 'a> |> RunSynchronously |> EnumerableToArray + |> closeAndReturn connection let Update<'a> asyncQuery = + let connection = connectToDatabase () asyncQuery |> connection.UpdateOutputAsync<'a, 'a> |> RunSynchronously |> EnumerableToArray + |> closeAndReturn connection let Delete<'a> asyncQuery = + let connection = connectToDatabase () asyncQuery |> connection.DeleteAsync - |> RunSynchronously \ No newline at end of file + |> RunSynchronously + |> closeAndReturn connection + +let NonDbSpecificQuery (sql: string) (connection: NpgsqlConnection) = + sql + |> fun str -> System.IO.File.WriteAllText("sql.log", str); str + |> connection.Execute + |> closeAndReturn connection + +let rawQuery (sql: string) = connectToDatabase () |> NonDbSpecificQuery sql + +let CreateTable (tableName: string) (columnList: (string * string) list) = + let columns = + columnList + |> List.filter (fun (columnName, _) -> columnName <> "id") + |> List.map (fun (columnName, columnType) -> $""" "{columnName}" {columnType} not null""") + |> String.concat ",\n\t\t\t" + + $""" + create table if not exists {tableName} + ( + id serial + constraint {tableName}_pk + primary key, + {columns} + ); + """ + |> fun str -> System.IO.File.WriteAllText("sql.log", str); str + |> rawQuery + |> ignore \ No newline at end of file diff --git a/Core/DredgeFramework.module.fs b/Core/DredgeFramework.module.fs index bf6f597..f61eb16 100644 --- a/Core/DredgeFramework.module.fs +++ b/Core/DredgeFramework.module.fs @@ -7,8 +7,8 @@ open System.Drawing open System.IO open System.Linq open System.Xml; -open System.Xml.XPath; open System.Xml.Xsl +open DredgePos.Types open FSharp.Reflection open Thoth.Json.Net @@ -100,4 +100,14 @@ let GetImageSize image = let loadedImage = loadImage image loadedImage.Width, loadedImage.Height -let CurrentTime() = DateTimeOffset.Now.ToUnixTimeSeconds() |> int \ No newline at end of file +let CurrentTime() = DateTimeOffset.Now.ToUnixTimeSeconds() |> int + +let getConfig () = + "config.json" + |> GetFileContents + |> Decode.Auto.fromString + |> (fun result -> + match result with + | Ok config -> config + | Error message -> failwith ("config.json is not valid :" + message) + ) \ No newline at end of file diff --git a/Core/GenericEntities.module.fs b/Core/GenericEntities.module.fs index 7fb00f9..6bf1723 100644 --- a/Core/GenericEntities.module.fs +++ b/Core/GenericEntities.module.fs @@ -1,4 +1,5 @@ module Entity +open Dapper open Dapper.FSharp open DredgeFramework open Pluralize.NET.Core @@ -15,7 +16,7 @@ let Create (record: 'x)= value record excludeColumn "id" } - |> db.InsertOutput + |> Database.InsertOutput |> first @@ -28,7 +29,7 @@ let inline Update (record: ^x) = where (eq "id" id) excludeColumn "id" } - |> db.Update + |> Database.Update let GetAll<'x> = let tableName = GetDatabaseTable<'x> @@ -36,7 +37,7 @@ let GetAll<'x> = select { table tableName } - |> db.Select<'x> + |> Database.Select<'x> let GetAllByColumn<'x> (column: string) (value: obj) = let tableName = GetDatabaseTable<'x> @@ -44,7 +45,9 @@ let GetAllByColumn<'x> (column: string) (value: obj) = select { table tableName where (eq column value) - } |> db.Select<'x> + } |> Database.Select<'x> + +let GetFirstByColumn<'x> (column: string) (value: obj) = (GetAllByColumn<'x> column value) |> first let GetAllInVenue<'x> = GetAllByColumn<'x> "venue_id" (getCurrentVenue ()) let GetById<'x> (id: int) = GetAllByColumn<'x> "id" id |> first @@ -67,7 +70,7 @@ let DeleteById<'x> id = delete { table tableName where (eq "id" id) - } |> db.Delete |> ignore + } |> Database.Delete |> ignore entity diff --git a/Core/Types.fs b/Core/Types.fs index c6136ec..4830a8e 100644 --- a/Core/Types.fs +++ b/Core/Types.fs @@ -13,7 +13,7 @@ type reservation = { [] type venue = { id: int - venue_name: string + name: string } [] @@ -38,7 +38,7 @@ type floorplan_table = { type print_group = { id: int name: string - printer: int + printer_id: int venue_id: int } @@ -47,14 +47,14 @@ type sales_category = { id: int parent: int name: string - print_group: int + print_group_id: int venue_id: int } [] -type floorplan_room = { +type room = { id: int - room_name: string + name: string background_image: string venue_id: int } @@ -62,18 +62,23 @@ type floorplan_room = { [] type floorplan_decoration = { id: int - decoration_room: int - decoration_pos_x: int - decoration_pos_y: int - decoration_rotation: int - decoration_width: int - decoration_height: int - decoration_image: string + room_id: int + pos_x: int + pos_y: int + rotation: int + width: int + height: int + image: string venue_id: int } [] -type clerk = {id: int; clerk_name: string; clerk_login_code: int; clerk_usergroup: int} +type clerk = { + id: int + name: string + login_code: int + user_group_id: int +} [] type session = {id: int; session_id: string; clerk_json: string; clerk_id: int; expires: int} @@ -82,7 +87,7 @@ type session = {id: int; session_id: string; clerk_json: string; clerk_id: int; type order_screen_page_group = {id: int; order: int; venue_id: int; label: string; grid_id: int} [] -type grid = {id: int; grid_name: string; grid_rows: int; grid_cols: int; grid_data: string} +type grid = {id: int; name: string; rows: int; cols: int; data: string} [] type button = { @@ -100,13 +105,30 @@ type button = { [] type item = { id: int - item_code: string - item_category: int - item_name: string + code: string + sales_category_id: int + name: string item_type: string price1: int - price2: int - price3: int - price4: int - price5: int } + +[] +type db_config = { + db_name: string + username: string + password: string + host: string + port: int +} + +[] +type config = { + database: db_config +} + +[] +type migration = { + id: int + name: string + timestamp: int +} \ No newline at end of file diff --git a/DredgePos.fsproj b/DredgePos.fsproj index a91790b..dfd31b3 100644 --- a/DredgePos.fsproj +++ b/DredgePos.fsproj @@ -26,6 +26,11 @@ + + + + + diff --git a/Floorplan/Controller.fs b/Floorplan/Controller.fs index 09087bd..903970d 100644 --- a/Floorplan/Controller.fs +++ b/Floorplan/Controller.fs @@ -10,10 +10,10 @@ open Microsoft.AspNetCore.Http open Model open System.IO -let makeRoomButton (room: floorplan_room) = +let makeRoomButton (room: room) = let vars = map [ "roomId", room.id |> string - "roomName", room.room_name + "roomName", room.name ] Theme.loadTemplateWithVars "roomButton" vars @@ -50,7 +50,7 @@ let getFloorplanData (id: int) = tables = tableList decorations = Entity.GetAllInVenue activeTableNumbers = Model.getActiveTables (getCurrentVenue()) - rooms = Entity.GetAllInVenue + rooms = Entity.GetAllInVenue reservations = reservationList |} |> ajaxSuccess @@ -78,19 +78,19 @@ let transferTable (origin, destination) = ajaxSuccess data |> json let AddDecoration (data: floorplan_decoration) = - let image = "wwwroot/images/decorations/" + data.decoration_image + let image = "wwwroot/images/decorations/" + data.image let width, height = image |> GetImageSize let aspectRatio = decimal width / decimal height let decoration : floorplan_decoration = { id = 0 - decoration_height = (200m / aspectRatio) |> int - decoration_width = 200 - decoration_rotation = 0 - decoration_image = data.decoration_image - decoration_pos_x = data.decoration_pos_x - decoration_pos_y = data.decoration_pos_y - decoration_room = data.decoration_room + height = (200m / aspectRatio) |> int + width = 200 + rotation = 0 + image = data.image + pos_x = data.pos_x + pos_y = data.pos_y + room_id = data.room_id venue_id = data.venue_id } @@ -110,7 +110,7 @@ let DeleteDecoration (decorationToDelete: floorplan_decoration) = let loadFloorplanView (ctx: HttpContext) = Authenticate.Model.RequireClerkAuthentication ctx - let roomMenu = Entity.GetAllInVenue |> Array.map View.roomButton + let roomMenu = Entity.GetAllInVenue |> Array.map View.roomButton let currentClerk = Authenticate.Model.getCurrentClerk ctx let styles = [|"dredgepos.floorplan.css"|] |> addDefaultStyles let scripts = [|"./external/konva.min.js" ; "dredgepos.floorplan.js"|] |> addDefaultScripts diff --git a/Floorplan/Model.fs b/Floorplan/Model.fs index b54081e..f7ac2db 100644 --- a/Floorplan/Model.fs +++ b/Floorplan/Model.fs @@ -39,7 +39,7 @@ let tablesInRoom (roomId: int) = //Get a list of all tables in a particular room table "floorplan_tables" where (eq "room_id" roomId) } - |> db.Select + |> Database.Select let getActiveTables (venueId: int) = @@ -47,7 +47,7 @@ let getActiveTables (venueId: int) = table "floorplan_tables" where (eq "venue_id" venueId) } - |> db.Select + |> Database.Select |> Array.filter tableIsOpen |> Array.map (fun table -> table.table_number) @@ -93,7 +93,7 @@ let getTable (tableNumber : int) = where (eq "table_number" tableNumber + eq "venue_id" (getCurrentVenue())) } - let result = query |> db.Select + let result = query |> Database.Select result |> first let getTableById (id : int) = @@ -101,14 +101,14 @@ let getTableById (id : int) = table "floorplan_tables" where (eq "id" id) } - |> db.Select + |> Database.Select |> first let getRoom (roomId: int) = select { table "floorplan_rooms" where (eq "id" roomId) - } |> db.Select |> first + } |> Database.Select |> first let updateTablePosition (floorplanTable: floorplan_table) = Entity.Update floorplanTable @@ -117,7 +117,7 @@ let createEmptyReservation (reservation: reservation) = table "floorplan_tables" set {| status = 2 |} where(eq "id" reservation.floorplan_table_id) - } |> db.Update |> ignore + } |> Database.Update |> ignore Entity.Create reservation @@ -154,7 +154,7 @@ let tableExists (tableNumber: int) = select{ table "floorplan_tables" where (eq "table_number" tableNumber + eq "venue_id" (getCurrentVenue())) - } |> db.Select |> length + } |> Database.Select |> length match numberOfResults with | 0 -> @@ -169,14 +169,14 @@ let tableExists (tableNumber: int) = | _ -> let parentTableData = getTable allTables[0] let parentRoom = getRoom parentTableData.room_id - let parentRoomName = parentRoom.room_name + let parentRoomName = parentRoom.name language.getAndReplace "error_table_exists_merged" [parentRoomName; parentTableData.table_number.ToString()] | _ -> let tableData = getTable tableNumber let room = getRoom tableData.room_id - language.getAndReplace "error_table_exists" [room.room_name] + language.getAndReplace "error_table_exists" [room.name] let addNewTableWithoutOutput (newTable: floorplan_table) = @@ -184,7 +184,7 @@ let addNewTableWithoutOutput (newTable: floorplan_table) = table "floorplan_tables" value newTable } - |> db.Insert + |> Database.Insert let addNewTable (newTable: floorplan_table) = Entity.Create newTable @@ -238,7 +238,7 @@ let mergeTables parent child = //Merge two tables together default_covers = parentTable.default_covers + childTable.default_covers |} where (eq "table_number" parent + eq "venue_id" (getCurrentVenue())) - } |> db.Update |> ignore + } |> Database.Update |> ignore Entity.DeleteById newChildTable.id |> ignore @@ -251,7 +251,7 @@ let updateUnmergedTables parentTable childTable = table "floorplan_tables" set parentTable where(eq "table_number" parentTable.table_number + eq "venue_id" (getCurrentVenue())) - } |> db.Update |> ignore + } |> Database.Update |> ignore addNewTableWithoutOutput childTable |> ignore true diff --git a/Floorplan/View.fs b/Floorplan/View.fs index 4300f60..7dc3a7d 100644 --- a/Floorplan/View.fs +++ b/Floorplan/View.fs @@ -14,7 +14,7 @@ let ActiveInMode (value: string) = value |> (attr "data-active-in-mode") let pageContainer (clerk: clerk) roomMenu = - let loggedInText = str (language.getAndReplace "logged_in_as" [clerk.clerk_name]) + let loggedInText = str (language.getAndReplace "logged_in_as" [clerk.name]) div [_id "pageContainer"] [ div [_id "floorplanLeftColumn"] [ @@ -77,7 +77,7 @@ let pageContainer (clerk: clerk) roomMenu = ] ] -let roomButton (room: floorplan_room) = a [_class "posButton roomButton"; Value (string room.id)] [str room.room_name ] +let roomButton (room: room) = a [_class "posButton roomButton"; Value (string room.id)] [str room.name ] let index styles scripts tags clerk decoratorRows roomMenu = [| diff --git a/Installer/Controller.fs b/Installer/Controller.fs new file mode 100644 index 0000000..4796343 --- /dev/null +++ b/Installer/Controller.fs @@ -0,0 +1,32 @@ +module DredgePos.Installer.Controller + +open System.Reflection +open DredgePos.Types +open FSharp.Reflection + +let RunMigration (fsModule: System.Type) = + fsModule + .GetMethod("run") + .Invoke(null, [||]) + |> ignore + Entity.Create {name=fsModule.FullName; timestamp=DredgeFramework.CurrentTime(); id=0} |> ignore + fsModule.FullName + " ran Successfully" + +let RunAllMigrations () = + let completedMigrations = + try + Entity.GetAll + with + | _ -> [||] + |> Array.map (fun migration -> migration.name) + + Assembly + .GetExecutingAssembly() + .GetTypes() + |> Array.filter FSharpType.IsModule + |> Array.filter (fun fsModule -> fsModule.Namespace = "DredgePos.Migrations") + |> Array.filter (fun fsModule -> not (completedMigrations |> Array.contains fsModule.FullName)) + |> Array.sortBy (fun fsModule -> fsModule.Name) + |> Array.map RunMigration + |> (fun arr -> if arr.Length > 0 then arr else [|"No Migrations Were Run"|]) + |> String.concat "

" \ No newline at end of file diff --git a/Installer/Model.fs b/Installer/Model.fs new file mode 100644 index 0000000..5b69a65 --- /dev/null +++ b/Installer/Model.fs @@ -0,0 +1,2 @@ +module DredgePos.Installer.Model + diff --git a/Installer/Router.fs b/Installer/Router.fs new file mode 100644 index 0000000..1014b33 --- /dev/null +++ b/Installer/Router.fs @@ -0,0 +1,10 @@ +module DredgePos.Installer.Router + +open DredgePos +open Saturn +open Giraffe + +let router = router { + pipe_through Ajax.Router.pipeline + get "/" (warbler (fun _ -> htmlString (Controller.RunAllMigrations ()))) +} \ No newline at end of file diff --git a/Migrations/CreateDatabaseSchema.fs b/Migrations/CreateDatabaseSchema.fs new file mode 100644 index 0000000..69f2b27 --- /dev/null +++ b/Migrations/CreateDatabaseSchema.fs @@ -0,0 +1,131 @@ +module DredgePos.Migrations.CreateDatabaseSchema + +open DredgePos.Types +open Database +open Dapper.FSharp +open Dapper.FSharp.PostgreSQL + +let CreateDatabase (db: db_config) connectionString = + let connection = connect connectionString + connection + |> NonDbSpecificQuery $""" + CREATE DATABASE {db.db_name}; + """ + +let addTables () = + CreateTable "sessions" [ + "session_id", "varchar(200)" + "clerk_json", "text" + "clerk_id", "int" + "expires", "int" + ] + + CreateTable "grids" [ + "name", "varchar(60)" + "rows", "int" + "cols", "int" + "data", "text" + ] + + CreateTable "reservations" [ + "name", "varchar(100)" + "time", "int" + "covers", "int" + "floorplan_table_id", "int" + "created_at", "int" + ] + + CreateTable "venues" [ + "name", "varchar(60)" + ] + + CreateTable "floorplan_tables" [ + "table_number", "int" + "room_id", "int" + "venue_id", "int" + "pos_x", "int" + "pos_y", "int" + "shape", "varchar(12)" + "width", "int" + "height", "int" + "default_covers", "int" + "rotation", "int" + "merged_children", "text" + "previous_state", "text" + "status", "int" + ] + + CreateTable "print_groups" [ + "name", "varchar(20)" + "printer_id", "int" + "venue_id", "int" + ] + + CreateTable "sales_categories" [ + "parent", "int" + "name", "varchar(20)" + "print_group_id", "int" + "venue_id", "int" + ] + + CreateTable "rooms" [ + "name", "varchar(20)" + "background_image", "varchar(100)" + "venue_id", "int" + ] + + CreateTable "floorplan_decorations" [ + "room_id", "int" + "pos_x", "int" + "pos_y", "int" + "rotation", "int" + "width", "int" + "height", "int" + "image", "varchar(100)" + "venue_id", "int" + ] + + CreateTable "clerks" [ + "name", "varchar(20)" + "login_code", "int" + "user_group_id", "int" + ] + + CreateTable "order_screen_page_groups" [ + "order", "int" + "venue_id", "int" + "label", "varchar(40)" + "grid_id", "int" + ] + + CreateTable "buttons" [ + "text", "varchar(60)" + "primary_action", "varchar(15)" + "primary_action_value", "varchar(20)" + "secondary_action", "varchar(15)" + "secondary_action_value", "varchar(20)" + "image", "varchar(60)" + "extra_classes", "text" + "extra_styles", "text" + ] + + CreateTable "items" [ + "code", "varchar(40)" + "sales_category_id", "int" + "name", "varchar(60)" + "item_type", "varchar(12)" + "price1", "int" + ] + + CreateTable "migrations" [ + "name", "varchar(100)" + "timestamp", "int" + ] + +let run () = + let db = getDatabaseSettings () + + $"Server={db.host};Port={db.port};User Id={db.username};Password={db.password};Include Error Detail=true" + |> CreateDatabase db + |> ignore + |> addTables \ No newline at end of file diff --git a/Migrations/PopulateTestData.fs b/Migrations/PopulateTestData.fs new file mode 100644 index 0000000..7d11b33 --- /dev/null +++ b/Migrations/PopulateTestData.fs @@ -0,0 +1,345 @@ +module DredgePos.Migrations.PopulateTestData + +open DredgeFramework +open DredgePos.Types +open System.IO + +let CreatePageFromDirectory index (dir: string) = + let dirName = DirectoryInfo(dir).Name + + let printGroup = + match dirName.ToLower() with + | "beer" | "wine" -> (Entity.GetFirstByColumn "name" "Beverage").id + | _ -> (Entity.GetFirstByColumn "name" "Food").id + + let parentName = + match dirName.ToLower() with + | "beer" | "wine" -> "Beverage" + | _ -> "Food" + let parentCategory = Entity.GetFirstByColumn "name" parentName + + if dirName.ToLower() <> "dips" && dirName.ToLower() <> "Steak Temperatures" then + let NewGrid = Entity.Create { + id=0 + name=dirName + rows=8 + cols=6 + data="" + } + + Entity.Create { + id=0 + order=index + venue_id=1 + label=dirName + grid_id=NewGrid.id + } |> ignore + else () + + Entity.Create { + id=0 + parent=parentCategory.id + name=dirName + print_group_id=printGroup + venue_id=1 + } |> ignore + + dir + +let CreateDefaultPrintGroups (path: string) = + Entity.Create { + id=0 + name="Food" + printer_id=1 + venue_id=1 + } |> ignore + + Entity.Create { + id=0 + name="Beverage" + printer_id=1 + venue_id=1 + } |> ignore + + + path + +let CreateDefaultVenue (path: string) = + let venue: venue = { + id=0 + name="Megalomania" + } + Entity.Create venue + |>ignore + path + +let CreateDefaultClerk (path: string) = + let venue: clerk = { + id=0 + name="Josh" + login_code=1408 + user_group_id=1 + } + Entity.Create venue + |>ignore + path + +let CreateDefaultSalesCategories (path: string) = + Entity.Create { + id=0 + parent=0 + name="Food" + print_group_id=(Entity.GetFirstByColumn "name" "Food").id + venue_id=1 + } |> ignore + + Entity.Create { + id=0 + parent=0 + name="Beverage" + print_group_id=(Entity.GetFirstByColumn "name" "Beverage").id + venue_id=1 + } |> ignore + + path + +let CreateRooms () = + "wwwroot/images/rooms" + |> Directory.GetFiles + |> Array.filter (fun file -> Path.GetExtension file = ".png" || Path.GetExtension file = ".jpg") + |> Array.iter (fun image -> + let roomName = Path.GetFileNameWithoutExtension image + Entity.Create { + id=0 + name=roomName + background_image= Path.GetFileName image + venue_id=1 + } |> ignore + ) + +let populateEntreeGrid () = + let SalesCategory = Entity.GetFirstByColumn "name" "Entrees" + let DipSalesCategory = Entity.GetFirstByColumn "name" "Dips" + let Entrees = Entity.GetAllByColumn "sales_category_id" SalesCategory.id + let Dips = Entity.GetAllByColumn "sales_category_id" DipSalesCategory.id + let GridData = + [| + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + |] + |> Array.mapi (fun index current -> + let isFirstColumn = (index % 6) = 0 + if not isFirstColumn then current else + let entree = Entrees |> Array.tryItem (index/6) + match entree with + | None -> 0 + | Some x -> x.id + ) + |> Array.mapi (fun index current -> + let isSecondRow = index > 6 && index < 12 + if not isSecondRow then current else + let entree = Dips |> Array.tryItem (index-7) + match entree with + | None -> 0 + | Some x -> x.id + ) + + let grid = + Entity.GetFirstByColumn "label" "Entrees" + |> Entity.GetRelated + + let newGrid = {grid with data=(jsonEncode {|page1=GridData|})} + Entity.Update newGrid |> ignore + + () + +let populateMainGrid (category: string) () = + let SalesCategory = Entity.GetFirstByColumn "name" category + let Mains = Entity.GetAllByColumn "sales_category_id" SalesCategory.id + let getId index = + match Mains |> Array.tryItem index with + | None -> 0 + | Some x -> x.id + let GridData = + [| + getId 0; 0; getId 1; 0; getId 2; 0; + 0; 0; 0; 0; 0; 0; + getId 3; 0; getId 4; 0; getId 5; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + |] + + let grid = + Entity.GetFirstByColumn "label" category + |> Entity.GetRelated + + let newGrid = {grid with data=(jsonEncode {|page1=GridData|})} + Entity.Update newGrid |> ignore + +let populateDessertGrid () = + let SalesCategory = Entity.GetFirstByColumn "name" "Dessert" + let Desserts = Entity.GetAllByColumn "sales_category_id" SalesCategory.id + let getId index = + match Desserts |> Array.tryItem index with + | None -> 0 + | Some x -> x.id + let GridData = + [| + getId 0; 0; getId 1; 0; 0 ; 0; + 0; 0; 0; 0; 0; 0; + 0; getId 2; 0; getId 4; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + |] + + let grid = + Entity.GetFirstByColumn "label" "Dessert" + |> Entity.GetRelated + + let newGrid = {grid with data=(jsonEncode {|page1=GridData|})} + Entity.Update newGrid |> ignore + +let populateBeerGrid () = + let SalesCategory = Entity.GetFirstByColumn "name" "Beer" + let Beers = Entity.GetAllByColumn "sales_category_id" SalesCategory.id + let grid = + Entity.GetFirstByColumn "label" "Beer" + |> Entity.GetRelated + + let GridData = + Beers + |> Array.chunkBySize 24 + |> Array.map (fun beerPage -> + let getId index = + match beerPage |> Array.tryItem index with + | None -> 0 + | Some x -> x.id + [| + getId 0; getId 1; getId 2; getId 3; getId 4 ; getId 5; + 0; 0; 0; 0; 0 ;0; + getId 6; getId 7; getId 8; getId 9; getId 10 ; getId 11; + 0; 0; 0; 0; 0 ;0; + getId 12; getId 13; getId 14; getId 15; getId 16 ; getId 17; + 0; 0; 0; 0; 0 ;0; + getId 18; getId 19; getId 20; getId 21; getId 22 ; getId 23; + 0; 0; 0; 0; 0 ;0; + |] + ) + |> Array.mapi (fun index beerpage -> (map [$"page{index+1}", beerpage])) + |> jsonEncode + + let newGrid = {grid with data=GridData} + Entity.Update newGrid |> ignore + +let populateSteakTemperaturesGrid () = + let SalesCategory = Entity.GetFirstByColumn "name" "Steak Temperatures" + let Temps = Entity.GetAllByColumn "sales_category_id" SalesCategory.id + let grid = + Entity.GetFirstByColumn "label" "Steak Temperatures" + |> Entity.GetRelated + + let getId index = + match Temps |> Array.tryItem index with + | None -> 0 + | Some x -> x.id + let GridData = + [| + getId 0; 0; getId 1; 0; getId 2; 0; + 0; 0; 0; 0; 0; 0; + getId 3; 0; getId 4; 0; getId 5; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + 0; 0; 0; 0; 0; 0; + |] + + let newGrid = {grid with data=(jsonEncode {|page1=GridData|}); rows=4; cols=6} + Entity.Update newGrid |> ignore + + let steakButtons = Entity.GetAllByColumn