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" [ // Domain types typeof; typeof // View models typeof; typeof; typeof; typeof typeof; typeof; typeof; typeof typeof; typeof // Framework types typeof; typeof; typeof ] |> List.iter (fun it -> Template.RegisterSafeType (it, [| "*" |])) 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