Rule add/edit/move/delete works (#39)

- Begin moving auth to route definition where practical
- Fix typo on post list page
This commit is contained in:
Daniel J. Summers 2023-07-30 21:00:31 -04:00
parent 3ef4499a90
commit dc6b066e79
15 changed files with 322 additions and 97 deletions

View File

@ -259,6 +259,9 @@ type IWebLogData =
/// Find a web log by its ID /// Find a web log by its ID
abstract member FindById : WebLogId -> Task<WebLog option> abstract member FindById : WebLogId -> Task<WebLog option>
/// Update redirect rules for a web log
abstract member UpdateRedirectRules : WebLog -> Task<unit>
/// Update RSS options for a web log /// Update RSS options for a web log
abstract member UpdateRssOptions : WebLog -> Task<unit> abstract member UpdateRssOptions : WebLog -> Task<unit>

View File

@ -45,11 +45,13 @@ type PostgresWebLogData (log : ILogger) =
log.LogTrace "WebLog.findById" log.LogTrace "WebLog.findById"
Find.byId<WebLog> Table.WebLog (WebLogId.toString webLogId) Find.byId<WebLog> Table.WebLog (WebLogId.toString webLogId)
/// Update settings for a web log let updateRedirectRules (webLog : WebLog) = backgroundTask {
let updateSettings (webLog : WebLog) = log.LogTrace "WebLog.updateRedirectRules"
log.LogTrace "WebLog.updateSettings" match! findById webLog.Id with
Update.full Table.WebLog (WebLogId.toString webLog.Id) webLog | Some _ ->
do! Update.partialById Table.WebLog (WebLogId.toString webLog.Id) {| RedirectRules = webLog.RedirectRules |}
| None -> ()
}
/// Update RSS options for a web log /// Update RSS options for a web log
let updateRssOptions (webLog : WebLog) = backgroundTask { let updateRssOptions (webLog : WebLog) = backgroundTask {
log.LogTrace "WebLog.updateRssOptions" log.LogTrace "WebLog.updateRssOptions"
@ -58,11 +60,17 @@ type PostgresWebLogData (log : ILogger) =
| None -> () | None -> ()
} }
/// Update settings for a web log
let updateSettings (webLog : WebLog) =
log.LogTrace "WebLog.updateSettings"
Update.full Table.WebLog (WebLogId.toString webLog.Id) webLog
interface IWebLogData with interface IWebLogData with
member _.Add webLog = add webLog member _.Add webLog = add webLog
member _.All () = all () member _.All () = all ()
member _.Delete webLogId = delete webLogId member _.Delete webLogId = delete webLogId
member _.FindByHost url = findByHost url member _.FindByHost url = findByHost url
member _.FindById webLogId = findById webLogId member _.FindById webLogId = findById webLogId
member _.UpdateSettings webLog = updateSettings webLog member _.UpdateRedirectRules webLog = updateRedirectRules webLog
member _.UpdateRssOptions webLog = updateRssOptions webLog member _.UpdateRssOptions webLog = updateRssOptions webLog
member _.UpdateSettings webLog = updateSettings webLog

View File

