Add group, WIP on role (#17)

This commit is contained in:
2026-08-01 22:45:18 -04:00
parent 2241742ab8
commit e7261f6019
13 changed files with 539 additions and 72 deletions
+25 -7
View File
@@ -84,6 +84,14 @@ module Json =
override _.ReadJson(reader: JsonReader, _: Type, _: OpenGraphType, _: bool, _: JsonSerializer) = override _.ReadJson(reader: JsonReader, _: Type, _: OpenGraphType, _: bool, _: JsonSerializer) =
(string >> OpenGraphType.Parse) reader.Value (string >> OpenGraphType.Parse) reader.Value
/// <summary>Converter for the <see cref="PageId" /> type</summary>
type PageIdConverter() =
inherit JsonConverter<PageId>()
override _.WriteJson(writer: JsonWriter, value: PageId, _: JsonSerializer) =
writer.WriteValue(string value)
override _.ReadJson(reader: JsonReader, _: Type, _: PageId, _: bool, _: JsonSerializer) =
(string >> PageId) reader.Value
/// <summary>Converter for the <see cref="Permalink" /> type</summary> /// <summary>Converter for the <see cref="Permalink" /> type</summary>
type PermalinkConverter() = type PermalinkConverter() =
inherit JsonConverter<Permalink>() inherit JsonConverter<Permalink>()
@@ -92,13 +100,21 @@ module Json =
override _.ReadJson(reader: JsonReader, _: Type, _: Permalink, _: bool, _: JsonSerializer) = override _.ReadJson(reader: JsonReader, _: Type, _: Permalink, _: bool, _: JsonSerializer) =
(string >> Permalink) reader.Value (string >> Permalink) reader.Value
/// <summary>Converter for the <see cref="PageId" /> type</summary> /// <summary>Converter for the <see cref="PersonGroup" /> type</summary>
type PageIdConverter() = type PersonGroupConverter() =
inherit JsonConverter<PageId>() inherit JsonConverter<PersonGroup>()
override _.WriteJson(writer: JsonWriter, value: PageId, _: JsonSerializer) = override _.WriteJson(writer: JsonWriter, value: PersonGroup, _: JsonSerializer) =
writer.WriteValue(string value) writer.WriteValue(string value)
override _.ReadJson(reader: JsonReader, _: Type, _: PageId, _: bool, _: JsonSerializer) = override _.ReadJson(reader: JsonReader, _: Type, _: PersonGroup, _: bool, _: JsonSerializer) =
(string >> PageId) reader.Value (string >> PersonGroup.Parse) reader.Value
/// <summary>Converter for the <see cref="PersonRole" /> type</summary>
type PersonRoleConverter() =
inherit JsonConverter<PersonRole>()
override _.WriteJson(writer: JsonWriter, value: PersonRole, _: JsonSerializer) =
writer.WriteValue(string value)
override _.ReadJson(reader: JsonReader, _: Type, _: PersonRole, _: bool, _: JsonSerializer) =
(string >> PersonRole.Parse) reader.Value
/// <summary>Converter for the <see cref="PodcastMedium" /> type</summary> /// <summary>Converter for the <see cref="PodcastMedium" /> type</summary>
type PodcastMediumConverter() = type PodcastMediumConverter() =
@@ -180,8 +196,10 @@ module Json =
ExplicitRatingConverter() ExplicitRatingConverter()
MarkupTextConverter() MarkupTextConverter()
OpenGraphTypeConverter() OpenGraphTypeConverter()
PermalinkConverter()
PageIdConverter() PageIdConverter()
PermalinkConverter()
PersonGroupConverter()
PersonRoleConverter()
PodcastMediumConverter() PodcastMediumConverter()
PostIdConverter() PostIdConverter()
TagMapIdConverter() TagMapIdConverter()
+1 -1
View File
@@ -457,7 +457,7 @@ type WebLogUser = {
PreferredName = "" PreferredName = ""
PasswordHash = "" PasswordHash = ""
Url = None Url = None
AccessLevel = Author AccessLevel = ContentAuthor
CreatedOn = Noda.epoch CreatedOn = Noda.epoch
LastSeenOn = None } LastSeenOn = None }
+333 -4
View File
@@ -61,18 +61,22 @@ module Noda =
/// <summary>A user's access level</summary> /// <summary>A user's access level</summary>
[<Struct>] [<Struct>]
type AccessLevel = type AccessLevel =
/// <summary>The user may create and publish posts and edit the ones they have created</summary> /// <summary>The user may create and publish posts and edit the ones they have created</summary>
| Author | ContentAuthor
/// <summary>The user may edit posts they did not create, but may not delete them</summary> /// <summary>The user may edit posts they did not create, but may not delete them</summary>
| Editor | Editor
/// <summary>The user may delete posts and configure web log settings</summary> /// <summary>The user may delete posts and configure web log settings</summary>
| WebLogAdmin | WebLogAdmin
/// <summary>The user may manage themes (which affects all web logs for an installation)</summary> /// <summary>The user may manage themes (which affects all web logs for an installation)</summary>
| Administrator | Administrator
/// <summary>Weights applied to each access level</summary> /// <summary>Weights applied to each access level</summary>
static member private Weights = static member private Weights =
[ Author, 10 [ ContentAuthor, 10
Editor, 20 Editor, 20
WebLogAdmin, 30 WebLogAdmin, 30
Administrator, 40 ] Administrator, 40 ]
@@ -84,7 +88,7 @@ type AccessLevel =
/// <exception cref="InvalidArgumentException">If the string is not valid</exception> /// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse level = static member Parse level =
match level with match level with
| "Author" -> Author | "Author" -> ContentAuthor
| "Editor" -> Editor | "Editor" -> Editor
| "WebLogAdmin" -> WebLogAdmin | "WebLogAdmin" -> WebLogAdmin
| "Administrator" -> Administrator | "Administrator" -> Administrator
@@ -93,7 +97,7 @@ type AccessLevel =
/// <inheritdoc /> /// <inheritdoc />
override this.ToString() = override this.ToString() =
match this with match this with
| Author -> "Author" | ContentAuthor -> "Author"
| Editor -> "Editor" | Editor -> "Editor"
| WebLogAdmin -> "WebLogAdmin" | WebLogAdmin -> "WebLogAdmin"
| Administrator -> "Administrator" | Administrator -> "Administrator"
@@ -269,6 +273,331 @@ type Chapter = {
EndTime = None EndTime = None
Location = None } Location = None }
/// <summary>A group to which a podcast person can belong</summary>
type PersonGroup =
| Administration
| AudioPostProduction
| AudioProduction
| Cast
| Community
| CreativeDirection
| Misc
| VideoPostProduction
| VideoProduction
| Visuals
| Writing
/// <summary>Parse a string into a group</summary>
/// <param name="group">The string representation of the group</param>
/// <returns>The <c>PersonGroup</c> instance parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse group =
match group with
| "Administration" -> Administration
| "Audio Post-Production" -> AudioPostProduction
| "Audio Production" -> AudioProduction
| "Cast" -> Cast
| "Community" -> Community
| "Creative Direction" -> CreativeDirection
| "Misc." -> Misc
| "Video Post-Production" -> VideoPostProduction
| "Video Production" -> VideoProduction
| "Visuals" -> Visuals
| "Writing" -> Writing
| _ -> invalidArg (nameof group) $"{group} is not a valid group"
/// <inheritdoc />
override this.ToString() =
match this with
| Administration -> "Administration"
| AudioPostProduction -> "Audio Post-Production"
| AudioProduction -> "Audio Production"
| Cast -> "Cast"
| Community -> "Community"
| CreativeDirection -> "Creative Direction"
| Misc -> "Misc."
| VideoPostProduction -> "Video Post-Production"
| VideoProduction -> "Video Production"
| Visuals -> "Visuals"
| Writing -> "Writing"
/// <summary>The role a person plays for a podcast or a podcast episode</summary>
type PersonRole =
| Announcer
| AssistantCamera
| AssistantDirector
| AssistantEditor
| AssociateProducer
| AudioEditor
| AudioEngineer
| Author
| BookingCoordinator
| CameraGrip
| CameraOperator
| CoHost
| Composer
| Consultant
| ContentManager
| CoverArtDesigner
| CoWriter
| CreativeDirector
| DevelopmentProducer
| Director
| EditorialDirector
| ExecutiveProducer
| FactChecker
| FoleyArtist
| GraphicDesigner
| Guest
| GuestHost
| GuestWriter
| Host
| Intern
| LightingDesigner
| Logger
| ManagingEditor
| MarketingManager
| MusicContributor
| MusicProduction
| Narrator
| PostProductionEngineer
| Producer
| ProductionAssistant
| ProductionCoordinator
| RemoteRecordingEngineer
| Reporter
| Researcher
| SalesManager
| SalesRepresentative
| ScriptCoordinator
| ScriptEditor
| SeniorProducer
| SocialMediaManager
| Songwriter
| SoundDesigner
| StoryEditor
| StudioCoordinator
| TechnicalDirector
| TechnicalManager
| ThemeMusic
| Transcriber
| Translator
| VideoEditor
| VoiceActor
| Writer
| WritingEditor
/// <summary>Parse a string into a role</summary>
/// <param name="role">The string representation of the role</param>
/// <returns>The <c>PersonRole</c> instance parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse role =
match role with
| "Announcer" -> Announcer
| "Assistant Camera" -> AssistantCamera
| "Assistant Director" -> AssistantDirector
| "Assistant Editor" -> AssistantEditor
| "Associate Producer" -> AssociateProducer
| "Audio Editor" -> AudioEditor
| "Audio Engineer" -> AudioEngineer
| "Author" -> Author
| "Booking Coordinator" -> BookingCoordinator
| "Camera Grip" -> CameraGrip
| "Camera Operator" -> CameraOperator
| "Co-Host" -> CoHost
| "Composer" -> Composer
| "Consultant" -> Consultant
| "Content Manager" -> ContentManager
| "Cover Art Designer" -> CoverArtDesigner
| "Co-Writer" -> CoWriter
| "Creative Director" -> CreativeDirector
| "Development Producer" -> DevelopmentProducer
| "Director" -> Director
| "Editorial Director" -> EditorialDirector
| "Executive Producer" -> ExecutiveProducer
| "Fact Checker" -> FactChecker
| "Foley Artist" -> FoleyArtist
| "Graphic Designer" -> GraphicDesigner
| "Guest" -> Guest
| "Guest Host" -> GuestHost
| "Guest Writer" -> GuestWriter
| "Host" -> Host
| "Intern" -> Intern
| "Lighting Designer" -> LightingDesigner
| "Logger" -> Logger
| "Managing Editor" -> ManagingEditor
| "Marketing Manager" -> MarketingManager
| "Music Contributor" -> MusicContributor
| "Music Production" -> MusicProduction
| "Narrator" -> Narrator
| "Post Production Engineer" -> PostProductionEngineer
| "Producer" -> Producer
| "Production Assistant" -> ProductionAssistant
| "Production Coordinator" -> ProductionCoordinator
| "Remote Recording Engineer" -> RemoteRecordingEngineer
| "Reporter" -> Reporter
| "Researcher" -> Researcher
| "Sales Manager" -> SalesManager
| "Sales Representative" -> SalesRepresentative
| "Script Coordinator" -> ScriptCoordinator
| "Script Editor" -> ScriptEditor
| "Senior Producer" -> SeniorProducer
| "Social Media Manager" -> SocialMediaManager
| "Songwriter" -> Songwriter
| "Sound Designer" -> SoundDesigner
| "Story Editor" -> StoryEditor
| "Studio Coordinator" -> StudioCoordinator
| "Technical Director" -> TechnicalDirector
| "Technical Manager" -> TechnicalManager
| "Theme Music" -> ThemeMusic
| "Transcriber" -> Transcriber
| "Translator" -> Translator
| "Editor (Video)" -> VideoEditor
| "Voice Actor" -> VoiceActor
| "Writer" -> Writer
| "Editor (Writing)" -> WritingEditor
| _ -> invalidArg (nameof role) $"{role} is not a valid role"
/// <inheritdoc />
override this.ToString() =
match this with
| Announcer -> "Announcer"
| AssistantCamera -> "Assistant Camera"
| AssistantDirector -> "Assistant Director"
| AssistantEditor -> "Assistant Editor"
| AssociateProducer -> "Associate Producer"
| AudioEditor -> "Audio Editor"
| AudioEngineer -> "Audio Engineer"
| Author -> "Author"
| BookingCoordinator -> "Booking Coordinator"
| CameraGrip -> "Camera Grip"
| CameraOperator -> "Camera Operator"
| CoHost -> "Co-Host"
| Composer -> "Composer"
| Consultant -> "Consultant"
| ContentManager -> "Content Manager"
| CoverArtDesigner -> "Cover Art Designer"
| CoWriter -> "Co-Writer"
| CreativeDirector -> "Creative Director"
| DevelopmentProducer -> "Development Producer"
| Director -> "Director"
| EditorialDirector -> "Editorial Director"
| ExecutiveProducer -> "Executive Producer"
| FactChecker -> "Fact Checker"
| FoleyArtist -> "Foley Artist"
| GraphicDesigner -> "Graphic Designer"
| Guest -> "Guest"
| GuestHost -> "Guest Host"
| GuestWriter -> "Guest Writer"
| Host -> "Host"
| Intern -> "Intern"
| LightingDesigner -> "Lighting Designer"
| Logger -> "Logger"
| ManagingEditor -> "Managing Editor"
| MarketingManager -> "Marketing Manager"
| MusicContributor -> "Music Contributor"
| MusicProduction -> "Music Production"
| Narrator -> "Narrator"
| PostProductionEngineer -> "Post Production Engineer"
| Producer -> "Producer"
| ProductionAssistant -> "Production Assistant"
| ProductionCoordinator -> "Production Coordinator"
| RemoteRecordingEngineer -> "Remote Recording Engineer"
| Reporter -> "Reporter"
| Researcher -> "Researcher"
| SalesManager -> "Sales Manager"
| SalesRepresentative -> "Sales Representative"
| ScriptCoordinator -> "Script Coordinator"
| ScriptEditor -> "Script Editor"
| SeniorProducer -> "Senior Producer"
| SocialMediaManager -> "Social Media Manager"
| Songwriter -> "Songwriter"
| SoundDesigner -> "Sound Designer"
| StoryEditor -> "Story Editor"
| StudioCoordinator -> "Studio Coordinator"
| TechnicalDirector -> "Technical Director"
| TechnicalManager -> "Technical Manager"
| ThemeMusic -> "Theme Music"
| Transcriber -> "Transcriber"
| Translator -> "Translator"
| VideoEditor -> "Editor (Video)"
| VoiceActor -> "Voice Actor"
| Writer -> "Writer"
| WritingEditor -> "Editor (Writing)"
/// <summary>Get the display name of the role</summary>
/// <remarks>Both Video Production and Writing have an "Editor"; this is how it should be rendered in RSS feeds and
/// in the UI</remarks>
member this.ToDisplay() =
match this with VideoEditor | WritingEditor -> "Editor" | _ -> string this
/// <summary>Role / group cross-reference</summary>
static member GroupXref =
[ Announcer, Cast
AssistantCamera, VideoProduction
AssistantDirector, CreativeDirection
AssistantEditor, VideoPostProduction
AssociateProducer, CreativeDirection
AudioEditor, AudioPostProduction
AudioEngineer, AudioProduction
Author, Writing
BookingCoordinator, Administration
CameraGrip, VideoProduction
CameraOperator, VideoProduction
CoHost, Cast
Composer, AudioPostProduction
ContentManager, Administration
Consultant, Misc
CoverArtDesigner, Visuals
CoWriter, Writing
CreativeDirector, CreativeDirection
DevelopmentProducer, CreativeDirection
Director, CreativeDirection
EditorialDirector, Writing
ExecutiveProducer, CreativeDirection
FactChecker, Writing
FoleyArtist, AudioPostProduction
GraphicDesigner, Visuals
Guest, Cast
GuestHost, Cast
GuestWriter, Writing
Host, Cast
Intern, Misc
LightingDesigner, VideoProduction
Logger, Writing
ManagingEditor, Writing
MarketingManager, Administration
MusicContributor, AudioPostProduction
MusicProduction, AudioPostProduction
Narrator, Cast
PostProductionEngineer, AudioProduction
Producer, CreativeDirection
ProductionAssistant, Administration
ProductionCoordinator, Administration
RemoteRecordingEngineer, AudioProduction
Reporter, Cast
Researcher, Writing
SalesManager, Administration
SalesRepresentative, Administration
ScriptCoordinator, Writing
ScriptEditor, Writing
SeniorProducer, CreativeDirection
SocialMediaManager, Community
Songwriter, Writing
SoundDesigner, AudioPostProduction
StoryEditor, Writing
StudioCoordinator, AudioProduction
TechnicalDirector, AudioProduction
TechnicalManager, AudioProduction
ThemeMusic, AudioPostProduction
Transcriber, Writing
Translator, Writing
VideoEditor, VideoPostProduction
VoiceActor, Cast
Writer, Writing
WritingEditor, Writing ]
open NodaTime.Text open NodaTime.Text
+41 -9
View File
@@ -136,6 +136,20 @@ let openGraphTypeConverterTests = testList "OpenGraphTypeConverter" [
} }
] ]
/// Unit tests for the PageIdConverter type
let pageIdConverterTests = testList "PageIdConverter" [
let opts = JsonSerializerSettings()
opts.Converters.Add(PageIdConverter())
test "succeeds when serializing" {
let after = JsonConvert.SerializeObject(PageId "test-page", opts)
Expect.equal after "\"test-page\"" "Page ID serialized incorrectly"
}
test "succeeds when deserializing" {
let after = JsonConvert.DeserializeObject<PageId>("\"page-test\"", opts)
Expect.equal after (PageId "page-test") "Page ID deserialized incorrectly"
}
]
/// Unit tests for the PermalinkConverter type /// Unit tests for the PermalinkConverter type
let permalinkConverterTests = testList "PermalinkConverter" [ let permalinkConverterTests = testList "PermalinkConverter" [
let opts = JsonSerializerSettings() let opts = JsonSerializerSettings()
@@ -150,17 +164,31 @@ let permalinkConverterTests = testList "PermalinkConverter" [
} }
] ]
/// Unit tests for the PageIdConverter type /// Unit tests for the PersonGroupConverter type
let pageIdConverterTests = testList "PageIdConverter" [ let personGroupConverterTests = testList "PersonGroupConverter" [
let opts = JsonSerializerSettings() let opts = JsonSerializerSettings()
opts.Converters.Add(PageIdConverter()) opts.Converters.Add(PersonGroupConverter())
test "succeeds when serializing" { test "succeeds when serializing" {
let after = JsonConvert.SerializeObject(PageId "test-page", opts) let after = JsonConvert.SerializeObject(Visuals, opts)
Expect.equal after "\"test-page\"" "Page ID serialized incorrectly" Expect.equal after "\"Visuals\"" "PersonGroup serialized incorrectly"
} }
test "succeeds when deserializing" { test "succeeds when deserializing" {
let after = JsonConvert.DeserializeObject<PageId>("\"page-test\"", opts) let after = JsonConvert.DeserializeObject<PersonGroup>("\"Cast\"", opts)
Expect.equal after (PageId "page-test") "Page ID deserialized incorrectly" Expect.equal after Cast "PersonGroup deserialized incorrectly"
}
]
/// Unit tests for the PersonRoleConverter type
let personRoleConverterTests = testList "PersonRoleConverter" [
let opts = JsonSerializerSettings()
opts.Converters.Add(PersonRoleConverter())
test "succeeds when serializing" {
let after = JsonConvert.SerializeObject(Host, opts)
Expect.equal after "\"Host\"" "PersonRole serialized incorrectly"
}
test "succeeds when deserializing" {
let after = JsonConvert.DeserializeObject<PersonRole>("\"Co-Host\"", opts)
Expect.equal after CoHost "PersonRole deserialized incorrectly"
} }
] ]
@@ -291,8 +319,10 @@ let configureTests = test "Json.configure succeeds" {
Expect.hasCountOf ser.Converters 1u (has typeof<ExplicitRatingConverter>) "Explicit rating converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<ExplicitRatingConverter>) "Explicit rating converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<MarkupTextConverter>) "Markup text converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<MarkupTextConverter>) "Markup text converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<OpenGraphTypeConverter>) "OpenGraph type converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<OpenGraphTypeConverter>) "OpenGraph type converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<PermalinkConverter>) "Permalink converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<PageIdConverter>) "Page ID converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<PageIdConverter>) "Page ID converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<PermalinkConverter>) "Permalink converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<PersonGroupConverter>) "PersonGroup converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<PersonRoleConverter>) "PersonRole converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<PodcastMediumConverter>) "Podcast medium converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<PodcastMediumConverter>) "Podcast medium converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<PostIdConverter>) "Post ID converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<PostIdConverter>) "Post ID converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<TagMapIdConverter>) "Tag map ID converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<TagMapIdConverter>) "Tag map ID converter not found"
@@ -318,8 +348,10 @@ let all = testList "Converters" [
explicitRatingConverterTests explicitRatingConverterTests
markupTextConverterTests markupTextConverterTests
openGraphTypeConverterTests openGraphTypeConverterTests
permalinkConverterTests
pageIdConverterTests pageIdConverterTests
permalinkConverterTests
personGroupConverterTests
personRoleConverterTests
podcastMediumConverterTests podcastMediumConverterTests
postIdConverterTests postIdConverterTests
tagMapIdConverterTests tagMapIdConverterTests
@@ -33,7 +33,7 @@ let ``Add succeeds`` (data: IData) = task {
PreferredName = "n00b" PreferredName = "n00b"
PasswordHash = "hashed-password" PasswordHash = "hashed-password"
Url = Some "https://example.com/~new" Url = Some "https://example.com/~new"
AccessLevel = Author AccessLevel = ContentAuthor
CreatedOn = Noda.epoch + Duration.FromDays 365 CreatedOn = Noda.epoch + Duration.FromDays 365
LastSeenOn = None } LastSeenOn = None }
let! user = data.WebLogUser.FindById newId rootId let! user = data.WebLogUser.FindById newId rootId
@@ -47,7 +47,7 @@ let ``Add succeeds`` (data: IData) = task {
Expect.equal it.PreferredName "n00b" "Preferred name is incorrect" Expect.equal it.PreferredName "n00b" "Preferred name is incorrect"
Expect.equal it.PasswordHash "hashed-password" "Password hash is incorrect" Expect.equal it.PasswordHash "hashed-password" "Password hash is incorrect"
Expect.equal it.Url (Some "https://example.com/~new") "URL is incorrect" Expect.equal it.Url (Some "https://example.com/~new") "URL is incorrect"
Expect.equal it.AccessLevel Author "Access level is incorrect" Expect.equal it.AccessLevel ContentAuthor "Access level is incorrect"
Expect.equal it.CreatedOn (Noda.epoch + Duration.FromDays 365) "Created on is incorrect" Expect.equal it.CreatedOn (Noda.epoch + Duration.FromDays 365) "Created on is incorrect"
Expect.isNone it.LastSeenOn "Last seen on should not have had a value" Expect.isNone it.LastSeenOn "Last seen on should not have had a value"
} }
+105 -17
View File
@@ -31,7 +31,7 @@ let nodaTests = testList "Noda" [
let accessLevelTests = testList "AccessLevel" [ let accessLevelTests = testList "AccessLevel" [
testList "Parse" [ testList "Parse" [
test "succeeds for \"Author\"" { test "succeeds for \"Author\"" {
Expect.equal (AccessLevel.Parse "Author") Author "Author not parsed correctly" Expect.equal (AccessLevel.Parse "Author") AccessLevel.ContentAuthor "Author not parsed correctly"
} }
test "succeeds for \"Editor\"" { test "succeeds for \"Editor\"" {
Expect.equal (AccessLevel.Parse "Editor") Editor "Editor not parsed correctly" Expect.equal (AccessLevel.Parse "Editor") Editor "Editor not parsed correctly"
@@ -48,8 +48,8 @@ let accessLevelTests = testList "AccessLevel" [
} }
] ]
testList "ToString" [ testList "ToString" [
test "Author succeeds" { test "ContentAuthor succeeds" {
Expect.equal (string Author) "Author" "Author string incorrect" Expect.equal (string AccessLevel.ContentAuthor) "Author" "ContentAuthor string incorrect"
} }
test "Editor succeeds" { test "Editor succeeds" {
Expect.equal (string Editor) "Editor" "Editor string incorrect" Expect.equal (string Editor) "Editor" "Editor string incorrect"
@@ -62,20 +62,20 @@ let accessLevelTests = testList "AccessLevel" [
} }
] ]
testList "HasAccess" [ testList "HasAccess" [
test "Author has Author access" { test "ContentAuthor has ContentAuthor access" {
Expect.isTrue (Author.HasAccess Author) "Author should have Author access" Expect.isTrue (ContentAuthor.HasAccess ContentAuthor) "ContentAuthor should have ContentAuthor access"
} }
test "Author does not have Editor access" { test "ContentAuthor does not have Editor access" {
Expect.isFalse (Author.HasAccess Editor) "Author should not have Editor access" Expect.isFalse (ContentAuthor.HasAccess Editor) "ContentAuthor should not have Editor access"
} }
test "Author does not have WebLogAdmin access" { test "ContentAuthor does not have WebLogAdmin access" {
Expect.isFalse (Author.HasAccess WebLogAdmin) "Author should not have WebLogAdmin access" Expect.isFalse (ContentAuthor.HasAccess WebLogAdmin) "ContentAuthor should not have WebLogAdmin access"
} }
test "Author does not have Administrator access" { test "ContentAuthor does not have Administrator access" {
Expect.isFalse (Author.HasAccess Administrator) "Author should not have Administrator access" Expect.isFalse (ContentAuthor.HasAccess Administrator) "ContentAuthor should not have Administrator access"
} }
test "Editor has Author access" { test "Editor has ContentAuthor access" {
Expect.isTrue (Editor.HasAccess Author) "Editor should have Author access" Expect.isTrue (Editor.HasAccess ContentAuthor) "Editor should have ContentAuthor access"
} }
test "Editor has Editor access" { test "Editor has Editor access" {
Expect.isTrue (Editor.HasAccess Editor) "Editor should have Editor access" Expect.isTrue (Editor.HasAccess Editor) "Editor should have Editor access"
@@ -86,8 +86,8 @@ let accessLevelTests = testList "AccessLevel" [
test "Editor does not have Administrator access" { test "Editor does not have Administrator access" {
Expect.isFalse (Editor.HasAccess Administrator) "Editor should not have Administrator access" Expect.isFalse (Editor.HasAccess Administrator) "Editor should not have Administrator access"
} }
test "WebLogAdmin has Author access" { test "WebLogAdmin has ContentAuthor access" {
Expect.isTrue (WebLogAdmin.HasAccess Author) "WebLogAdmin should have Author access" Expect.isTrue (WebLogAdmin.HasAccess ContentAuthor) "WebLogAdmin should have ContentAuthor access"
} }
test "WebLogAdmin has Editor access" { test "WebLogAdmin has Editor access" {
Expect.isTrue (WebLogAdmin.HasAccess Editor) "WebLogAdmin should have Editor access" Expect.isTrue (WebLogAdmin.HasAccess Editor) "WebLogAdmin should have Editor access"
@@ -98,8 +98,8 @@ let accessLevelTests = testList "AccessLevel" [
test "WebLogAdmin does not have Administrator access" { test "WebLogAdmin does not have Administrator access" {
Expect.isFalse (WebLogAdmin.HasAccess Administrator) "WebLogAdmin should not have Administrator access" Expect.isFalse (WebLogAdmin.HasAccess Administrator) "WebLogAdmin should not have Administrator access"
} }
test "Administrator has Author access" { test "Administrator has ContentAuthor access" {
Expect.isTrue (Administrator.HasAccess Author) "Administrator should have Author access" Expect.isTrue (Administrator.HasAccess ContentAuthor) "Administrator should have ContentAuthor access"
} }
test "Administrator has Editor access" { test "Administrator has Editor access" {
Expect.isTrue (Administrator.HasAccess Editor) "Administrator should have Editor access" Expect.isTrue (Administrator.HasAccess Editor) "Administrator should have Editor access"
@@ -684,6 +684,93 @@ let openGraphPropertiesTests = testList "OpenGraphProperties" [
] ]
] ]
/// Tests for the PersonGroup type
let personGroupTests = testList "PersonGroup" [
testList "Parse" [
test "succeeds for \"Administration\"" {
Expect.equal (PersonGroup.Parse "Administration") Administration "Administration not parsed correctly"
}
test "succeeds for \"Audio Post-Production\"" {
Expect.equal
(PersonGroup.Parse "Audio Post-Production")
AudioPostProduction
"Audio Post-Production not parsed correctly"
}
test "succeeds for \"Audio Production\"" {
Expect.equal
(PersonGroup.Parse "Audio Production") AudioProduction "Audio Production not parsed correctly"
}
test "succeeds for \"Cast\"" {
Expect.equal (PersonGroup.Parse "Cast") Cast "Cast not parsed correctly"
}
test "succeeds for \"Community\"" {
Expect.equal (PersonGroup.Parse "Community") Community "Community not parsed correctly"
}
test "succeeds for \"Creative Direction\"" {
Expect.equal
(PersonGroup.Parse "Creative Direction") CreativeDirection "Creative Direction not parsed correctly"
}
test "succeeds for \"Misc.\"" {
Expect.equal (PersonGroup.Parse "Misc.") Misc "Misc. not parsed correctly"
}
test "succeeds for \"Video Post-Production\"" {
Expect.equal
(PersonGroup.Parse "Video Post-Production")
VideoPostProduction
"Video Post-Production not parsed correctly"
}
test "succeeds for \"Video Production\"" {
Expect.equal
(PersonGroup.Parse "Video Production") VideoProduction "Video Production not parsed correctly"
}
test "succeeds for \"Visuals\"" {
Expect.equal (PersonGroup.Parse "Visuals") Visuals "Visuals not parsed correctly"
}
test "succeeds for \"Writing\"" {
Expect.equal (PersonGroup.Parse "Writing") Writing "Writing not parsed correctly"
}
test "fails for unrecognized value" {
Expect.throwsT<ArgumentException>
(fun () -> ignore (PersonGroup.Parse "Fan")) "Invalid value should have raised an exception"
}
]
testList "ToString" [
test "Administration succeeds" {
Expect.equal (string Administration) "Administration" "Administration string incorrect"
}
test "Audio Post-Production succeeds" {
Expect.equal (string AudioPostProduction) "Audio Post-Production" "Audio Post-Production string incorrect"
}
test "Audio Production succeeds" {
Expect.equal (string AudioProduction) "Audio Production" "Audio Production string incorrect"
}
test "Cast succeeds" {
Expect.equal (string Cast) "Cast" "Cast string incorrect"
}
test "Community succeeds" {
Expect.equal (string Community) "Community" "Community string incorrect"
}
test "Creative Direction succeeds" {
Expect.equal (string CreativeDirection) "Creative Direction" "Creative Direction string incorrect"
}
test "Misc succeeds" {
Expect.equal (string Misc) "Misc." "Misc string incorrect"
}
test "Video Post-Production succeeds" {
Expect.equal (string VideoPostProduction) "Video Post-Production" "Video Post-Production string incorrect"
}
test "Video Production succeeds" {
Expect.equal (string VideoProduction) "Video Production" "Video Production string incorrect"
}
test "Visuals succeeds" {
Expect.equal (string Visuals) "Visuals" "Visuals string incorrect"
}
test "Writing succeeds" {
Expect.equal (string Writing) "Writing" "Writing string incorrect"
}
]
]
/// Unit tests for the PodcastMedium type /// Unit tests for the PodcastMedium type
let podcastMediumTests = testList "PodcastMedium" [ let podcastMediumTests = testList "PodcastMedium" [
testList "Parse" [ testList "Parse" [
@@ -840,6 +927,7 @@ let all = testList "SupportTypes" [
openGraphVideoTests openGraphVideoTests
openGraphTypeTests openGraphTypeTests
openGraphPropertiesTests openGraphPropertiesTests
personGroupTests
podcastMediumTests podcastMediumTests
postStatusTests postStatusTests
customFeedSourceTests customFeedSourceTests
+1 -1
View File
@@ -13,7 +13,7 @@ module Dashboard =
// GET /admin/dashboard // GET /admin/dashboard
let user : HttpHandler = let user : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let getCount (f: WebLogId -> Task<int>) = f ctx.WebLog.Id let getCount (f: WebLogId -> Task<int>) = f ctx.WebLog.Id
let data = ctx.Data let data = ctx.Data
+1 -1
View File
@@ -88,7 +88,7 @@ let private generateViewContext messages viewCtx (ctx: HttpContext) =
CurrentPage = ctx.Request.Path.Value[1..] CurrentPage = ctx.Request.Path.Value[1..]
Messages = messages Messages = messages
Generator = ctx.Generator Generator = ctx.Generator
IsAuthor = ctx.HasAccessLevel Author IsAuthor = ctx.HasAccessLevel ContentAuthor
IsEditor = ctx.HasAccessLevel Editor IsEditor = ctx.HasAccessLevel Editor
IsWebLogAdmin = ctx.HasAccessLevel WebLogAdmin IsWebLogAdmin = ctx.HasAccessLevel WebLogAdmin
IsAdministrator = ctx.HasAccessLevel Administrator } IsAdministrator = ctx.HasAccessLevel Administrator }
+10 -10
View File
@@ -8,7 +8,7 @@ open MyWebLog.ViewModels
// GET /admin/pages // GET /admin/pages
// GET /admin/pages/page/{pageNbr} // GET /admin/pages/page/{pageNbr}
let all pageNbr : HttpHandler = let all pageNbr : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let! pages = ctx.Data.Page.FindPageOfPages ctx.WebLog.Id pageNbr let! pages = ctx.Data.Page.FindPageOfPages ctx.WebLog.Id pageNbr
let displayPages = let displayPages =
@@ -24,7 +24,7 @@ let all pageNbr : HttpHandler =
// GET /admin/page/{id}/edit // GET /admin/page/{id}/edit
let edit pgId : HttpHandler = let edit pgId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let! result = task { let! result = task {
match pgId with match pgId with
@@ -57,7 +57,7 @@ let delete pgId : HttpHandler =
// GET /admin/page/{id}/permalinks // GET /admin/page/{id}/permalinks
let editPermalinks pgId : HttpHandler = let editPermalinks pgId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
| Some pg when canEdit pg.AuthorId ctx -> | Some pg when canEdit pg.AuthorId ctx ->
@@ -71,7 +71,7 @@ let editPermalinks pgId : HttpHandler =
// POST /admin/page/permalinks // POST /admin/page/permalinks
let savePermalinks : HttpHandler = let savePermalinks : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<ManagePermalinksModel>() let! model = ctx.BindFormAsync<ManagePermalinksModel>()
@@ -90,7 +90,7 @@ let savePermalinks : HttpHandler =
// GET /admin/page/{id}/revisions // GET /admin/page/{id}/revisions
let editRevisions pgId : HttpHandler = let editRevisions pgId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
| Some pg when canEdit pg.AuthorId ctx -> | Some pg when canEdit pg.AuthorId ctx ->
@@ -104,7 +104,7 @@ let editRevisions pgId : HttpHandler =
// DELETE /admin/page/{id}/revisions // DELETE /admin/page/{id}/revisions
let purgeRevisions pgId : HttpHandler = let purgeRevisions pgId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
match! data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with match! data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
@@ -128,7 +128,7 @@ let private findPageRevision pgId revDate (ctx: HttpContext) = task {
// GET /admin/page/{id}/revision/{revision-date}/preview // GET /admin/page/{id}/revision/{revision-date}/preview
let previewRevision (pgId, revDate) : HttpHandler = let previewRevision (pgId, revDate) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! findPageRevision pgId revDate ctx with match! findPageRevision pgId revDate ctx with
| Some pg, Some rev when canEdit pg.AuthorId ctx -> | Some pg, Some rev when canEdit pg.AuthorId ctx ->
@@ -139,7 +139,7 @@ let previewRevision (pgId, revDate) : HttpHandler =
// POST /admin/page/{id}/revision/{revision-date}/restore // POST /admin/page/{id}/revision/{revision-date}/restore
let restoreRevision (pgId, revDate) : HttpHandler = let restoreRevision (pgId, revDate) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! findPageRevision pgId revDate ctx with match! findPageRevision pgId revDate ctx with
@@ -157,7 +157,7 @@ let restoreRevision (pgId, revDate) : HttpHandler =
// DELETE /admin/page/{id}/revision/{revision-date} // DELETE /admin/page/{id}/revision/{revision-date}
let deleteRevision (pgId, revDate) : HttpHandler = let deleteRevision (pgId, revDate) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! findPageRevision pgId revDate ctx with match! findPageRevision pgId revDate ctx with
| Some pg, Some rev when canEdit pg.AuthorId ctx -> | Some pg, Some rev when canEdit pg.AuthorId ctx ->
@@ -171,7 +171,7 @@ let deleteRevision (pgId, revDate) : HttpHandler =
// POST /admin/page/save // POST /admin/page/save
let save : HttpHandler = let save : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<EditPageModel>() let! model = ctx.BindFormAsync<EditPageModel>()
+14 -14
View File
@@ -253,7 +253,7 @@ let chapters (post: Post) : HttpHandler = fun next ctx ->
// GET /admin/posts // GET /admin/posts
// GET /admin/posts/page/{pageNbr} // GET /admin/posts/page/{pageNbr}
let all pageNbr : HttpHandler = let all pageNbr : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
let! posts = data.Post.FindPageOfPosts ctx.WebLog.Id pageNbr 25 let! posts = data.Post.FindPageOfPosts ctx.WebLog.Id pageNbr 25
@@ -263,7 +263,7 @@ let all pageNbr : HttpHandler =
// GET /admin/post/{id}/edit // GET /admin/post/{id}/edit
let edit postId : HttpHandler = let edit postId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
let! result = task { let! result = task {
@@ -302,7 +302,7 @@ let delete postId : HttpHandler =
// GET /admin/post/{id}/permalinks // GET /admin/post/{id}/permalinks
let editPermalinks postId : HttpHandler = let editPermalinks postId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
| Some post when canEdit post.AuthorId ctx -> | Some post when canEdit post.AuthorId ctx ->
@@ -316,7 +316,7 @@ let editPermalinks postId : HttpHandler =
// POST /admin/post/permalinks // POST /admin/post/permalinks
let savePermalinks : HttpHandler = let savePermalinks : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<ManagePermalinksModel>() let! model = ctx.BindFormAsync<ManagePermalinksModel>()
@@ -335,7 +335,7 @@ let savePermalinks : HttpHandler =
// GET /admin/post/{id}/revisions // GET /admin/post/{id}/revisions
let editRevisions postId : HttpHandler = let editRevisions postId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
| Some post when canEdit post.AuthorId ctx -> | Some post when canEdit post.AuthorId ctx ->
@@ -349,7 +349,7 @@ let editRevisions postId : HttpHandler =
// DELETE /admin/post/{id}/revisions // DELETE /admin/post/{id}/revisions
let purgeRevisions postId : HttpHandler = let purgeRevisions postId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
match! data.Post.FindFullById (PostId postId) ctx.WebLog.Id with match! data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
@@ -374,7 +374,7 @@ let private findPostRevision postId revDate (ctx: HttpContext) = task {
// GET /admin/post/{id}/revision/{revision-date}/preview // GET /admin/post/{id}/revision/{revision-date}/preview
let previewRevision (postId, revDate) : HttpHandler = let previewRevision (postId, revDate) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! findPostRevision postId revDate ctx with match! findPostRevision postId revDate ctx with
| Some post, Some rev when canEdit post.AuthorId ctx -> | Some post, Some rev when canEdit post.AuthorId ctx ->
@@ -385,7 +385,7 @@ let previewRevision (postId, revDate) : HttpHandler =
// POST /admin/post/{id}/revision/{revision-date}/restore // POST /admin/post/{id}/revision/{revision-date}/restore
let restoreRevision (postId, revDate) : HttpHandler = let restoreRevision (postId, revDate) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! findPostRevision postId revDate ctx with match! findPostRevision postId revDate ctx with
@@ -403,7 +403,7 @@ let restoreRevision (postId, revDate) : HttpHandler =
// DELETE /admin/post/{id}/revision/{revision-date} // DELETE /admin/post/{id}/revision/{revision-date}
let deleteRevision (postId, revDate) : HttpHandler = let deleteRevision (postId, revDate) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! findPostRevision postId revDate ctx with match! findPostRevision postId revDate ctx with
| Some post, Some rev when canEdit post.AuthorId ctx -> | Some post, Some rev when canEdit post.AuthorId ctx ->
@@ -417,7 +417,7 @@ let deleteRevision (postId, revDate) : HttpHandler =
// GET /admin/post/{id}/chapters // GET /admin/post/{id}/chapters
let manageChapters postId : HttpHandler = let manageChapters postId : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! ctx.Data.Post.FindById (PostId postId) ctx.WebLog.Id with match! ctx.Data.Post.FindById (PostId postId) ctx.WebLog.Id with
| Some post | Some post
@@ -432,7 +432,7 @@ let manageChapters postId : HttpHandler =
// GET /admin/post/{id}/chapter/{idx} // GET /admin/post/{id}/chapter/{idx}
let editChapter (postId, index) : HttpHandler = let editChapter (postId, index) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! ctx.Data.Post.FindById (PostId postId) ctx.WebLog.Id with match! ctx.Data.Post.FindById (PostId postId) ctx.WebLog.Id with
| Some post | Some post
@@ -455,7 +455,7 @@ let editChapter (postId, index) : HttpHandler =
// POST /admin/post/{id}/chapter/{idx} // POST /admin/post/{id}/chapter/{idx}
let saveChapter (postId, index) : HttpHandler = let saveChapter (postId, index) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
@@ -488,7 +488,7 @@ let saveChapter (postId, index) : HttpHandler =
// DELETE /admin/post/{id}/chapter/{idx} // DELETE /admin/post/{id}/chapter/{idx}
let deleteChapter (postId, index) : HttpHandler = let deleteChapter (postId, index) : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
match! data.Post.FindById (PostId postId) ctx.WebLog.Id with match! data.Post.FindById (PostId postId) ctx.WebLog.Id with
@@ -512,7 +512,7 @@ let deleteChapter (postId, index) : HttpHandler =
// POST /admin/post/save // POST /admin/post/save
let save : HttpHandler = let save : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<EditPostModel>() let! model = ctx.BindFormAsync<EditPostModel>()
+3 -3
View File
@@ -91,7 +91,7 @@ let makeSlug it = (Regex """\s+""").Replace((Regex "[^A-z0-9 -]").Replace(it, ""
// GET /admin/uploads // GET /admin/uploads
let list : HttpHandler = let list : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
let webLog = ctx.WebLog let webLog = ctx.WebLog
let! dbUploads = ctx.Data.Upload.FindByWebLog webLog.Id let! dbUploads = ctx.Data.Upload.FindByWebLog webLog.Id
@@ -127,12 +127,12 @@ let list : HttpHandler =
// GET /admin/upload/new // GET /admin/upload/new
let showNew : HttpHandler = let showNew : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> adminPage "Upload a File" next ctx Views.WebLog.uploadNew >=> fun next ctx -> adminPage "Upload a File" next ctx Views.WebLog.uploadNew
// POST /admin/upload/save // POST /admin/upload/save
let save : HttpHandler = let save : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
if ctx.Request.HasFormContentType && ctx.Request.Form.Files.Count > 0 then if ctx.Request.HasFormContentType && ctx.Request.Form.Files.Count > 0 then
+2 -2
View File
@@ -142,7 +142,7 @@ let delete userId : HttpHandler =
// GET /admin/my-info // GET /admin/my-info
let myInfo : HttpHandler = let myInfo : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> fun next ctx -> task { >=> fun next ctx -> task {
match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with
| Some user -> | Some user ->
@@ -154,7 +154,7 @@ let myInfo : HttpHandler =
// POST /admin/my-info // POST /admin/my-info
let saveMyInfo : HttpHandler = let saveMyInfo : HttpHandler =
requireAccess Author requireAccess ContentAuthor
>=> validateCsrf >=> validateCsrf
>=> fun next ctx -> task { >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<EditMyInfoModel>() let! model = ctx.BindFormAsync<EditMyInfoModel>()
+1 -1
View File
@@ -133,7 +133,7 @@ let userList (model: WebLogUser list) app =
| Administrator -> span [ _class $"{badge}-success" ] [ raw "ADMINISTRATOR" ] | Administrator -> span [ _class $"{badge}-success" ] [ raw "ADMINISTRATOR" ]
| WebLogAdmin -> span [ _class $"{badge}-primary" ] [ raw "WEB LOG ADMIN" ] | WebLogAdmin -> span [ _class $"{badge}-primary" ] [ raw "WEB LOG ADMIN" ]
| Editor -> span [ _class $"{badge}-secondary" ] [ raw "EDITOR" ] | Editor -> span [ _class $"{badge}-secondary" ] [ raw "EDITOR" ]
| Author -> span [ _class $"{badge}-dark" ] [ raw "AUTHOR" ] | ContentAuthor -> span [ _class $"{badge}-dark" ] [ raw "AUTHOR" ]
br [] br []
if app.IsAdministrator || (app.IsWebLogAdmin && not (user.AccessLevel = Administrator)) then if app.IsAdministrator || (app.IsWebLogAdmin && not (user.AccessLevel = Administrator)) then
let userUrl = relUrl app $"admin/settings/user/{user.Id}" let userUrl = relUrl app $"admin/settings/user/{user.Id}"