V2 #1

Merged
danieljsummers merged 102 commits from v2 into main 2022-06-23 00:35:12 +00:00
6 changed files with 59 additions and 5 deletions
Showing only changes of commit 17178c3290 - Show all commits

View File

@ -737,12 +737,26 @@ module Theme =
/// Functions to manipulate theme assets /// Functions to manipulate theme assets
module ThemeAsset = module ThemeAsset =
open RethinkDb.Driver.Ast
/// Match the ID by its prefix (the theme ID)
let private matchById themeId =
let keyPrefix = $"^{ThemeId.toString themeId}/"
fun (row : ReqlExpr) -> row["id"].Match keyPrefix :> obj
/// List all theme assets (excludes data)
let all =
rethink<ThemeAsset list> {
withTable Table.ThemeAsset
without [ "data" ]
result; withRetryDefault
}
/// Delete all assets for a theme /// Delete all assets for a theme
let deleteByTheme themeId = let deleteByTheme themeId =
let keyPrefix = $"^{ThemeId.toString themeId}/"
rethink { rethink {
withTable Table.ThemeAsset withTable Table.ThemeAsset
filter (fun row -> row["id"].Match keyPrefix :> obj) filter (matchById themeId)
delete delete
write; withRetryDefault; ignoreResult write; withRetryDefault; ignoreResult
} }
@ -755,7 +769,16 @@ module ThemeAsset =
resultOption; withRetryOptionDefault resultOption; withRetryOptionDefault
} }
/// Save a theme assed /// List all assets for a theme (data excluded)
let findByThemeId (themeId : ThemeId) =
rethink<ThemeAsset list> {
withTable Table.ThemeAsset
filter (matchById themeId)
without [ "data" ]
result; withRetryDefault
}
/// Save a theme asset
let save (asset : ThemeAsset) = let save (asset : ThemeAsset) =
rethink { rethink {
withTable Table.ThemeAsset withTable Table.ThemeAsset

View File

@ -131,3 +131,27 @@ module TemplateCache =
|> List.ofSeq |> List.ofSeq
|> List.iter (fun key -> match _cache.TryRemove key with _, _ -> ()) |> List.iter (fun key -> match _cache.TryRemove key with _, _ -> ())
/// A cache of asset names by themes
module ThemeAssetCache =
/// A list of asset names for each theme
let private _cache = ConcurrentDictionary<ThemeId, string list> ()
/// Retrieve the assets for the given theme ID
let get themeId = _cache[themeId]
/// Refresh the list of assets for the given theme
let refreshTheme themeId conn = backgroundTask {
let! assets = Data.ThemeAsset.findByThemeId themeId conn
_cache[themeId] <- assets |> List.map (fun a -> match a.id with ThemeAssetId (_, path) -> path)
}
/// Fill the theme asset cache
let fill conn = backgroundTask {
let! assets = Data.ThemeAsset.all conn
for asset in assets do
let (ThemeAssetId (themeId, path)) = asset.id
if not (_cache.ContainsKey themeId) then _cache[themeId] <- []
_cache[themeId] <- path :: _cache[themeId]
}

View File

@ -14,7 +14,7 @@ let webLog (ctx : Context) =
/// Does an asset exist for the current theme? /// Does an asset exist for the current theme?
let assetExists fileName (webLog : WebLog) = let assetExists fileName (webLog : WebLog) =
File.Exists (Path.Combine ("wwwroot", "themes", webLog.themePath, fileName)) ThemeAssetCache.get (ThemeId webLog.themePath) |> List.exists (fun it -> it = fileName)
/// Obtain the link from known types /// Obtain the link from known types
let permalink (ctx : Context) (item : obj) (linkFunc : WebLog -> Permalink -> string) = let permalink (ctx : Context) (item : obj) (linkFunc : WebLog -> Permalink -> string) =

View File

@ -444,9 +444,11 @@ let updateTheme : HttpHandler = fun next ctx -> task {
let themeName = themeFile.FileName.Split(".").[0].ToLowerInvariant().Replace (" ", "-") let themeName = themeFile.FileName.Split(".").[0].ToLowerInvariant().Replace (" ", "-")
// TODO: add restriction for admin theme based on role // TODO: add restriction for admin theme based on role
if Regex.IsMatch (themeName, """^[a-z0-9\-]+$""") then if Regex.IsMatch (themeName, """^[a-z0-9\-]+$""") then
let conn = ctx.Conn
use stream = new MemoryStream () use stream = new MemoryStream ()
do! themeFile.CopyToAsync stream do! themeFile.CopyToAsync stream
do! loadThemeFromZip themeName stream true ctx.Conn do! loadThemeFromZip themeName stream true conn
do! ThemeAssetCache.refreshTheme (ThemeId themeName) conn
do! addMessage ctx { UserMessage.success with message = "Theme updated successfully" } do! addMessage ctx { UserMessage.success with message = "Theme updated successfully" }
return! redirectToGet (WebLog.relativeUrl ctx.WebLog (Permalink "admin/dashboard")) next ctx return! redirectToGet (WebLog.relativeUrl ctx.WebLog (Permalink "admin/dashboard")) next ctx
else else

View File

@ -126,6 +126,10 @@ module Asset =
let headers = ResponseHeaders ctx.Response.Headers let headers = ResponseHeaders ctx.Response.Headers
headers.LastModified <- Some (DateTimeOffset asset.updatedOn) |> Option.toNullable headers.LastModified <- Some (DateTimeOffset asset.updatedOn) |> Option.toNullable
headers.ContentType <- MediaTypeHeaderValue mimeType headers.ContentType <- MediaTypeHeaderValue mimeType
headers.CacheControl <-
let hdr = CacheControlHeaderValue()
hdr.MaxAge <- Some (TimeSpan.FromDays 30) |> Option.toNullable
hdr
return! setBody asset.data next ctx return! setBody asset.data next ctx
| None -> return! Error.notFound next ctx | None -> return! Error.notFound next ctx
} }

View File

@ -63,6 +63,7 @@ let main args =
let! conn = rethinkCfg.CreateConnectionAsync () let! conn = rethinkCfg.CreateConnectionAsync ()
do! Data.Startup.ensureDb rethinkCfg (loggerFac.CreateLogger (nameof Data.Startup)) conn do! Data.Startup.ensureDb rethinkCfg (loggerFac.CreateLogger (nameof Data.Startup)) conn
do! WebLogCache.fill conn do! WebLogCache.fill conn
do! ThemeAssetCache.fill conn
return conn return conn
} |> Async.AwaitTask |> Async.RunSynchronously } |> Async.AwaitTask |> Async.RunSynchronously
let _ = builder.Services.AddSingleton<IConnection> conn let _ = builder.Services.AddSingleton<IConnection> conn