@ -162,7 +162,7 @@ type PostgresData (log : ILogger<PostgresData>, ser : JsonSerializer) =
/// Migrate from v2 to v2.1 /// Migrate from v2 to v2.1
let migrateV2ToV2point1 () = backgroundTask { let migrateV2ToV2point1 () = backgroundTask {
Utils.logMigrationStep log "v2 to v2.1" "Adding empty redirect rule set to all weblogs" Utils.logMigrationStep log "v2 to v2.1" "Adding empty redirect rule set to all weblogs"
do! Custom.nonQuery $"UPDATE {Table.WebLog} SET data['RedirectRules'] = '[]'::json" [] do! Custom.nonQuery $"""UPDATE {Table.WebLog} SET data = data + '{{ "RedirectRules": [] }}'::json""" []
Utils.logMigrationStep log "v2 to v2.1" "Setting database to version 2.1" Utils.logMigrationStep log "v2 to v2.1" "Setting database to version 2.1"
do! setDbVersion "v2.1" do! setDbVersion "v2.1"

View File

@ -1031,6 +1031,13 @@ type RethinkDbData (conn : Net.IConnection, config : DataConfig, log : ILogger<R
resultOption; withRetryOptionDefault conn resultOption; withRetryOptionDefault conn
} }
member _.UpdateRedirectRules webLog = rethink {
withTable Table.WebLog
get webLog.Id
update [ nameof WebLog.empty.RedirectRules, webLog.RedirectRules :> obj ]
write; withRetryDefault; ignoreResult conn
}
member _.UpdateRssOptions webLog = rethink { member _.UpdateRssOptions webLog = rethink {
withTable Table.WebLog withTable Table.WebLog
get webLog.Id get webLog.Id

View File

@ -206,6 +206,33 @@ type SQLiteWebLogData (conn : SqliteConnection, ser : JsonSerializer) =
return None return None
} }
/// Update redirect rules for a web log
let updateRedirectRules webLog = backgroundTask {
use cmd = conn.CreateCommand ()
cmd.CommandText <- "UPDATE web_log SET redirect_rules = @redirectRules WHERE id = @id"
cmd.Parameters.AddWithValue ("@redirectRules", Utils.serialize ser webLog.RedirectRules) |> ignore
cmd.Parameters.AddWithValue ("@id", WebLogId.toString webLog.Id) |> ignore
do! write cmd
}
/// Update RSS options for a web log
let updateRssOptions webLog = backgroundTask {
use cmd = conn.CreateCommand ()
cmd.CommandText <-
"UPDATE web_log
SET is_feed_enabled = @isFeedEnabled,
feed_name = @feedName,
items_in_feed = @itemsInFeed,
is_category_enabled = @isCategoryEnabled,
is_tag_enabled = @isTagEnabled,
copyright = @copyright
WHERE id = @id"
addWebLogRssParameters cmd webLog
cmd.Parameters.AddWithValue ("@id", WebLogId.toString webLog.Id) |> ignore
do! write cmd
do! updateCustomFeeds webLog
}
/// Update settings for a web log /// Update settings for a web log
let updateSettings webLog = backgroundTask { let updateSettings webLog = backgroundTask {
use cmd = conn.CreateCommand () use cmd = conn.CreateCommand ()
@ -233,29 +260,12 @@ type SQLiteWebLogData (conn : SqliteConnection, ser : JsonSerializer) =
do! write cmd do! write cmd
} }
/// Update RSS options for a web log
let updateRssOptions webLog = backgroundTask {
use cmd = conn.CreateCommand ()
cmd.CommandText <-
"UPDATE web_log
SET is_feed_enabled = @isFeedEnabled,
feed_name = @feedName,
items_in_feed = @itemsInFeed,
is_category_enabled = @isCategoryEnabled,
is_tag_enabled = @isTagEnabled,
copyright = @copyright
WHERE id = @id"
addWebLogRssParameters cmd webLog
cmd.Parameters.AddWithValue ("@id", WebLogId.toString webLog.Id) |> ignore
do! write cmd
do! updateCustomFeeds webLog
}
interface IWebLogData with interface IWebLogData with
member _.Add webLog = add webLog member _.Add webLog = add webLog
member _.All () = all () member _.All () = all ()
member _.Delete webLogId = delete webLogId member _.Delete webLogId = delete webLogId
member _.FindByHost url = findByHost url member _.FindByHost url = findByHost url
member _.FindById webLogId = findById webLogId member _.FindById webLogId = findById webLogId
member _.UpdateSettings webLog = updateSettings webLog member _.UpdateRedirectRules webLog = updateRedirectRules webLog
member _.UpdateRssOptions webLog = updateRssOptions webLog member _.UpdateRssOptions webLog = updateRssOptions webLog
member _.UpdateSettings webLog = updateSettings webLog

View File

@ -426,12 +426,24 @@ module PostId =
type RedirectRule = type RedirectRule =
{ /// The From string or pattern { /// The From string or pattern
From : string From : string
/// The To string or pattern /// The To string or pattern
To : string To : string
/// Whether to use regular expressions on this rule /// Whether to use regular expressions on this rule
IsRegex : bool IsRegex : bool
} }
/// Functions to support redirect rules
module RedirectRule =
/// An empty redirect rule
let empty =
{ From = ""
To = ""
IsRegex = false
}
/// An identifier for a custom feed /// An identifier for a custom feed
type CustomFeedId = CustomFeedId of string type CustomFeedId = CustomFeedId of string

View File

