Load themes at startup (#20)
- Adjust release packaging (#20) - Fix default theme for beta-5 changes (#24) - Remove RethinkDB case fix (cleanup from #21) - Bump versions for next release
This commit is contained in:
parent
99ccdebcc7
commit
4514c4864d
15
build.fsx
15
build.fsx
|
@ -36,7 +36,7 @@ let zipTheme (name : string) (_ : TargetParameter) =
|
||||||
!! $"{path}/**/*"
|
!! $"{path}/**/*"
|
||||||
|> Zip.filesAsSpecs path //$"src/{name}-theme"
|
|> Zip.filesAsSpecs path //$"src/{name}-theme"
|
||||||
|> Seq.filter (fun (_, name) -> not (name.EndsWith ".zip"))
|
|> Seq.filter (fun (_, name) -> not (name.EndsWith ".zip"))
|
||||||
|> Zip.zipSpec $"{releasePath}/{name}.zip"
|
|> Zip.zipSpec $"{releasePath}/{name}-theme.zip"
|
||||||
|
|
||||||
/// Publish the project for the given runtime ID
|
/// Publish the project for the given runtime ID
|
||||||
let publishFor rid (_ : TargetParameter) =
|
let publishFor rid (_ : TargetParameter) =
|
||||||
|
@ -45,11 +45,12 @@ let publishFor rid (_ : TargetParameter) =
|
||||||
/// Package published output for the given runtime ID
|
/// Package published output for the given runtime ID
|
||||||
let packageFor (rid : string) (_ : TargetParameter) =
|
let packageFor (rid : string) (_ : TargetParameter) =
|
||||||
let path = $"{projectPath}/bin/Release/net6.0/{rid}/publish"
|
let path = $"{projectPath}/bin/Release/net6.0/{rid}/publish"
|
||||||
|
let prodSettings = $"{path}/appsettings.Production.json"
|
||||||
|
if File.exists prodSettings then File.delete prodSettings
|
||||||
[ !! $"{path}/**/*"
|
[ !! $"{path}/**/*"
|
||||||
|> Zip.filesAsSpecs path
|
|> Zip.filesAsSpecs path
|
||||||
|> Zip.moveToFolder "app"
|
Seq.singleton ($"{releasePath}/admin-theme.zip", "admin-theme.zip")
|
||||||
Seq.singleton ($"{releasePath}/admin.zip", "admin.zip")
|
Seq.singleton ($"{releasePath}/default-theme.zip", "default-theme.zip")
|
||||||
Seq.singleton ($"{releasePath}/default.zip", "default.zip")
|
|
||||||
]
|
]
|
||||||
|> Seq.concat
|
|> Seq.concat
|
||||||
|> Zip.zipSpec $"{releasePath}/myWebLog-{version}.{rid}.zip"
|
|> Zip.zipSpec $"{releasePath}/myWebLog-{version}.{rid}.zip"
|
||||||
|
@ -86,7 +87,7 @@ Target.create "RepackageLinux" (fun _ ->
|
||||||
Shell.mkdir workDir
|
Shell.mkdir workDir
|
||||||
Zip.unzip workDir zipArchive
|
Zip.unzip workDir zipArchive
|
||||||
Shell.cd workDir
|
Shell.cd workDir
|
||||||
sh "chmod" [ "+x"; "app/MyWebLog" ]
|
sh "chmod" [ "+x"; "./MyWebLog" ]
|
||||||
sh "tar" [ "cfj"; $"../myWebLog-{version}.linux-x64.tar.bz2"; "." ]
|
sh "tar" [ "cfj"; $"../myWebLog-{version}.linux-x64.tar.bz2"; "." ]
|
||||||
Shell.cd "../.."
|
Shell.cd "../.."
|
||||||
Shell.rm zipArchive
|
Shell.rm zipArchive
|
||||||
|
@ -96,8 +97,8 @@ Target.create "RepackageLinux" (fun _ ->
|
||||||
Target.create "All" ignore
|
Target.create "All" ignore
|
||||||
|
|
||||||
Target.create "RemoveThemeArchives" (fun _ ->
|
Target.create "RemoveThemeArchives" (fun _ ->
|
||||||
Shell.rm $"{releasePath}/admin.zip"
|
Shell.rm $"{releasePath}/admin-theme.zip"
|
||||||
Shell.rm $"{releasePath}/default.zip"
|
Shell.rm $"{releasePath}/default-theme.zip"
|
||||||
)
|
)
|
||||||
|
|
||||||
Target.create "CI" ignore
|
Target.create "CI" ignore
|
||||||
|
|
|
@ -1,171 +0,0 @@
|
||||||
|
|
||||||
// Category
|
|
||||||
r.db('myWebLog').table('Category').map({
|
|
||||||
Description: r.row('description'),
|
|
||||||
Id: r.row('id'),
|
|
||||||
Name: r.row('name'),
|
|
||||||
ParentId: r.row('parentId'),
|
|
||||||
Slug: r.row('slug'),
|
|
||||||
WebLogId: r.row('webLogId')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Page
|
|
||||||
r.db('myWebLog').table('Page').map({
|
|
||||||
AuthorId: r.row('authorId'),
|
|
||||||
Id: r.row('id'),
|
|
||||||
Metadata: r.row('metadata').map(function (meta) {
|
|
||||||
return { Name: meta('name'), Value: meta('value') }
|
|
||||||
}),
|
|
||||||
Permalink: r.row('permalink'),
|
|
||||||
PriorPermalinks: r.row('priorPermalinks'),
|
|
||||||
PublishedOn: r.row('publishedOn'),
|
|
||||||
Revisions: r.row('revisions').map(function (rev) {
|
|
||||||
return {
|
|
||||||
AsOf: rev('asOf'),
|
|
||||||
Text: rev('text')
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
IsInPageList: r.row('showInPageList'),
|
|
||||||
Template: r.row('template'),
|
|
||||||
Text: r.row('text'),
|
|
||||||
Title: r.row('title'),
|
|
||||||
UpdatedOn: r.row('updatedOn'),
|
|
||||||
WebLogId: r.row('webLogId')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Post
|
|
||||||
r.db('myWebLog').table('Post').map({
|
|
||||||
AuthorId: r.row('authorId'),
|
|
||||||
CategoryIds: r.row('categoryIds'),
|
|
||||||
Episode: r.branch(r.row.hasFields('episode'), {
|
|
||||||
Duration: r.row('episode')('duration'),
|
|
||||||
Length: r.row('episode')('length'),
|
|
||||||
Media: r.row('episode')('media'),
|
|
||||||
MediaType: r.row('episode')('mediaType').default(null),
|
|
||||||
ImageUrl: r.row('episode')('imageUrl').default(null),
|
|
||||||
Subtitle: r.row('episode')('subtitle').default(null),
|
|
||||||
Explicit: r.row('episode')('explicit').default(null),
|
|
||||||
ChapterFile: r.row('episode')('chapterFile').default(null),
|
|
||||||
ChapterType: r.row('episode')('chapterType').default(null),
|
|
||||||
TranscriptUrl: r.row('episode')('transcriptUrl').default(null),
|
|
||||||
TranscriptType: r.row('episode')('transcriptType').default(null),
|
|
||||||
TranscriptLang: r.row('episode')('transcriptLang').default(null),
|
|
||||||
TranscriptCaptions: r.row('episode')('transcriptCaptions').default(null),
|
|
||||||
SeasonNumber: r.row('episode')('seasonNumber').default(null),
|
|
||||||
SeasonDescription: r.row('episode')('seasonDescription').default(null),
|
|
||||||
EpisodeNumber: r.row('episode')('episodeNumber').default(null),
|
|
||||||
EpisodeDescription: r.row('episode')('episodeDescription').default(null)
|
|
||||||
}, null),
|
|
||||||
Id: r.row('id'),
|
|
||||||
Metadata: r.row('metadata').map(function (meta) {
|
|
||||||
return { Name: meta('name'), Value: meta('value') }
|
|
||||||
}),
|
|
||||||
Permalink: r.row('permalink'),
|
|
||||||
PriorPermalinks: r.row('priorPermalinks'),
|
|
||||||
PublishedOn: r.row('publishedOn'),
|
|
||||||
Revisions: r.row('revisions').map(function (rev) {
|
|
||||||
return {
|
|
||||||
AsOf: rev('asOf'),
|
|
||||||
Text: rev('text')
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Status: r.row('status'),
|
|
||||||
Tags: r.row('tags'),
|
|
||||||
Template: r.row('template').default(null),
|
|
||||||
Text: r.row('text'),
|
|
||||||
Title: r.row('title'),
|
|
||||||
UpdatedOn: r.row('updatedOn'),
|
|
||||||
WebLogId: r.row('webLogId')
|
|
||||||
})
|
|
||||||
|
|
||||||
// TagMap
|
|
||||||
r.db('myWebLog').table('TagMap').map({
|
|
||||||
Id: r.row('id'),
|
|
||||||
Tag: r.row('tag'),
|
|
||||||
UrlValue: r.row('urlValue'),
|
|
||||||
WebLogId: r.row('webLogId')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Theme
|
|
||||||
r.db('myWebLog').table('Theme').map({
|
|
||||||
Id: r.row('id'),
|
|
||||||
Name: r.row('name'),
|
|
||||||
Templates: r.row('templates').map(function (tmpl) {
|
|
||||||
return {
|
|
||||||
Name: tmpl('name'),
|
|
||||||
Text: tmpl('text')
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Version: r.row('version')
|
|
||||||
})
|
|
||||||
|
|
||||||
// ThemeAsset
|
|
||||||
r.db('myWebLog').table('ThemeAsset').map({
|
|
||||||
Data: r.row('data'),
|
|
||||||
Id: r.row('id'),
|
|
||||||
UpdatedOn: r.row('updatedOn')
|
|
||||||
})
|
|
||||||
|
|
||||||
// WebLog
|
|
||||||
r.db('myWebLog').table('WebLog').map(
|
|
||||||
{ AutoHtmx: r.row('autoHtmx'),
|
|
||||||
DefaultPage: r.row('defaultPage'),
|
|
||||||
Id: r.row('id'),
|
|
||||||
Name: r.row('name'),
|
|
||||||
PostsPerPage: r.row('postsPerPage'),
|
|
||||||
Rss: {
|
|
||||||
IsCategoryEnabled: r.row('rss')('categoryEnabled'),
|
|
||||||
Copyright: r.row('rss')('copyright'),
|
|
||||||
CustomFeeds: r.row('rss')('customFeeds').map(function (feed) {
|
|
||||||
return {
|
|
||||||
Id: feed('id'),
|
|
||||||
Path: feed('path'),
|
|
||||||
Podcast: {
|
|
||||||
DefaultMediaType: feed('podcast')('defaultMediaType'),
|
|
||||||
DisplayedAuthor: feed('podcast')('displayedAuthor'),
|
|
||||||
Email: feed('podcast')('email'),
|
|
||||||
Explicit: feed('podcast')('explicit'),
|
|
||||||
FundingText: feed('podcast')('fundingText'),
|
|
||||||
FundingUrl: feed('podcast')('fundingUrl'),
|
|
||||||
PodcastGuid: feed('podcast')('guid'),
|
|
||||||
AppleCategory: feed('podcast')('iTunesCategory'),
|
|
||||||
AppleSubcategory: feed('podcast')('iTunesSubcategory'),
|
|
||||||
ImageUrl: feed('podcast')('imageUrl'),
|
|
||||||
ItemsInFeed: feed('podcast')('itemsInFeed'),
|
|
||||||
MediaBaseUrl: feed('podcast')('mediaBaseUrl'),
|
|
||||||
Medium: feed('podcast')('medium'),
|
|
||||||
Subtitle: feed('podcast')('subtitle'),
|
|
||||||
Summary: feed('podcast')('summary'),
|
|
||||||
Title: feed('podcast')('title')
|
|
||||||
},
|
|
||||||
Source: feed('source')
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
IsFeedEnabled: r.row('rss')('feedEnabled'),
|
|
||||||
FeedName: r.row('rss')('feedName'),
|
|
||||||
ItemsInFeed: r.row('rss')('itemsInFeed'),
|
|
||||||
IsTagEnabled: r.row('rss')('tagEnabled')
|
|
||||||
},
|
|
||||||
Slug: r.row('slug'),
|
|
||||||
Subtitle: r.row('subtitle'),
|
|
||||||
ThemeId: r.row('themePath'),
|
|
||||||
TimeZone: r.row('timeZone'),
|
|
||||||
Uploads: r.row('uploads'),
|
|
||||||
UrlBase: r.row('urlBase')
|
|
||||||
})
|
|
||||||
|
|
||||||
// WebLogUser
|
|
||||||
r.db('myWebLog').table('WebLogUser').map({
|
|
||||||
AccessLevel: r.row('authorizationLevel'),
|
|
||||||
FirstName: r.row('firstName'),
|
|
||||||
Id: r.row('id'),
|
|
||||||
LastName: r.row('lastName'),
|
|
||||||
PasswordHash: r.row('passwordHash'),
|
|
||||||
PreferredName: r.row('preferredName'),
|
|
||||||
Salt: r.row('salt'),
|
|
||||||
Url: r.row('url'),
|
|
||||||
Email: r.row('userName'),
|
|
||||||
WebLogId: r.row('webLogId'),
|
|
||||||
CreatedOn: r.branch(r.row.hasFields('createdOn'), r.row('createdOn'), r.expr(new Date(0))),
|
|
||||||
LastSeenOn: r.row('lastSeenOn').default(null)
|
|
||||||
})
|
|
10
src/Directory.Build.props
Normal file
10
src/Directory.Build.props
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||||
|
<FileVersion>2.0.0.0</FileVersion>
|
||||||
|
<Version>2.0.0</Version>
|
||||||
|
<VersionSuffix>rc1</VersionSuffix>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
|
@ -1,11 +1,5 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MyWebLog.Domain\MyWebLog.Domain.fsproj" />
|
<ProjectReference Include="..\MyWebLog.Domain\MyWebLog.Domain.fsproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="SupportTypes.fs" />
|
<Compile Include="SupportTypes.fs" />
|
||||||
<Compile Include="DataTypes.fs" />
|
<Compile Include="DataTypes.fs" />
|
||||||
|
|
|
@ -244,7 +244,10 @@ let private updateAssets themeId (zip : ZipArchive) (data : IData) = backgroundT
|
||||||
/// Get the theme name from the file name given
|
/// Get the theme name from the file name given
|
||||||
let getThemeName (fileName : string) =
|
let getThemeName (fileName : string) =
|
||||||
let themeName = fileName.Split(".").[0].ToLowerInvariant().Replace (" ", "-")
|
let themeName = fileName.Split(".").[0].ToLowerInvariant().Replace (" ", "-")
|
||||||
if Regex.IsMatch (themeName, """^[a-z0-9\-]+$""") then Ok themeName else Error $"Theme name {fileName} is invalid"
|
if themeName.EndsWith "-theme" then
|
||||||
|
if Regex.IsMatch (themeName, """^[a-z0-9\-]+$""") then Ok (themeName.Substring (0, themeName.Length - 6))
|
||||||
|
else Error $"Theme name {fileName} is invalid"
|
||||||
|
else Error "Theme .zip file name must end in \"-theme.zip\""
|
||||||
|
|
||||||
/// Load a theme from the given stream, which should contain a ZIP archive
|
/// Load a theme from the given stream, which should contain a ZIP archive
|
||||||
let loadThemeFromZip themeName file clean (data : IData) = backgroundTask {
|
let loadThemeFromZip themeName file clean (data : IData) = backgroundTask {
|
||||||
|
@ -260,6 +263,8 @@ let loadThemeFromZip themeName file clean (data : IData) = backgroundTask {
|
||||||
let! theme = updateTemplates theme zip
|
let! theme = updateTemplates theme zip
|
||||||
do! data.Theme.Save theme
|
do! data.Theme.Save theme
|
||||||
do! updateAssets themeId zip data
|
do! updateAssets themeId zip data
|
||||||
|
|
||||||
|
return theme
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /admin/theme/update
|
// POST /admin/theme/update
|
||||||
|
@ -271,7 +276,7 @@ let updateTheme : HttpHandler = requireAccess Administrator >=> fun next ctx ->
|
||||||
let data = ctx.Data
|
let data = ctx.Data
|
||||||
use stream = new MemoryStream ()
|
use stream = new MemoryStream ()
|
||||||
do! themeFile.CopyToAsync stream
|
do! themeFile.CopyToAsync stream
|
||||||
do! loadThemeFromZip themeName stream true data
|
let! _ = loadThemeFromZip themeName stream true data
|
||||||
do! ThemeAssetCache.refreshTheme (ThemeId themeName) data
|
do! ThemeAssetCache.refreshTheme (ThemeId themeName) data
|
||||||
TemplateCache.invalidateTheme themeName
|
TemplateCache.invalidateTheme themeName
|
||||||
do! addMessage ctx { UserMessage.success with Message = "Theme updated successfully" }
|
do! addMessage ctx { UserMessage.success with Message = "Theme updated successfully" }
|
||||||
|
|
|
@ -128,43 +128,6 @@ let edit usrId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> tas
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /admin/user/save
|
|
||||||
let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
|
|
||||||
let! model = ctx.BindFormAsync<EditUserModel> ()
|
|
||||||
let data = ctx.Data
|
|
||||||
let tryUser =
|
|
||||||
if model.IsNew then
|
|
||||||
{ WebLogUser.empty with
|
|
||||||
Id = WebLogUserId.create ()
|
|
||||||
WebLogId = ctx.WebLog.Id
|
|
||||||
CreatedOn = DateTime.UtcNow
|
|
||||||
} |> someTask
|
|
||||||
else data.WebLogUser.FindById (WebLogUserId model.Id) ctx.WebLog.Id
|
|
||||||
match! tryUser with
|
|
||||||
| Some user when model.Password = model.PasswordConfirm ->
|
|
||||||
let updatedUser = model.UpdateUser user
|
|
||||||
if updatedUser.AccessLevel = Administrator && not (ctx.HasAccessLevel Administrator) then
|
|
||||||
return! goAway next ctx
|
|
||||||
else
|
|
||||||
let updatedUser =
|
|
||||||
if model.Password = "" then updatedUser
|
|
||||||
else
|
|
||||||
let salt = Guid.NewGuid ()
|
|
||||||
{ updatedUser with PasswordHash = hashedPassword model.Password model.Email salt; Salt = salt }
|
|
||||||
do! (if model.IsNew then data.WebLogUser.Add else data.WebLogUser.Update) updatedUser
|
|
||||||
do! addMessage ctx
|
|
||||||
{ UserMessage.success with
|
|
||||||
Message = $"""{if model.IsNew then "Add" else "Updat"}ed user successfully"""
|
|
||||||
}
|
|
||||||
return! bare next ctx
|
|
||||||
| Some _ ->
|
|
||||||
do! addMessage ctx { UserMessage.error with Message = "The passwords did not match; nothing saved" }
|
|
||||||
return!
|
|
||||||
(withHxRetarget $"#user_{model.Id}" >=> showEdit { model with Password = ""; PasswordConfirm = "" })
|
|
||||||
next ctx
|
|
||||||
| None -> return! Error.notFound next ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /admin/user/{id}/delete
|
// POST /admin/user/{id}/delete
|
||||||
let delete userId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
|
let delete userId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
|
||||||
let data = ctx.Data
|
let data = ctx.Data
|
||||||
|
@ -237,3 +200,44 @@ let saveMyInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
||||||
return! showMyInfo { model with NewPassword = ""; NewPasswordConfirm = "" } user next ctx
|
return! showMyInfo { model with NewPassword = ""; NewPasswordConfirm = "" } user next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User save is not statically compilable; not sure why, but we'll revisit it at some point
|
||||||
|
#nowarn "3511"
|
||||||
|
|
||||||
|
// POST /admin/user/save
|
||||||
|
let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
|
||||||
|
let! model = ctx.BindFormAsync<EditUserModel> ()
|
||||||
|
let data = ctx.Data
|
||||||
|
let tryUser =
|
||||||
|
if model.IsNew then
|
||||||
|
{ WebLogUser.empty with
|
||||||
|
Id = WebLogUserId.create ()
|
||||||
|
WebLogId = ctx.WebLog.Id
|
||||||
|
CreatedOn = DateTime.UtcNow
|
||||||
|
} |> someTask
|
||||||
|
else data.WebLogUser.FindById (WebLogUserId model.Id) ctx.WebLog.Id
|
||||||
|
match! tryUser with
|
||||||
|
| Some user when model.Password = model.PasswordConfirm ->
|
||||||
|
let updatedUser = model.UpdateUser user
|
||||||
|
if updatedUser.AccessLevel = Administrator && not (ctx.HasAccessLevel Administrator) then
|
||||||
|
return! goAway next ctx
|
||||||
|
else
|
||||||
|
let toUpdate =
|
||||||
|
if model.Password = "" then updatedUser
|
||||||
|
else
|
||||||
|
let salt = Guid.NewGuid ()
|
||||||
|
{ updatedUser with PasswordHash = hashedPassword model.Password model.Email salt; Salt = salt }
|
||||||
|
do! (if model.IsNew then data.WebLogUser.Add else data.WebLogUser.Update) toUpdate
|
||||||
|
do! addMessage ctx
|
||||||
|
{ UserMessage.success with
|
||||||
|
Message = $"""{if model.IsNew then "Add" else "Updat"}ed user successfully"""
|
||||||
|
}
|
||||||
|
return! bare next ctx
|
||||||
|
| Some _ ->
|
||||||
|
do! addMessage ctx { UserMessage.error with Message = "The passwords did not match; nothing saved" }
|
||||||
|
return!
|
||||||
|
(withHxRetarget $"#user_{model.Id}" >=> showEdit { model with Password = ""; PasswordConfirm = "" })
|
||||||
|
next ctx
|
||||||
|
| None -> return! Error.notFound next ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,6 +128,8 @@ let importLinks args sp = task {
|
||||||
// Loading a theme and restoring a backup are not statically compilable; this is OK
|
// Loading a theme and restoring a backup are not statically compilable; this is OK
|
||||||
#nowarn "3511"
|
#nowarn "3511"
|
||||||
|
|
||||||
|
open Microsoft.Extensions.Logging
|
||||||
|
|
||||||
/// Load a theme from the given ZIP file
|
/// Load a theme from the given ZIP file
|
||||||
let loadTheme (args : string[]) (sp : IServiceProvider) = task {
|
let loadTheme (args : string[]) (sp : IServiceProvider) = task {
|
||||||
if args.Length > 1 then
|
if args.Length > 1 then
|
||||||
|
@ -142,8 +144,10 @@ let loadTheme (args : string[]) (sp : IServiceProvider) = task {
|
||||||
use stream = File.Open (args[1], FileMode.Open)
|
use stream = File.Open (args[1], FileMode.Open)
|
||||||
use copy = new MemoryStream ()
|
use copy = new MemoryStream ()
|
||||||
do! stream.CopyToAsync copy
|
do! stream.CopyToAsync copy
|
||||||
do! Handlers.Admin.loadThemeFromZip themeName copy clean data
|
let! theme = Handlers.Admin.loadThemeFromZip themeName copy clean data
|
||||||
printfn $"Theme {themeName} loaded successfully"
|
let fac = sp.GetRequiredService<ILoggerFactory> ()
|
||||||
|
let log = fac.CreateLogger "MyWebLog.Themes"
|
||||||
|
log.LogInformation $"{theme.Name} v{theme.Version} ({ThemeId.toString theme.Id}) loaded"
|
||||||
| Error message -> eprintfn $"{message}"
|
| Error message -> eprintfn $"{message}"
|
||||||
else
|
else
|
||||||
eprintfn "Usage: MyWebLog load-theme [theme-zip-file-name] [*clean-load]"
|
eprintfn "Usage: MyWebLog load-theme [theme-zip-file-name] [*clean-load]"
|
||||||
|
|
|
@ -2,11 +2,8 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<SelfContained>false</SelfContained>
|
<SelfContained>false</SelfContained>
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
<NoWarn>3391</NoWarn>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -82,6 +82,7 @@ let showHelp () =
|
||||||
Task.FromResult ()
|
Task.FromResult ()
|
||||||
|
|
||||||
|
|
||||||
|
open System.IO
|
||||||
open Giraffe
|
open Giraffe
|
||||||
open Giraffe.EndpointRouting
|
open Giraffe.EndpointRouting
|
||||||
open Microsoft.AspNetCore.Authentication.Cookies
|
open Microsoft.AspNetCore.Authentication.Cookies
|
||||||
|
@ -135,7 +136,7 @@ let rec main args =
|
||||||
|> ignore
|
|> ignore
|
||||||
builder.Services.AddScoped<IData, SQLiteData> () |> ignore
|
builder.Services.AddScoped<IData, SQLiteData> () |> ignore
|
||||||
// Use SQLite for caching as well
|
// Use SQLite for caching as well
|
||||||
let cachePath = Option.ofObj (cfg.GetConnectionString "SQLiteCachePath") |> Option.defaultValue "./session.db"
|
let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SQLiteCachePath")) "./session.db"
|
||||||
builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath) |> ignore
|
builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath) |> ignore
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
|
|
||||||
|
@ -162,7 +163,11 @@ let rec main args =
|
||||||
| Some it ->
|
| Some it ->
|
||||||
printfn $"""Unrecognized command "{it}" - valid commands are:"""
|
printfn $"""Unrecognized command "{it}" - valid commands are:"""
|
||||||
showHelp ()
|
showHelp ()
|
||||||
| None ->
|
| None -> task {
|
||||||
|
// Load all themes in the application directory
|
||||||
|
for themeFile in Directory.EnumerateFiles (".", "*-theme.zip") do
|
||||||
|
do! Maintenance.loadTheme [| ""; themeFile |] app.Services
|
||||||
|
|
||||||
let _ = app.UseForwardedHeaders ()
|
let _ = app.UseForwardedHeaders ()
|
||||||
let _ = app.UseCookiePolicy (CookiePolicyOptions (MinimumSameSitePolicy = SameSiteMode.Strict))
|
let _ = app.UseCookiePolicy (CookiePolicyOptions (MinimumSameSitePolicy = SameSiteMode.Strict))
|
||||||
let _ = app.UseMiddleware<WebLogMiddleware> ()
|
let _ = app.UseMiddleware<WebLogMiddleware> ()
|
||||||
|
@ -172,7 +177,8 @@ let rec main args =
|
||||||
let _ = app.UseSession ()
|
let _ = app.UseSession ()
|
||||||
let _ = app.UseGiraffe Handlers.Routes.endpoint
|
let _ = app.UseGiraffe Handlers.Routes.endpoint
|
||||||
|
|
||||||
Task.FromResult (app.Run ())
|
app.Run ()
|
||||||
|
}
|
||||||
|> Async.AwaitTask |> Async.RunSynchronously
|
|> Async.AwaitTask |> Async.RunSynchronously
|
||||||
|
|
||||||
0 // Exit code
|
0 // Exit code
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"Generator": "myWebLog 2.0-beta05",
|
"Generator": "myWebLog 2.0-rc1",
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"MyWebLog.Handlers": "Information"
|
"MyWebLog.Handlers": "Information"
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
myWebLog Admin
|
myWebLog Admin
|
||||||
2.0.0-beta05
|
2.0.0-rc1
|
|
@ -1,10 +1,7 @@
|
||||||
{%- if is_category or is_tag %}
|
{%- if is_category or is_tag %}
|
||||||
<h1 class="index-title">{{ page_title }}</h1>
|
<h1 class="index-title">{{ page_title }}</h1>
|
||||||
{%- if is_category %}
|
{%- if subtitle %}<h4 class="text-muted">{{ subtitle }}</h4>{% endif -%}
|
||||||
{%- assign cat = categories | where: "slug", slug | first -%}
|
{% endif %}
|
||||||
{%- if cat.description %}<h4 class="text-muted">{{ cat.description.value }}</h4>{% endif -%}
|
|
||||||
{%- endif %}
|
|
||||||
{%- endif %}
|
|
||||||
<section class="container mt-3" aria-label="The posts for the page">
|
<section class="container mt-3" aria-label="The posts for the page">
|
||||||
{% for post in model.posts %}
|
{% for post in model.posts %}
|
||||||
<article>
|
<article>
|
||||||
|
@ -27,7 +24,7 @@
|
||||||
{%- if category_count > 0 -%}
|
{%- if category_count > 0 -%}
|
||||||
Categorized under:
|
Categorized under:
|
||||||
{% for cat in post.category_ids -%}
|
{% for cat in post.category_ids -%}
|
||||||
{%- assign this_cat = categories | where: "id", cat | first -%}
|
{%- assign this_cat = categories | where: "Id", cat | first -%}
|
||||||
{{ this_cat.name }}{% unless forloop.last %}, {% endunless %}
|
{{ this_cat.name }}{% unless forloop.last %}, {% endunless %}
|
||||||
{%- assign cat_names = this_cat.name | concat: cat_names -%}
|
{%- assign cat_names = this_cat.name | concat: cat_names -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<h4 class="item-meta text-muted">
|
<h4 class="item-meta text-muted">
|
||||||
Categorized under
|
Categorized under
|
||||||
{% for cat_id in post.category_ids -%}
|
{% for cat_id in post.category_ids -%}
|
||||||
{% assign cat = categories | where: "id", cat_id | first %}
|
{% assign cat = categories | where: "Id", cat_id | first %}
|
||||||
<span class="text-nowrap">
|
<span class="text-nowrap">
|
||||||
<a href="{{ cat | category_link }}" title="Categorized under “{{ cat.name | escape }}”">
|
<a href="{{ cat | category_link }}" title="Categorized under “{{ cat.name | escape }}”">
|
||||||
{{ cat.name }}
|
{{ cat.name }}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
myWebLog Default Theme
|
myWebLog Default Theme
|
||||||
2.0.0-alpha36
|
2.0.0-rc1
|
Loading…
Reference in New Issue
Block a user