V2 #1
|
@ -737,12 +737,26 @@ module Theme =
|
|||
/// Functions to manipulate theme assets
|
||||
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
|
||||
let deleteByTheme themeId =
|
||||
let keyPrefix = $"^{ThemeId.toString themeId}/"
|
||||
rethink {
|
||||
withTable Table.ThemeAsset
|
||||
filter (fun row -> row["id"].Match keyPrefix :> obj)
|
||||
filter (matchById themeId)
|
||||
delete
|
||||
write; withRetryDefault; ignoreResult
|
||||
}
|
||||
|
@ -755,7 +769,16 @@ module ThemeAsset =
|
|||
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) =
|
||||
rethink {
|
||||
withTable Table.ThemeAsset
|
||||
|
|
|
@ -131,3 +131,27 @@ module TemplateCache =
|
|||
|> List.ofSeq
|
||||
|> 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]
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ let webLog (ctx : Context) =
|
|||
|
||||
/// Does an asset exist for the current theme?
|
||||
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
|
||||
let permalink (ctx : Context) (item : obj) (linkFunc : WebLog -> Permalink -> string) =
|
||||
|
|
|
@ -444,9 +444,11 @@ let updateTheme : HttpHandler = fun next ctx -> task {
|
|||
let themeName = themeFile.FileName.Split(".").[0].ToLowerInvariant().Replace (" ", "-")
|
||||
// TODO: add restriction for admin theme based on role
|
||||
if Regex.IsMatch (themeName, """^[a-z0-9\-]+$""") then
|
||||
let conn = ctx.Conn
|
||||
use stream = new MemoryStream ()
|
||||
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" }
|
||||
return! redirectToGet (WebLog.relativeUrl ctx.WebLog (Permalink "admin/dashboard")) next ctx
|
||||
else
|
||||
|
|
|
@ -126,6 +126,10 @@ module Asset =
|
|||
let headers = ResponseHeaders ctx.Response.Headers
|
||||
headers.LastModified <- Some (DateTimeOffset asset.updatedOn) |> Option.toNullable
|
||||
headers.ContentType <- MediaTypeHeaderValue mimeType
|
||||
headers.CacheControl <-
|
||||
let hdr = CacheControlHeaderValue()
|
||||
hdr.MaxAge <- Some (TimeSpan.FromDays 30) |> Option.toNullable
|
||||
hdr
|
||||
return! setBody asset.data next ctx
|
||||
| None -> return! Error.notFound next ctx
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ let main args =
|
|||
let! conn = rethinkCfg.CreateConnectionAsync ()
|
||||
do! Data.Startup.ensureDb rethinkCfg (loggerFac.CreateLogger (nameof Data.Startup)) conn
|
||||
do! WebLogCache.fill conn
|
||||
do! ThemeAssetCache.fill conn
|
||||
return conn
|
||||
} |> Async.AwaitTask |> Async.RunSynchronously
|
||||
let _ = builder.Services.AddSingleton<IConnection> conn
|
||||
|
|
Loading…
Reference in New Issue
Block a user