@ -807,6 +807,43 @@ type EditPostModel =
} }
/// View model to add/edit a redirect rule
[<CLIMutable; NoComparison; NoEquality>]
type EditRedirectRuleModel =
{ /// The ID (index) of the rule being edited
RuleId : int
/// The "from" side of the rule
From : string
/// The "to" side of the rule
To : string
/// Whether this rule uses a regular expression
IsRegex : bool
/// Whether a new rule should be inserted at the top or appended to the end (ignored for edits)
InsertAtTop : bool
}
/// Create a model from an existing rule
static member fromRule idx (rule : RedirectRule) =
{ RuleId = idx
From = rule.From
To = rule.To
IsRegex = rule.IsRegex
InsertAtTop = false
}
/// Update a rule with the values from this model
member this.UpdateRule (rule : RedirectRule) =
{ rule with
From = this.From
To = this.To
IsRegex = this.IsRegex
}
/// View model to edit RSS settings /// View model to edit RSS settings
[<CLIMutable; NoComparison; NoEquality>] [<CLIMutable; NoComparison; NoEquality>]
type EditRssModel = type EditRssModel =

View File

@ -224,15 +224,17 @@ let register () =
Template.RegisterTag<UserLinksTag> "user_links" Template.RegisterTag<UserLinksTag> "user_links"
[ // Domain types [ // Domain types
typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page> typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page>
typeof<RssOptions>; typeof<TagMap>; typeof<UploadDestination>; typeof<WebLog> typeof<RedirectRule>; typeof<RssOptions>; typeof<TagMap>; typeof<UploadDestination>; typeof<WebLog>
// View models // View models
typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayCustomFeed>; typeof<DisplayPage> typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayCustomFeed>
typeof<DisplayRevision>; typeof<DisplayTheme>; typeof<DisplayUpload>; typeof<DisplayUser> typeof<DisplayPage>; typeof<DisplayRevision>; typeof<DisplayTheme>
typeof<EditCategoryModel>; typeof<EditCustomFeedModel>; typeof<EditMyInfoModel>; typeof<EditPageModel> typeof<DisplayUpload>; typeof<DisplayUser>; typeof<EditCategoryModel>
typeof<EditPostModel>; typeof<EditRssModel>; typeof<EditTagMapModel>; typeof<EditUserModel> typeof<EditCustomFeedModel>; typeof<EditMyInfoModel>; typeof<EditPageModel>
typeof<LogOnModel>; typeof<ManagePermalinksModel>; typeof<ManageRevisionsModel>; typeof<PostDisplay> typeof<EditPostModel>; typeof<EditRedirectRuleModel>; typeof<EditRssModel>
typeof<PostListItem>; typeof<SettingsModel>; typeof<UserMessage> typeof<EditTagMapModel>; typeof<EditUserModel>; typeof<LogOnModel>
typeof<ManagePermalinksModel>; typeof<ManageRevisionsModel>; typeof<PostDisplay>
typeof<PostListItem>; typeof<SettingsModel>; typeof<UserMessage>
// Framework types // Framework types
typeof<AntiforgeryTokenSet>; typeof<DateTime option>; typeof<int option>; typeof<KeyValuePair> typeof<AntiforgeryTokenSet>; typeof<DateTime option>; typeof<int option>; typeof<KeyValuePair>
typeof<MetaItem list>; typeof<string list>; typeof<string option>; typeof<TagMap list> typeof<MetaItem list>; typeof<string list>; typeof<string option>; typeof<TagMap list>

View File

@ -132,7 +132,7 @@ module Category =
open MyWebLog.Data open MyWebLog.Data
// GET /admin/categories // GET /admin/categories
let all : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let all : HttpHandler = fun next ctx -> task {
match! TemplateCache.get adminTheme "category-list-body" ctx.Data with match! TemplateCache.get adminTheme "category-list-body" ctx.Data with
| Ok catListTemplate -> | Ok catListTemplate ->
let! hash = let! hash =
@ -146,14 +146,14 @@ module Category =
} }
// GET /admin/categories/bare // GET /admin/categories/bare
let bare : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> let bare : HttpHandler = fun next ctx ->
hashForPage "Categories" hashForPage "Categories"
|> withAntiCsrf ctx |> withAntiCsrf ctx
|> adminBareView "category-list-body" next ctx |> adminBareView "category-list-body" next ctx
// GET /admin/category/{id}/edit // GET /admin/category/{id}/edit
let edit catId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let edit catId : HttpHandler = fun next ctx -> task {
let! result = task { let! result = task {
match catId with match catId with
| "new" -> return Some ("Add a New Category", { Category.empty with Id = CategoryId "new" }) | "new" -> return Some ("Add a New Category", { Category.empty with Id = CategoryId "new" })
@ -173,7 +173,7 @@ module Category =
} }
// POST /admin/category/save // POST /admin/category/save
let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let save : HttpHandler = fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
let! model = ctx.BindFormAsync<EditCategoryModel> () let! model = ctx.BindFormAsync<EditCategoryModel> ()
let category = let category =
@ -196,7 +196,7 @@ module Category =
} }
// POST /admin/category/{id}/delete // POST /admin/category/{id}/delete
let delete catId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let delete catId : HttpHandler = fun next ctx -> task {
let! result = ctx.Data.Category.Delete (CategoryId catId) ctx.WebLog.Id let! result = ctx.Data.Category.Delete (CategoryId catId) ctx.WebLog.Id
match result with match result with
| CategoryDeleted | CategoryDeleted
@ -217,8 +217,10 @@ module Category =
/// ~~~ REDIRECT RULES ~~~ /// ~~~ REDIRECT RULES ~~~
module RedirectRules = module RedirectRules =
// GET /admin/redirect-rules open Microsoft.AspNetCore.Http
let all : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
// GET /admin/settings/redirect-rules
let all : HttpHandler = fun next ctx -> task {
return! return!
hashForPage "Redirect Rules" hashForPage "Redirect Rules"
|> withAntiCsrf ctx |> withAntiCsrf ctx
@ -226,6 +228,82 @@ module RedirectRules =
|> adminView "redirect-list" next ctx |> adminView "redirect-list" next ctx
} }
// GET /admin/settings/redirect-rules/[index]
let edit idx : HttpHandler = fun next ctx -> task {
if idx = -1 then
return!
hashForPage "Add Redirect Rule"
|> addToHash "model" (EditRedirectRuleModel.fromRule -1 RedirectRule.empty)
|> withAntiCsrf ctx
|> adminBareView "redirect-edit" next ctx
else
let rules = ctx.WebLog.RedirectRules
if rules.Length < idx || idx < 0 then
return! Error.notFound next ctx
else
return!
hashForPage "Edit Redirect Rule"
|> addToHash "model" (EditRedirectRuleModel.fromRule idx (List.item idx rules))
|> withAntiCsrf ctx
|> adminBareView "redirect-edit" next ctx
}
/// Update the web log's redirect rules in the database, the request web log, and the web log cache
let private updateRedirectRules (ctx : HttpContext) webLog = backgroundTask {
do! ctx.Data.WebLog.UpdateRedirectRules webLog
ctx.Items["webLog"] <- webLog
WebLogCache.set webLog
}
// POST /admin/settings/redirect-rules/[index]
let save idx : HttpHandler = fun next ctx -> task {
let! model = ctx.BindFormAsync<EditRedirectRuleModel> ()
let isNew = idx = -1
let rules = ctx.WebLog.RedirectRules
let rule = model.UpdateRule (if isNew then RedirectRule.empty else List.item idx rules)
let newRules =
match isNew with
| true when model.InsertAtTop -> List.insertAt 0 rule rules
| true -> List.insertAt (rules.Length) rule rules
| false -> rules |> List.removeAt idx |> List.insertAt idx rule
do! updateRedirectRules ctx { ctx.WebLog with RedirectRules = newRules }
do! addMessage ctx { UserMessage.success with Message = "Redirect rule saved successfully" }
return! all next ctx
}
// POST /admin/settings/redirect-rules/[index]/up
let moveUp idx : HttpHandler = fun next ctx -> task {
if idx < 1 || idx >= ctx.WebLog.RedirectRules.Length then
return! Error.notFound next ctx
else
let toMove = List.item idx ctx.WebLog.RedirectRules
let newRules = ctx.WebLog.RedirectRules |> List.removeAt idx |> List.insertAt (idx - 1) toMove
do! updateRedirectRules ctx { ctx.WebLog with RedirectRules = newRules }
return! all next ctx
}
// POST /admin/settings/redirect-rules/[index]/down
let moveDown idx : HttpHandler = fun next ctx -> task {
if idx < 0 || idx >= ctx.WebLog.RedirectRules.Length - 1 then
return! Error.notFound next ctx
else
let toMove = List.item idx ctx.WebLog.RedirectRules
let newRules = ctx.WebLog.RedirectRules |> List.removeAt idx |> List.insertAt (idx + 1) toMove
do! updateRedirectRules ctx { ctx.WebLog with RedirectRules = newRules }
return! all next ctx
}
// POST /admin/settings/redirect-rules/[index]/delete
let delete idx : HttpHandler = fun next ctx -> task {
if idx < 0 || idx >= ctx.WebLog.RedirectRules.Length then
return! Error.notFound next ctx
else
let rules = ctx.WebLog.RedirectRules |> List.removeAt idx
do! updateRedirectRules ctx { ctx.WebLog with RedirectRules = rules }
do! addMessage ctx { UserMessage.success with Message = "Redirect rule deleted successfully" }
return! all next ctx
}
/// ~~~ TAG MAPPINGS ~~~ /// ~~~ TAG MAPPINGS ~~~
module TagMapping = module TagMapping =
@ -243,7 +321,7 @@ module TagMapping =
} }
// GET /admin/settings/tag-mappings // GET /admin/settings/tag-mappings
let all : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let all : HttpHandler = fun next ctx -> task {
let! hash = let! hash =
hashForPage "" hashForPage ""
|> withAntiCsrf ctx |> withAntiCsrf ctx
@ -252,7 +330,7 @@ module TagMapping =
} }
// GET /admin/settings/tag-mapping/{id}/edit // GET /admin/settings/tag-mapping/{id}/edit
let edit tagMapId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let edit tagMapId : HttpHandler = fun next ctx -> task {
let isNew = tagMapId = "new" let isNew = tagMapId = "new"
let tagMap = let tagMap =
if isNew then someTask { TagMap.empty with Id = TagMapId "new" } if isNew then someTask { TagMap.empty with Id = TagMapId "new" }
@ -268,7 +346,7 @@ module TagMapping =
} }
// POST /admin/settings/tag-mapping/save // POST /admin/settings/tag-mapping/save
let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let save : HttpHandler = fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
let! model = ctx.BindFormAsync<EditTagMapModel> () let! model = ctx.BindFormAsync<EditTagMapModel> ()
let tagMap = let tagMap =
@ -283,7 +361,7 @@ module TagMapping =
} }
// POST /admin/settings/tag-mapping/{id}/delete // POST /admin/settings/tag-mapping/{id}/delete
let delete tagMapId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let delete tagMapId : HttpHandler = fun next ctx -> task {
match! ctx.Data.TagMap.Delete (TagMapId tagMapId) ctx.WebLog.Id with match! ctx.Data.TagMap.Delete (TagMapId tagMapId) ctx.WebLog.Id with
| true -> do! addMessage ctx { UserMessage.success with Message = "Tag mapping deleted successfully" } | true -> do! addMessage ctx { UserMessage.success with Message = "Tag mapping deleted successfully" }
| false -> do! addMessage ctx { UserMessage.error with Message = "Tag mapping not found; nothing deleted" } | false -> do! addMessage ctx { UserMessage.error with Message = "Tag mapping not found; nothing deleted" }
@ -460,7 +538,7 @@ module WebLog =
open System.IO open System.IO
// GET /admin/settings // GET /admin/settings
let settings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let settings : HttpHandler = fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
match! TemplateCache.get adminTheme "user-list-body" data with match! TemplateCache.get adminTheme "user-list-body" data with
| Ok userTemplate -> | Ok userTemplate ->
@ -508,7 +586,7 @@ module WebLog =
} }
// POST /admin/settings // POST /admin/settings
let saveSettings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let saveSettings : HttpHandler = fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
let! model = ctx.BindFormAsync<SettingsModel> () let! model = ctx.BindFormAsync<SettingsModel> ()
match! data.WebLog.FindById ctx.WebLog.Id with match! data.WebLog.FindById ctx.WebLog.Id with

