Update to .NET 8/9; minor tweaks
This commit is contained in:
parent
870f87cb17
commit
88841fd3f8
10
build.fs
10
build.fs
@ -23,7 +23,7 @@ let version =
|
||||
let appVersion = generator.Replace("\"Generator\": \"", "")
|
||||
let appVersion = appVersion.Substring (0, appVersion.IndexOf "\"")
|
||||
appVersion.Split ' ' |> Array.last
|
||||
|
||||
|
||||
/// Zip a theme distributed with myWebLog
|
||||
let zipTheme (name : string) (_ : TargetParameter) =
|
||||
let path = $"src/{name}-theme"
|
||||
@ -33,9 +33,9 @@ let zipTheme (name : string) (_ : TargetParameter) =
|
||||
|> Zip.zipSpec $"{releasePath}/{name}-theme.zip"
|
||||
|
||||
/// Frameworks supported by this build
|
||||
let frameworks = [ "net6.0"; "net8.0" ]
|
||||
let frameworks = [ "net8.0"; "net9.0" ]
|
||||
|
||||
/// Publish the project for the given runtime ID
|
||||
/// Publish the project for the given runtime ID
|
||||
let publishFor rid (_ : TargetParameter) =
|
||||
frameworks
|
||||
|> List.iter (fun fwk ->
|
||||
@ -65,7 +65,7 @@ let packageFor rid (_ : TargetParameter) =
|
||||
Target.create "Clean" (fun _ ->
|
||||
!! "src/**/bin"
|
||||
++ "src/**/obj"
|
||||
|> Shell.cleanDirs
|
||||
|> Shell.cleanDirs
|
||||
Shell.cleanDir releasePath
|
||||
)
|
||||
|
||||
@ -87,7 +87,7 @@ Target.create "RepackageLinux" (fun _ ->
|
||||
frameworks
|
||||
|> List.iter (fun fwk ->
|
||||
let zipArchive = $"{releasePath}/myWebLog-{version}.{fwk}.linux-x64.zip"
|
||||
let sh command args =
|
||||
let sh command args =
|
||||
CreateProcess.fromRawCommand command args
|
||||
|> CreateProcess.redirectOutput
|
||||
|> Proc.run
|
||||
|
@ -1,9 +1,10 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
<DebugType>embedded</DebugType>
|
||||
<AssemblyVersion>2.2.0.0</AssemblyVersion>
|
||||
<FileVersion>2.2.0.0</FileVersion>
|
||||
<Version>2.2.0</Version>
|
||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
||||
<FileVersion>3.0.0.0</FileVersion>
|
||||
<Version>3.0.0</Version>
|
||||
<VersionSuffix>beta1</VersionSuffix>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
@ -5,17 +5,17 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BitBadger.Documents.Postgres" Version="4.0.0-rc5" />
|
||||
<PackageReference Include="BitBadger.Documents.Sqlite" Version="4.0.0-rc5" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="BitBadger.Documents.Postgres" Version="4.0.0" />
|
||||
<PackageReference Include="BitBadger.Documents.Sqlite" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
|
||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.1.0" />
|
||||
<PackageReference Include="Npgsql.NodaTime" Version="8.0.4" />
|
||||
<PackageReference Include="Npgsql.NodaTime" Version="9.0.2" />
|
||||
<PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
|
||||
<PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-07" />
|
||||
<PackageReference Update="FSharp.Core" Version="8.0.400" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -5,55 +5,55 @@ module MyWebLog.Data.SQLite.SQLiteHelpers
|
||||
/// The table names used in the SQLite implementation
|
||||
[<RequireQualifiedAccess>]
|
||||
module Table =
|
||||
|
||||
|
||||
/// Categories
|
||||
[<Literal>]
|
||||
let Category = "category"
|
||||
|
||||
|
||||
/// Database Version
|
||||
[<Literal>]
|
||||
let DbVersion = "db_version"
|
||||
|
||||
|
||||
/// Pages
|
||||
[<Literal>]
|
||||
let Page = "page"
|
||||
|
||||
|
||||
/// Page Revisions
|
||||
[<Literal>]
|
||||
let PageRevision = "page_revision"
|
||||
|
||||
|
||||
/// Posts
|
||||
[<Literal>]
|
||||
let Post = "post"
|
||||
|
||||
|
||||
/// Post Comments
|
||||
[<Literal>]
|
||||
let PostComment = "post_comment"
|
||||
|
||||
|
||||
/// Post Revisions
|
||||
[<Literal>]
|
||||
let PostRevision = "post_revision"
|
||||
|
||||
|
||||
/// Tag/URL Mappings
|
||||
[<Literal>]
|
||||
let TagMap = "tag_map"
|
||||
|
||||
|
||||
/// Themes
|
||||
[<Literal>]
|
||||
let Theme = "theme"
|
||||
|
||||
|
||||
/// Theme Assets
|
||||
[<Literal>]
|
||||
let ThemeAsset = "theme_asset"
|
||||
|
||||
|
||||
/// Uploads
|
||||
[<Literal>]
|
||||
let Upload = "upload"
|
||||
|
||||
|
||||
/// Web Logs
|
||||
[<Literal>]
|
||||
let WebLog = "web_log"
|
||||
|
||||
|
||||
/// Users
|
||||
[<Literal>]
|
||||
let WebLogUser = "web_log_user"
|
||||
@ -85,75 +85,75 @@ let maybeInstant =
|
||||
|
||||
/// Functions to map domain items from a data reader
|
||||
module Map =
|
||||
|
||||
|
||||
open System.IO
|
||||
|
||||
|
||||
/// Get a boolean value from a data reader
|
||||
let getBoolean col (rdr: SqliteDataReader) = rdr.GetBoolean(rdr.GetOrdinal col)
|
||||
|
||||
|
||||
/// Get a date/time value from a data reader
|
||||
let getDateTime col (rdr: SqliteDataReader) = rdr.GetDateTime(rdr.GetOrdinal col)
|
||||
|
||||
|
||||
/// Get a Guid value from a data reader
|
||||
let getGuid col (rdr: SqliteDataReader) = rdr.GetGuid(rdr.GetOrdinal col)
|
||||
|
||||
|
||||
/// Get an int value from a data reader
|
||||
let getInt col (rdr: SqliteDataReader) = rdr.GetInt32(rdr.GetOrdinal col)
|
||||
|
||||
|
||||
/// Get a long (64-bit int) value from a data reader
|
||||
let getLong col (rdr: SqliteDataReader) = rdr.GetInt64(rdr.GetOrdinal col)
|
||||
|
||||
|
||||
/// Get a BLOB stream value from a data reader
|
||||
let getStream col (rdr: SqliteDataReader) = rdr.GetStream(rdr.GetOrdinal col)
|
||||
|
||||
|
||||
/// Get a string value from a data reader
|
||||
let getString col (rdr: SqliteDataReader) = rdr.GetString(rdr.GetOrdinal col)
|
||||
|
||||
|
||||
/// Parse an Instant from the given value
|
||||
let parseInstant value =
|
||||
match InstantPattern.General.Parse value with
|
||||
| it when it.Success -> it.Value
|
||||
| it -> raise it.Exception
|
||||
|
||||
|
||||
/// Get an Instant value from a data reader
|
||||
let getInstant col rdr =
|
||||
getString col rdr |> parseInstant
|
||||
|
||||
|
||||
/// Get a timespan value from a data reader
|
||||
let getTimeSpan col (rdr: SqliteDataReader) = rdr.GetTimeSpan(rdr.GetOrdinal col)
|
||||
|
||||
|
||||
/// Get a possibly null boolean value from a data reader
|
||||
let tryBoolean col (rdr: SqliteDataReader) =
|
||||
if rdr.IsDBNull(rdr.GetOrdinal col) then None else Some (getBoolean col rdr)
|
||||
|
||||
|
||||
/// Get a possibly null date/time value from a data reader
|
||||
let tryDateTime col (rdr: SqliteDataReader) =
|
||||
if rdr.IsDBNull(rdr.GetOrdinal col) then None else Some (getDateTime col rdr)
|
||||
|
||||
|
||||
/// Get a possibly null Guid value from a data reader
|
||||
let tryGuid col (rdr: SqliteDataReader) =
|
||||
if rdr.IsDBNull(rdr.GetOrdinal col) then None else Some (getGuid col rdr)
|
||||
|
||||
|
||||
/// Get a possibly null int value from a data reader
|
||||
let tryInt col (rdr: SqliteDataReader) =
|
||||
if rdr.IsDBNull(rdr.GetOrdinal col) then None else Some (getInt col rdr)
|
||||
|
||||
|
||||
/// Get a possibly null string value from a data reader
|
||||
let tryString col (rdr: SqliteDataReader) =
|
||||
if rdr.IsDBNull(rdr.GetOrdinal col) then None else Some (getString col rdr)
|
||||
|
||||
|
||||
/// Get a possibly null timespan value from a data reader
|
||||
let tryTimeSpan col (rdr: SqliteDataReader) =
|
||||
if rdr.IsDBNull(rdr.GetOrdinal col) then None else Some (getTimeSpan col rdr)
|
||||
|
||||
|
||||
/// Create a permalink from the current row in the given data reader
|
||||
let toPermalink rdr = getString "permalink" rdr |> Permalink
|
||||
|
||||
|
||||
/// Create a revision from the current row in the given data reader
|
||||
let toRevision rdr : Revision =
|
||||
{ AsOf = getInstant "as_of" rdr
|
||||
Text = getString "revision_text" rdr |> MarkupText.Parse }
|
||||
|
||||
|
||||
/// Create a theme asset from the current row in the given data reader
|
||||
let toThemeAsset includeData rdr : ThemeAsset =
|
||||
let assetData =
|
||||
@ -167,7 +167,7 @@ module Map =
|
||||
{ Id = ThemeAssetId (ThemeId (getString "theme_id" rdr), getString "path" rdr)
|
||||
UpdatedOn = getInstant "updated_on" rdr
|
||||
Data = assetData }
|
||||
|
||||
|
||||
/// Create an uploaded file from the current row in the given data reader
|
||||
let toUpload includeData rdr : Upload =
|
||||
let data =
|
||||
@ -190,7 +190,7 @@ open BitBadger.Documents
|
||||
/// Create a named parameter
|
||||
let sqlParam name (value: obj) =
|
||||
SqliteParameter(name, value)
|
||||
|
||||
|
||||
/// Create a web log ID parameter
|
||||
let webLogParam (webLogId: WebLogId) =
|
||||
sqlParam "@webLogId" (string webLogId)
|
||||
@ -205,22 +205,20 @@ let webLogField (webLogId: WebLogId) =
|
||||
|
||||
|
||||
open BitBadger.Documents.Sqlite
|
||||
open BitBadger.Documents.Sqlite.WithConn
|
||||
|
||||
/// Functions to support revisions
|
||||
module Revisions =
|
||||
|
||||
|
||||
/// Find all revisions for the given entity
|
||||
let findByEntityId<'TKey> revTable entityTable (key: 'TKey) conn =
|
||||
Custom.list
|
||||
let findByEntityId<'TKey> revTable entityTable (key: 'TKey) (conn: SqliteConnection) =
|
||||
conn.customList
|
||||
$"SELECT as_of, revision_text FROM %s{revTable} WHERE %s{entityTable}_id = @id ORDER BY as_of DESC"
|
||||
[ idParam key ]
|
||||
Map.toRevision
|
||||
conn
|
||||
|
||||
|
||||
/// Find all revisions for all posts for the given web log
|
||||
let findByWebLog<'TKey> revTable entityTable (keyFunc: string -> 'TKey) webLogId conn =
|
||||
Custom.list
|
||||
let findByWebLog<'TKey> revTable entityTable (keyFunc: string -> 'TKey) webLogId (conn: SqliteConnection) =
|
||||
conn.customList
|
||||
$"SELECT pr.*
|
||||
FROM %s{revTable} pr
|
||||
INNER JOIN %s{entityTable} p ON p.data->>'Id' = pr.{entityTable}_id
|
||||
@ -228,19 +226,16 @@ module Revisions =
|
||||
ORDER BY as_of DESC"
|
||||
[ webLogParam webLogId ]
|
||||
(fun rdr -> keyFunc (Map.getString $"{entityTable}_id" rdr), Map.toRevision rdr)
|
||||
conn
|
||||
|
||||
/// Update a page or post's revisions
|
||||
let update<'TKey> revTable entityTable (key: 'TKey) oldRevs newRevs conn = backgroundTask {
|
||||
let update<'TKey> revTable entityTable (key: 'TKey) oldRevs newRevs (conn: SqliteConnection) = backgroundTask {
|
||||
let toDelete, toAdd = Utils.diffRevisions oldRevs newRevs
|
||||
for delRev in toDelete do
|
||||
do! Custom.nonQuery
|
||||
do! conn.customNonQuery
|
||||
$"DELETE FROM %s{revTable} WHERE %s{entityTable}_id = @id AND as_of = @asOf"
|
||||
[ idParam key; sqlParam "@asOf" (instantParam delRev.AsOf) ]
|
||||
conn
|
||||
for addRev in toAdd do
|
||||
do! Custom.nonQuery
|
||||
do! conn.customNonQuery
|
||||
$"INSERT INTO {revTable} VALUES (@id, @asOf, @text)"
|
||||
[ idParam key; sqlParam "asOf" (instantParam addRev.AsOf); sqlParam "@text" (string addRev.Text) ]
|
||||
conn
|
||||
}
|
||||
|
@ -7,11 +7,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||
<PackageReference Include="Markdown.ColorCode" Version="2.2.2" />
|
||||
<PackageReference Include="Markdig" Version="0.39.1" />
|
||||
<PackageReference Include="Markdown.ColorCode" Version="2.3.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NodaTime" Version="3.1.12" />
|
||||
<PackageReference Update="FSharp.Core" Version="8.0.400" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -28,7 +28,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||
<PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="8.0.400" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -6,26 +6,26 @@ open MyWebLog.Data
|
||||
/// Extension properties on HTTP context for web log
|
||||
[<AutoOpen>]
|
||||
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<IAntiforgery>()
|
||||
|
||||
|
||||
/// 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<IData>()
|
||||
|
||||
|
||||
/// The generator string
|
||||
member this.Generator =
|
||||
match generatorString with
|
||||
@ -50,7 +50,7 @@ module Extensions =
|
||||
|
||||
/// The web log for the current request
|
||||
member this.WebLog = this.Items["webLog"] :?> WebLog
|
||||
|
||||
|
||||
/// Does the current user have the requested level of access?
|
||||
member this.HasAccessLevel level =
|
||||
defaultArg (this.UserAccessLevel |> Option.map _.HasAccess(level)) false
|
||||
@ -64,21 +64,21 @@ open System.Collections.Concurrent
|
||||
/// <remarks>This is filled by the middleware via the first request for each host, and can be updated via the web log
|
||||
/// settings update page</remarks>
|
||||
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
|
||||
/// 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 = []
|
||||
let mutable private _cache: WebLog list = []
|
||||
|
||||
/// Redirect rules with compiled regular expressions
|
||||
let mutable private _redirectCache = ConcurrentDictionary<WebLogId, CachedRedirectRule list> ()
|
||||
let mutable private _redirectCache = ConcurrentDictionary<WebLogId, CachedRedirectRule list>()
|
||||
|
||||
/// Try to get the web log for the current request (longest matching URL base wins)
|
||||
let tryGet (path : string) =
|
||||
@ -100,21 +100,21 @@ module WebLogCache =
|
||||
RegEx(Regex(pattern, RegexOptions.Compiled ||| RegexOptions.IgnoreCase), urlTo)
|
||||
else
|
||||
Text(relUrl it.From, urlTo))
|
||||
|
||||
|
||||
/// Get all cached web logs
|
||||
let all () =
|
||||
_cache
|
||||
|
||||
|
||||
/// Fill the web log cache from the database
|
||||
let fill (data: IData) = backgroundTask {
|
||||
let! webLogs = data.WebLog.All()
|
||||
webLogs |> List.iter set
|
||||
}
|
||||
|
||||
|
||||
/// Get the cached redirect rules for the given web log
|
||||
let redirectRules webLogId =
|
||||
_redirectCache[webLogId]
|
||||
|
||||
|
||||
/// Is the given theme in use by any web logs?
|
||||
let isThemeInUse themeId =
|
||||
_cache |> List.exists (fun wl -> wl.ThemeId = themeId)
|
||||
@ -122,30 +122,30 @@ module WebLogCache =
|
||||
|
||||
/// 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<WebLogId, DisplayPage array> ()
|
||||
|
||||
let private _cache = ConcurrentDictionary<WebLogId, DisplayPage array>()
|
||||
|
||||
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?
|
||||
let exists (ctx: HttpContext) = _cache.ContainsKey ctx.WebLog.Id
|
||||
|
||||
|
||||
/// Get the pages for the web log for this request
|
||||
let get (ctx: HttpContext) = _cache[ctx.WebLog.Id]
|
||||
|
||||
|
||||
/// Update the pages for the current web log
|
||||
let update (ctx: HttpContext) = backgroundTask {
|
||||
let! pages = ctx.Data.Page.FindListed ctx.WebLog.Id
|
||||
fillPages ctx.WebLog pages
|
||||
}
|
||||
|
||||
|
||||
/// Refresh the pages for the given web log
|
||||
let refresh (webLog: WebLog) (data: IData) = backgroundTask {
|
||||
let! pages = data.Page.FindListed webLog.Id
|
||||
@ -155,24 +155,24 @@ module PageListCache =
|
||||
|
||||
/// Cache of all categories, indexed by web log
|
||||
module CategoryCache =
|
||||
|
||||
|
||||
open MyWebLog.ViewModels
|
||||
|
||||
|
||||
/// The cache itself
|
||||
let private _cache = ConcurrentDictionary<WebLogId, DisplayCategory array> ()
|
||||
|
||||
let private _cache = ConcurrentDictionary<WebLogId, DisplayCategory array>()
|
||||
|
||||
/// Are there categories cached for this web log?
|
||||
let exists (ctx: HttpContext) = _cache.ContainsKey ctx.WebLog.Id
|
||||
|
||||
|
||||
/// Get the categories for the web log for this request
|
||||
let get (ctx: HttpContext) = _cache[ctx.WebLog.Id]
|
||||
|
||||
|
||||
/// Update the cache with fresh data
|
||||
let update (ctx: HttpContext) = backgroundTask {
|
||||
let! cats = ctx.Data.Category.FindAllForView ctx.WebLog.Id
|
||||
_cache[ctx.WebLog.Id] <- cats
|
||||
}
|
||||
|
||||
|
||||
/// Refresh the category cache for the given web log
|
||||
let refresh webLogId (data: IData) = backgroundTask {
|
||||
let! cats = data.Category.FindAllForView webLogId
|
||||
@ -182,19 +182,19 @@ module CategoryCache =
|
||||
|
||||
/// A cache of asset names by themes
|
||||
module ThemeAssetCache =
|
||||
|
||||
|
||||
/// A list of asset names for each theme
|
||||
let private _cache = ConcurrentDictionary<ThemeId, string list> ()
|
||||
|
||||
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 (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
|
||||
let fill (data: IData) = backgroundTask {
|
||||
let! assets = data.ThemeAsset.All()
|
||||
|
@ -31,16 +31,16 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.0.0" />
|
||||
<PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.1.0" />
|
||||
<PackageReference Include="DotLiquid" Version="2.2.692" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.11.1" />
|
||||
<PackageReference Include="Giraffe" Version="6.4.0" />
|
||||
<PackageReference Include="Giraffe.Htmx" Version="2.0.2" />
|
||||
<PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.2" />
|
||||
<PackageReference Include="NeoSmart.Caching.Sqlite.AspNetCore" Version="8.0.0" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.16.0" />
|
||||
<PackageReference Include="Giraffe" Version="7.0.2" />
|
||||
<PackageReference Include="Giraffe.Htmx" Version="2.0.4" />
|
||||
<PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.4" />
|
||||
<PackageReference Include="NeoSmart.Caching.Sqlite.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="RethinkDB.DistributedCache" Version="1.0.0-rc1" />
|
||||
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="8.0.400" />
|
||||
<PackageReference Include="System.ServiceModel.Syndication" Version="9.0.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -6,10 +6,10 @@ open MyWebLog
|
||||
|
||||
/// Middleware to derive the current web log
|
||||
type WebLogMiddleware(next: RequestDelegate, log: ILogger<WebLogMiddleware>) =
|
||||
|
||||
|
||||
/// Is the debug level enabled on the logger?
|
||||
let isDebug = log.IsEnabled LogLevel.Debug
|
||||
|
||||
|
||||
member _.InvokeAsync(ctx: HttpContext) = task {
|
||||
/// Create the full path of the request
|
||||
let path = $"{ctx.Request.Scheme}://{ctx.Request.Host.Value}{ctx.Request.Path.Value}"
|
||||
@ -32,7 +32,7 @@ type RedirectRuleMiddleware(next: RequestDelegate, _log: ILogger<RedirectRuleMid
|
||||
/// Shorthand for case-insensitive string equality
|
||||
let ciEquals str1 str2 =
|
||||
System.String.Equals(str1, str2, System.StringComparison.InvariantCultureIgnoreCase)
|
||||
|
||||
|
||||
member _.InvokeAsync(ctx: HttpContext) = task {
|
||||
let path = ctx.Request.Path.Value.ToLower()
|
||||
let matched =
|
||||
@ -59,11 +59,11 @@ open Npgsql
|
||||
|
||||
/// Logic to obtain a data connection and implementation based on configured values
|
||||
module DataImplementation =
|
||||
|
||||
|
||||
open MyWebLog.Converters
|
||||
open RethinkDb.Driver.FSharp
|
||||
open RethinkDb.Driver.Net
|
||||
|
||||
|
||||
/// Create an NpgsqlDataSource from the connection string, configuring appropriately
|
||||
let createNpgsqlDataSource (cfg: IConfiguration) =
|
||||
let builder = NpgsqlDataSourceBuilder(cfg.GetConnectionString "PostgreSQL")
|
||||
@ -83,12 +83,12 @@ module DataImplementation =
|
||||
let conn = Sqlite.Configuration.dbConn ()
|
||||
log.LogInformation $"Using SQLite database {conn.DataSource}"
|
||||
SQLiteData(conn, log, Json.configure (JsonSerializer.CreateDefault()))
|
||||
|
||||
|
||||
if hasConnStr "SQLite" then
|
||||
createSQLite (connStr "SQLite")
|
||||
elif hasConnStr "RethinkDB" then
|
||||
let log = sp.GetRequiredService<ILogger<RethinkDbData>>()
|
||||
let _ = Json.configure Converter.Serializer
|
||||
let _ = Json.configure Converter.Serializer
|
||||
let rethinkCfg = DataConfig.FromUri (connStr "RethinkDB")
|
||||
let conn = await (rethinkCfg.CreateConnectionAsync log)
|
||||
RethinkDbData(conn, rethinkCfg, log)
|
||||
@ -131,7 +131,7 @@ open Microsoft.AspNetCore.Authentication.Cookies
|
||||
open Microsoft.AspNetCore.Builder
|
||||
open Microsoft.AspNetCore.HttpOverrides
|
||||
open Microsoft.Extensions.Caching.Distributed
|
||||
open NeoSmart.Caching.Sqlite.AspNetCore
|
||||
open NeoSmart.Caching.Sqlite
|
||||
open RethinkDB.DistributedCache
|
||||
|
||||
[<EntryPoint>]
|
||||
@ -140,7 +140,7 @@ let main args =
|
||||
let builder = WebApplication.CreateBuilder(args)
|
||||
let _ = builder.Services.Configure<ForwardedHeadersOptions>(fun (opts : ForwardedHeadersOptions) ->
|
||||
opts.ForwardedHeaders <- ForwardedHeaders.XForwardedFor ||| ForwardedHeaders.XForwardedProto)
|
||||
let _ =
|
||||
let _ =
|
||||
builder.Services
|
||||
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(fun opts ->
|
||||
@ -150,17 +150,17 @@ let main args =
|
||||
let _ = builder.Services.AddLogging()
|
||||
let _ = builder.Services.AddAuthorization()
|
||||
let _ = builder.Services.AddAntiforgery()
|
||||
|
||||
|
||||
let sp = builder.Services.BuildServiceProvider()
|
||||
let data = DataImplementation.get sp
|
||||
let _ = builder.Services.AddSingleton<JsonSerializer> data.Serializer
|
||||
|
||||
|
||||
task {
|
||||
do! data.StartUp()
|
||||
do! WebLogCache.fill data
|
||||
do! ThemeAssetCache.fill data
|
||||
} |> Async.AwaitTask |> Async.RunSynchronously
|
||||
|
||||
|
||||
// Define distributed cache implementation based on data implementation
|
||||
match data with
|
||||
| :? RethinkDbData as rethink ->
|
||||
@ -189,18 +189,18 @@ let main args =
|
||||
Postgres.DistributedCache() :> IDistributedCache)
|
||||
()
|
||||
| _ -> ()
|
||||
|
||||
|
||||
let _ = builder.Services.AddSession(fun opts ->
|
||||
opts.IdleTimeout <- TimeSpan.FromMinutes 60
|
||||
opts.IdleTimeout <- TimeSpan.FromMinutes 60.
|
||||
opts.Cookie.HttpOnly <- true
|
||||
opts.Cookie.IsEssential <- true)
|
||||
let _ = builder.Services.AddGiraffe()
|
||||
|
||||
|
||||
// Set up DotLiquid
|
||||
DotLiquidBespoke.register ()
|
||||
|
||||
let app = builder.Build()
|
||||
|
||||
|
||||
match args |> Array.tryHead with
|
||||
| Some it when it = "init" -> Maintenance.createWebLog args app.Services
|
||||
| Some it when it = "import-links" -> Maintenance.importLinks args app.Services
|
||||
@ -222,13 +222,13 @@ let main args =
|
||||
if Directory.Exists themePath then
|
||||
for themeFile in Directory.EnumerateFiles(themePath, "*-theme.zip") do
|
||||
do! Maintenance.loadTheme [| ""; themeFile |] app.Services
|
||||
|
||||
|
||||
let _ = app.UseForwardedHeaders()
|
||||
|
||||
|
||||
(app.Services.GetRequiredService<IConfiguration>().GetSection "CanonicalDomains").Value
|
||||
|> (isNull >> not)
|
||||
|> function true -> app.UseCanonicalDomains() |> ignore | false -> ()
|
||||
|
||||
|
||||
let _ = app.UseCookiePolicy(CookiePolicyOptions (MinimumSameSitePolicy = SameSiteMode.Strict))
|
||||
let _ = app.UseMiddleware<WebLogMiddleware>()
|
||||
let _ = app.UseMiddleware<RedirectRuleMiddleware>()
|
||||
@ -241,5 +241,5 @@ let main args =
|
||||
app.Run()
|
||||
}
|
||||
|> Async.AwaitTask |> Async.RunSynchronously
|
||||
|
||||
|
||||
0 // Exit code
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"Generator": "myWebLog 3",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
@ -8,7 +8,7 @@
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://0.0.0.0:80"
|
||||
"Url": "http://0.0.0.0:5000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user