V2 #1
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
|
@ -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) =
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user