View File

@ -107,7 +107,7 @@ let router : HttpHandler = choose [
subRoute "/admin" (requireUser >=> choose [ subRoute "/admin" (requireUser >=> choose [
GET_HEAD >=> choose [ GET_HEAD >=> choose [
route "/administration" >=> Admin.Dashboard.admin route "/administration" >=> Admin.Dashboard.admin
subRoute "/categor" (choose [ subRoute "/categor" (requireAccess WebLogAdmin >=> choose [
route "ies" >=> Admin.Category.all route "ies" >=> Admin.Category.all
route "ies/bare" >=> Admin.Category.bare route "ies/bare" >=> Admin.Category.bare
routef "y/%s/edit" Admin.Category.edit routef "y/%s/edit" Admin.Category.edit
@ -130,20 +130,21 @@ let router : HttpHandler = choose [
routef "/%s/revision/%s/preview" Post.previewRevision routef "/%s/revision/%s/preview" Post.previewRevision
routef "/%s/revisions" Post.editRevisions routef "/%s/revisions" Post.editRevisions
]) ])
subRoute "/redirect-rules" (choose [ subRoute "/settings" (requireAccess WebLogAdmin >=> choose [
route "" >=> Admin.RedirectRules.all route "" >=> Admin.WebLog.settings
]) routef "/rss/%s/edit" Feed.editCustomFeed
subRoute "/settings" (choose [ subRoute "/redirect-rules" (choose [
route "" >=> Admin.WebLog.settings route "" >=> Admin.RedirectRules.all
routef "/rss/%s/edit" Feed.editCustomFeed routef "/%i" Admin.RedirectRules.edit
subRoute "/user" (choose [
route "s" >=> User.all
routef "/%s/edit" User.edit
]) ])
subRoute "/tag-mapping" (choose [ subRoute "/tag-mapping" (choose [
route "s" >=> Admin.TagMapping.all route "s" >=> Admin.TagMapping.all
routef "/%s/edit" Admin.TagMapping.edit routef "/%s/edit" Admin.TagMapping.edit
]) ])
subRoute "/user" (choose [
route "s" >=> User.all
routef "/%s/edit" User.edit
])
]) ])
subRoute "/theme" (choose [ subRoute "/theme" (choose [
route "/list" >=> Admin.Theme.all route "/list" >=> Admin.Theme.all
@ -159,7 +160,7 @@ let router : HttpHandler = choose [
routef "/theme/%s/refresh" Admin.Cache.refreshTheme routef "/theme/%s/refresh" Admin.Cache.refreshTheme
routef "/web-log/%s/refresh" Admin.Cache.refreshWebLog routef "/web-log/%s/refresh" Admin.Cache.refreshWebLog
]) ])
subRoute "/category" (choose [ subRoute "/category" (requireAccess WebLogAdmin >=> choose [
route "/save" >=> Admin.Category.save route "/save" >=> Admin.Category.save
routef "/%s/delete" Admin.Category.delete routef "/%s/delete" Admin.Category.delete
]) ])
@ -180,13 +181,19 @@ let router : HttpHandler = choose [
routef "/%s/revision/%s/restore" Post.restoreRevision routef "/%s/revision/%s/restore" Post.restoreRevision
routef "/%s/revisions/purge" Post.purgeRevisions routef "/%s/revisions/purge" Post.purgeRevisions
]) ])
subRoute "/settings" (choose [ subRoute "/settings" (requireAccess WebLogAdmin >=> choose [
route "" >=> Admin.WebLog.saveSettings route "" >=> Admin.WebLog.saveSettings
subRoute "/rss" (choose [ subRoute "/rss" (choose [
route "" >=> Feed.saveSettings route "" >=> Feed.saveSettings
route "/save" >=> Feed.saveCustomFeed route "/save" >=> Feed.saveCustomFeed
routef "/%s/delete" Feed.deleteCustomFeed routef "/%s/delete" Feed.deleteCustomFeed
]) ])
subRoute "/redirect-rules" (choose [
routef "/%i" Admin.RedirectRules.save
routef "/%i/up" Admin.RedirectRules.moveUp
routef "/%i/down" Admin.RedirectRules.moveDown
routef "/%i/delete" Admin.RedirectRules.delete
])
subRoute "/tag-mapping" (choose [ subRoute "/tag-mapping" (choose [
route "/save" >=> Admin.TagMapping.save route "/save" >=> Admin.TagMapping.save
routef "/%s/delete" Admin.TagMapping.delete routef "/%s/delete" Admin.TagMapping.delete

View File

@ -95,7 +95,7 @@ open Giraffe.Htmx
let private goAway : HttpHandler = RequestErrors.BAD_REQUEST "really?" let private goAway : HttpHandler = RequestErrors.BAD_REQUEST "really?"
// GET /admin/settings/users // GET /admin/settings/users
let all : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let all : HttpHandler = fun next ctx -> task {
let! users = ctx.Data.WebLogUser.FindByWebLog ctx.WebLog.Id let! users = ctx.Data.WebLogUser.FindByWebLog ctx.WebLog.Id
return! return!
hashForPage "User Administration" hashForPage "User Administration"
@ -119,7 +119,7 @@ let private showEdit (model : EditUserModel) : HttpHandler = fun next ctx ->
|> adminBareView "user-edit" next ctx |> adminBareView "user-edit" next ctx
// GET /admin/settings/user/{id}/edit // GET /admin/settings/user/{id}/edit
let edit usrId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let edit usrId : HttpHandler = fun next ctx -> task {
let isNew = usrId = "new" let isNew = usrId = "new"
let userId = WebLogUserId usrId let userId = WebLogUserId usrId
let tryUser = let tryUser =
@ -131,7 +131,7 @@ let edit usrId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> tas
} }
// POST /admin/settings/user/{id}/delete // POST /admin/settings/user/{id}/delete
let delete userId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let delete userId : HttpHandler = fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
match! data.WebLogUser.FindById (WebLogUserId userId) ctx.WebLog.Id with match! data.WebLogUser.FindById (WebLogUserId userId) ctx.WebLog.Id with
| Some user -> | Some user ->

View File

@ -54,7 +54,7 @@
<span class="text-muted"> &bull; </span> <span class="text-muted"> &bull; </span>
{%- assign post_del_link = "admin/post/" | append: post.id | append: "/delete" | relative_link -%} {%- assign post_del_link = "admin/post/" | append: post.id | append: "/delete" | relative_link -%}
<a href="{{ post_del_link }}" hx-post="{{ post_del_link }}" class="text-danger" <a href="{{ post_del_link }}" hx-post="{{ post_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the page &ldquo;{{ post.title | strip_html | escape }}&rdquo;? This action cannot be undone."> hx-confirm="Are you sure you want to delete the post &ldquo;{{ post.title | strip_html | escape }}&rdquo;? This action cannot be undone.">
Delete Delete
</a> </a>
{% endif %} {% endif %}

View File

@ -0,0 +1,48 @@
<h3>{% if model.rule_id < 0 %}Add{% else %}Edit{% endif %} Redirect Rule</h3>
{%- assign post_url = "admin/settings/redirect-rules/" | append: model.rule_id | relative_link -%}
<form action="{{ post_url }}" hx-post="{{ post_url }}" hx-target="body" method="POST" class="container">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<input type="hidden" name="RuleId" value="{{ model.rule_id }}">
<div class="row">
<div class="col-12 col-lg-5 mb-3">
<div class="form-floating">
<input type="text" name="From" id="from" class="form-control" placeholder="From local URL/pattern" autofocus
required value="{{ model.from | escape }}">
<label for="from">From</label>
</div>
</div>
<div class="col-12 col-lg-5 mb-3">
<div class="form-floating">
<input type="text" name="To" id="to" class="form-control" placeholder="To URL/pattern" required
value="{{ model.to | escape }}">
<label for="from">To</label>
</div>
</div>
<div class="col-12 col-lg-2 mb-3">
<div class="form-check form-switch">
<input type="checkbox" name="IsRegex" id="isRegex" class="form-check-input" value="true"
{%- if model.is_regex %} checked="checked"{% endif %}>
<label for="isRegex">Use RegEx</label>
</div>
</div>
</div>
{% if model.rule_id < 0 %}
<div class="row mb-3">
<div class="col-12 text-center">
<label>Add Rule</label>
<div class="btn-group btn-group-sm" role="group" aria-label="New rule placement button group">
<input type="radio" name="InsertAtTop" id="insert_top" class="btn-check" value="true">
<label class="btn btn-sm btn-outline-secondary" for="insert_top">Top</label>
<input type="radio" name="InsertAtTop" id="insert_bottom" class="btn-check" value="false" checked="checked">
<label class="btn btn-sm btn-outline-secondary" for="insert_bottom">Bottom</label>
</div>
</div>
</div>
{% endif %}
<div class="row mb-3">
<div class="col text-center">
<button type="submit" class="btn btn-sm btn-primary">Save Changes</button>
<a href="{{ "admin/settings/redirect-rules" | relative_link }}" class="btn btn-sm btn-secondary ms-3">Cancel</a>
</div>
</div>
</form>

View File

@ -1,7 +1,17 @@
<h2 class="my-3">Redirect Rules</h2> <h2 class="my-3">{{ page_title }}</h2>
<article> <article>
<a href="{{ "admin/settings" | relative_link }}">&laquo; Back to Settings</a> <p class="mb-3">
<a href="{{ "admin/settings" | relative_link }}">&laquo; Back to Settings</a>
</p>
<div class="container"> <div class="container">
<div class="row">
<div class="col">
<a href="{{ "admin/settings/redirect-rules/-1" | relative_link }}" class="btn btn-primary btn-sm mb-3"
hx-target="#redir_new">
Add Redirect Rule
</a>
</div>
</div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
{%- assign redir_count = redirections | size -%} {%- assign redir_count = redirections | size -%}
@ -13,42 +23,45 @@
<div class="col">RegEx?</div> <div class="col">RegEx?</div>
</div> </div>
</div> </div>
<form method="post" class="container"> <div class="row mwl-table-detail" id="redir_new"></div>
<form method="post" class="container" hx-target="body">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<div class="row mwl-table-detail" id="redir_new"></div> {% for redir in redirections -%}
{% for redir in redirections -%} {%- assign redir_id = "redir_" | append: forloop.index0 -%}
{%- assign map_id = mapping_ids | value: map.tag -%} <div class="row mwl-table-detail" id="{{ redir_id }}">
<div class="row mwl-table-detail" id="redir_{{ forloop.index0 }}"> <div class="col no-wrap">
<div class="col no-wrap"> {{ redir.from }}<br>
{{ redir.from }}<br> <small>
<small> {%- assign redir_url = "admin/settings/redirect-rules/" | append: forloop.index0 -%}
{%- assign redir_url = "admin/settings/redirect-rules/" | append: forloop.index0 -%} <a href="{{ redir_url | relative_link }}" hx-target="#{{ redir_id }}"
<a href="{{ redir_url | relative_link }}" hx-target="#tag_{{ forloop.index0 }}" hx-swap="innerHTML show:#{{ redir_id }}:top">
hx-swap="innerHTML show:#redir_{{ forloop.index0 }}:top"> Edit
Edit </a>
</a> {% unless forloop.first %}
{% unless forloop.first %}
<span class="text-muted"> &bull; </span>
{%- assign move_up = redir_url | append: "/up" | relative_link -%}
<a href="{{ move_up }}" hx-post="{{ move_up }}">Move Up</a>
{% endunless %}
{% unless forloop.last %}
<span class="text-muted"> &bull; </span>
{%- assign move_down = redir_url | append: "/down" | relative_link -%}
<a href="{{ move_down }}" hx-post="{{ move_down }}">Move Down</a>
{% endunless %}
<span class="text-muted"> &bull; </span> <span class="text-muted"> &bull; </span>
{%- assign del_url = redir_url | append: "/delete" | relative_link -%} {%- assign move_up = redir_url | append: "/up" | relative_link -%}
<a href="{{ del_url }}" hx-post="{{ del_url }}" class="text-danger">Delete</a> <a href="{{ move_up }}" hx-post="{{ move_up }}">Move Up</a>
</small> {% endunless %}
</div> {% unless forloop.last %}
<div class="col">{{ redir.to }}</div> <span class="text-muted"> &bull; </span>
<div class="col">{% if redir.is_regex %}Yes{% else %}No{% endif %}</div> {%- assign move_down = redir_url | append: "/down" | relative_link -%}
<a href="{{ move_down }}" hx-post="{{ move_down }}">Move Down</a>
{% endunless %}
<span class="text-muted"> &bull; </span>
{%- assign del_url = redir_url | append: "/delete" | relative_link -%}
<a href="{{ del_url }}" hx-post="{{ del_url }}" class="text-danger"
hx-confirm="Are you sure you want to delete this redirect rule?">
Delete
</a>
</small>
</div> </div>
{%- endfor %} <div class="col">{{ redir.to }}</div>
<div class="col">{% if redir.is_regex %}Yes{% else %}No{% endif %}</div>
</div>
{%- endfor %}
</form> </form>
{%- else -%} {%- else -%}
<div id="tag_new"> <div id="redir_new">
<p class="text-muted text-center fst-italic">This web log has no redirect rules defined</p> <p class="text-muted text-center fst-italic">This web log has no redirect rules defined</p>
</div> </div>
{%- endif %} {%- endif %}

View File

@ -3,7 +3,7 @@
<p class="text-muted"> <p class="text-muted">
Go to: <a href="#users">Users</a> &bull; <a href="#rss-settings">RSS Settings</a> &bull; Go to: <a href="#users">Users</a> &bull; <a href="#rss-settings">RSS Settings</a> &bull;
<a href="#tag-mappings">Tag Mappings</a> &bull; <a href="#tag-mappings">Tag Mappings</a> &bull;
<a href="{{ "admin/redirect-rules" | relative_link }}">Redirect Rules</a> <a href="{{ "admin/settings/redirect-rules" | relative_link }}">Redirect Rules</a>
</p> </p>
<fieldset class="container mb-3"> <fieldset class="container mb-3">
<legend>Web Log Settings</legend> <legend>Web Log Settings</legend>