open System.Collections.Generic open Microsoft.AspNetCore.Http open Microsoft.Extensions.DependencyInjection open MyWebLog open RethinkDb.Driver.Net open System /// Middleware to derive the current web log type WebLogMiddleware (next : RequestDelegate) = member this.InvokeAsync (ctx : HttpContext) = task { match WebLogCache.exists ctx with | true -> return! next.Invoke ctx | false -> let conn = ctx.RequestServices.GetRequiredService () match! Data.WebLog.findByHost (Cache.makeKey ctx) conn with | Some webLog -> WebLogCache.set ctx webLog do! PageListCache.update ctx return! next.Invoke ctx | None -> ctx.Response.StatusCode <- 404 } /// DotLiquid filters module DotLiquidBespoke = open DotLiquid open System.IO /// A filter to generate nav links, highlighting the active link (exact match) type NavLinkFilter () = static member NavLink (ctx : Context, url : string, text : string) = seq { "
  • " text "
  • " } |> Seq.fold (+) "" /// Create links for a user to log on or off, and a dashboard link if they are logged off type UserLinksTag () = inherit Tag () override this.Render (context : Context, result : TextWriter) = seq { """" } |> Seq.iter result.WriteLine /// Create the default information for a new web log module NewWebLog = /// Create the web log information let private createWebLog (args : string[]) (sp : IServiceProvider) = task { let conn = sp.GetRequiredService () let timeZone = let local = TimeZoneInfo.Local.Id match TimeZoneInfo.Local.HasIanaId with | true -> local | false -> match TimeZoneInfo.TryConvertWindowsIdToIanaId local with | true, ianaId -> ianaId | false, _ -> raise <| TimeZoneNotFoundException $"Cannot find IANA timezone for {local}" // Create the web log let webLogId = WebLogId.create () let userId = WebLogUserId.create () let homePageId = PageId.create () do! Data.WebLog.add { WebLog.empty with id = webLogId name = args[2] urlBase = args[1] defaultPage = PageId.toString homePageId timeZone = timeZone } conn // Create the admin user let salt = Guid.NewGuid () do! Data.WebLogUser.add { WebLogUser.empty with id = userId webLogId = webLogId userName = args[3] firstName = "Admin" lastName = "User" preferredName = "Admin" passwordHash = Handlers.User.hashedPassword args[4] args[3] salt salt = salt authorizationLevel = Administrator } conn // Create the default home page do! Data.Page.add { Page.empty with id = homePageId webLogId = webLogId authorId = userId title = "Welcome to myWebLog!" permalink = Permalink "welcome-to-myweblog.html" publishedOn = DateTime.UtcNow updatedOn = DateTime.UtcNow text = "

    This is your default home page.

    " revisions = [ { asOf = DateTime.UtcNow text = Html "

    This is your default home page.

    " } ] } conn Console.WriteLine($"Successfully initialized database for {args[2]} with URL base {args[1]}"); } /// Create a new web log let create args sp = task { match args |> Array.length with | 5 -> return! createWebLog args sp | _ -> Console.WriteLine "Usage: MyWebLog init [url] [name] [admin-email] [admin-pw]" return! System.Threading.Tasks.Task.CompletedTask } open DotLiquid open Giraffe open Giraffe.EndpointRouting open Microsoft.AspNetCore.Antiforgery open Microsoft.AspNetCore.Authentication.Cookies open Microsoft.AspNetCore.Builder open Microsoft.Extensions.Configuration open Microsoft.Extensions.Logging open MyWebLog.ViewModels open RethinkDB.DistributedCache open RethinkDb.Driver.FSharp [] let main args = let builder = WebApplication.CreateBuilder(args) let _ = builder.Services .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(fun opts -> opts.ExpireTimeSpan <- TimeSpan.FromMinutes 20. opts.SlidingExpiration <- true opts.AccessDeniedPath <- "/forbidden") let _ = builder.Services.AddLogging () let _ = builder.Services.AddAuthorization () let _ = builder.Services.AddAntiforgery () // Configure RethinkDB's connection JsonConverters.all () |> Seq.iter Converter.Serializer.Converters.Add let sp = builder.Services.BuildServiceProvider () let config = sp.GetRequiredService () let loggerFac = sp.GetRequiredService () let rethinkCfg = DataConfig.FromConfiguration (config.GetSection "RethinkDB") let conn = task { let! conn = rethinkCfg.CreateConnectionAsync () do! Data.Startup.ensureDb rethinkCfg (loggerFac.CreateLogger (nameof Data.Startup)) conn return conn } |> Async.AwaitTask |> Async.RunSynchronously let _ = builder.Services.AddSingleton conn let _ = builder.Services.AddDistributedRethinkDBCache (fun opts -> opts.TableName <- "Session" opts.Connection <- conn) let _ = builder.Services.AddSession(fun opts -> opts.IdleTimeout <- TimeSpan.FromMinutes 30 opts.Cookie.HttpOnly <- true opts.Cookie.IsEssential <- true) // this needs to be after the session... maybe? let _ = builder.Services.AddGiraffe () // Set up DotLiquid Template.RegisterFilter typeof Template.RegisterTag "user_links" let all = [| "*" |] Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) Template.RegisterSafeType (typeof, all) let app = builder.Build () match args |> Array.tryHead with | Some it when it = "init" -> NewWebLog.create args app.Services |> Async.AwaitTask |> Async.RunSynchronously | _ -> let _ = app.UseCookiePolicy (CookiePolicyOptions (MinimumSameSitePolicy = SameSiteMode.Strict)) let _ = app.UseMiddleware () let _ = app.UseAuthentication () let _ = app.UseStaticFiles () let _ = app.UseRouting () let _ = app.UseSession () let _ = app.UseGiraffe Handlers.endpoints app.Run() 0 // Exit code