namespace MyWebLog open Microsoft.AspNetCore.Http open MyWebLog.Data /// Extension properties on HTTP context for web log [] module Extensions = open System.Security.Claims open Microsoft.AspNetCore.Antiforgery open Microsoft.Extensions.Configuration open Microsoft.Extensions.DependencyInjection /// Hold variable for the configured generator string let mutable private generatorString: string option = None type HttpContext with /// The anti-CSRF service member this.AntiForgery = this.RequestServices.GetRequiredService() /// The cross-site request forgery token set for this request member this.CsrfTokenSet = this.AntiForgery.GetAndStoreTokens this /// The data implementation member this.Data = this.RequestServices.GetRequiredService() /// The generator string member this.Generator = match generatorString with | Some gen -> gen | None -> let cfg = this.RequestServices.GetRequiredService() generatorString <- match Option.ofObj cfg["Generator"] with | Some gen -> Some gen | None -> Some "generator not configured" generatorString.Value /// The access level for the current user member this.UserAccessLevel = this.User.Claims |> Seq.tryFind (fun claim -> claim.Type = ClaimTypes.Role) |> Option.map (fun claim -> AccessLevel.Parse claim.Value) /// The user ID for the current request member this.UserId = WebLogUserId (this.User.Claims |> Seq.find (fun c -> c.Type = ClaimTypes.NameIdentifier)).Value /// The web log for the current request member this.WebLog = this.Items["webLog"] :?> WebLog /// Does the current user have the required level of access? /// The required level of access /// True if the user has the required access, false if not member this.HasAccessLevel level = defaultArg (this.UserAccessLevel |> Option.map _.HasAccess(level)) false open System.Collections.Concurrent /// /// In-memory cache of web log details /// /// This is filled by the middleware via the first request for each host, and can be updated via the web log /// settings update page module WebLogCache = open System.Text.RegularExpressions /// A redirect rule that caches compiled regular expression rules type CachedRedirectRule = /// A straight text match rule | Text of string * string /// A regular expression match rule | RegEx of Regex * string /// The cache of web log details let mutable private _cache: WebLog list = [] /// Redirect rules with compiled regular expressions let mutable private _redirectCache = ConcurrentDictionary() /// Try to get the web log for the current request (longest matching URL base wins) /// The path for the current request /// Some with the web log matching the URL, or None if none is found let tryGet (path: string) = _cache |> List.filter (fun wl -> path.StartsWith wl.UrlBase) |> List.sortByDescending _.UrlBase.Length |> List.tryHead /// Cache the web log for a particular host /// The web log to be cached let set webLog = _cache <- webLog :: (_cache |> List.filter (fun wl -> wl.Id <> webLog.Id)) _redirectCache[webLog.Id] <- webLog.RedirectRules |> List.map (fun it -> let relUrl = Permalink >> webLog.RelativeUrl let urlTo = if it.To.Contains "://" then it.To else relUrl it.To if it.IsRegex then RegEx(Regex(it.From, RegexOptions.Compiled ||| RegexOptions.IgnoreCase), urlTo) else Text(it.From, urlTo)) /// Get all cached web logs /// All cached web logs let all () = _cache /// Fill the web log cache from the database /// The data implementation from which web logs will be retrieved let fill (data: IData) = backgroundTask { let! webLogs = data.WebLog.All() webLogs |> List.iter set } /// Get the cached redirect rules for the given web log /// The ID of the web log for which rules should be retrieved /// The redirect rules for the given web log ID let redirectRules webLogId = _redirectCache[webLogId] /// Is the given theme in use by any web logs? /// The ID of the theme whose use should be checked /// True if any web logs are using the given theme, false if not let isThemeInUse themeId = _cache |> List.exists (fun wl -> wl.ThemeId = themeId) /// A cache of page information needed to display the page list in templates module PageListCache = open MyWebLog.ViewModels /// Cache of displayed pages let private _cache = ConcurrentDictionary() /// Fill the page list for the given web log let private fillPages (webLog: WebLog) pages = _cache[webLog.Id] <- pages |> List.map (fun pg -> DisplayPage.FromPage webLog { pg with Text = "" }) |> Array.ofList /// Are there pages cached for this web log? /// The HttpContext for the current request /// True if the current web log has any pages cached, false if not let exists (ctx: HttpContext) = _cache.ContainsKey ctx.WebLog.Id /// Get the pages for the web log for this request /// The HttpContext for the current request /// The page list for the current web log let get (ctx: HttpContext) = _cache[ctx.WebLog.Id] /// Refresh the pages for the given web log /// The web log for which pages should be refreshed /// The data implementation from which pages should be retrieved let refresh (webLog: WebLog) (data: IData) = backgroundTask { let! pages = data.Page.FindListed webLog.Id fillPages webLog pages } /// Update the pages for the current web log /// The HttpContext for the current request let update (ctx: HttpContext) = refresh ctx.WebLog ctx.Data /// Cache of all categories, indexed by web log module CategoryCache = open MyWebLog.ViewModels /// The cache itself let private _cache = ConcurrentDictionary() /// Are there categories cached for this web log? /// The HttpContext for the current request /// True if the current web logs has any categories cached, false if not let exists (ctx: HttpContext) = _cache.ContainsKey ctx.WebLog.Id /// Get the categories for the web log for this request /// The HttpContext for the current request /// The categories for the current web log let get (ctx: HttpContext) = _cache[ctx.WebLog.Id] /// Refresh the category cache for the given web log /// The ID of the web log for which the cache should be refreshed /// The data implementation from which categories should be retrieved let refresh webLogId (data: IData) = backgroundTask { let! cats = data.Category.FindAllForView webLogId _cache[webLogId] <- cats } /// Update the cache with fresh data for the current web log /// The HttpContext for the current request let update (ctx: HttpContext) = refresh ctx.WebLog.Id ctx.Data /// A cache of asset names by themes module ThemeAssetCache = /// A list of asset names for each theme let private _cache = ConcurrentDictionary() /// Retrieve the assets for the given theme ID /// The ID of the theme whose assets should be returned /// The assets for the given theme let get themeId = _cache[themeId] /// Refresh the list of assets for the given theme /// The ID of the theme whose assets should be refreshed /// The data implementation from which assets should be retrieved let refreshTheme themeId (data: IData) = backgroundTask { let! assets = data.ThemeAsset.FindByTheme themeId _cache[themeId] <- assets |> List.map (fun a -> match a.Id with ThemeAssetId (_, path) -> path) } /// Fill the theme asset cache /// The data implementation from which assets should be retrieved let fill (data: IData) = backgroundTask { let! assets = data.ThemeAsset.All() for asset in assets do let (ThemeAssetId (themeId, path)) = asset.Id if not (_cache.ContainsKey themeId) then _cache[themeId] <- [] _cache[themeId] <- path :: _cache[themeId] }