diff --git a/src/MyWebLog.Data/Converters.fs b/src/MyWebLog.Data/Converters.fs
index c360ac5..4c779c9 100644
--- a/src/MyWebLog.Data/Converters.fs
+++ b/src/MyWebLog.Data/Converters.fs
@@ -84,6 +84,14 @@ module Json =
override _.ReadJson(reader: JsonReader, _: Type, _: OpenGraphType, _: bool, _: JsonSerializer) =
(string >> OpenGraphType.Parse) reader.Value
+ /// Converter for the type
+ type PageIdConverter() =
+ inherit JsonConverter()
+ override _.WriteJson(writer: JsonWriter, value: PageId, _: JsonSerializer) =
+ writer.WriteValue(string value)
+ override _.ReadJson(reader: JsonReader, _: Type, _: PageId, _: bool, _: JsonSerializer) =
+ (string >> PageId) reader.Value
+
/// Converter for the type
type PermalinkConverter() =
inherit JsonConverter()
@@ -92,13 +100,21 @@ module Json =
override _.ReadJson(reader: JsonReader, _: Type, _: Permalink, _: bool, _: JsonSerializer) =
(string >> Permalink) reader.Value
- /// Converter for the type
- type PageIdConverter() =
- inherit JsonConverter()
- override _.WriteJson(writer: JsonWriter, value: PageId, _: JsonSerializer) =
+ /// Converter for the type
+ type PersonGroupConverter() =
+ inherit JsonConverter()
+ override _.WriteJson(writer: JsonWriter, value: PersonGroup, _: JsonSerializer) =
writer.WriteValue(string value)
- override _.ReadJson(reader: JsonReader, _: Type, _: PageId, _: bool, _: JsonSerializer) =
- (string >> PageId) reader.Value
+ override _.ReadJson(reader: JsonReader, _: Type, _: PersonGroup, _: bool, _: JsonSerializer) =
+ (string >> PersonGroup.Parse) reader.Value
+
+ /// Converter for the type
+ type PersonRoleConverter() =
+ inherit JsonConverter()
+ override _.WriteJson(writer: JsonWriter, value: PersonRole, _: JsonSerializer) =
+ writer.WriteValue(string value)
+ override _.ReadJson(reader: JsonReader, _: Type, _: PersonRole, _: bool, _: JsonSerializer) =
+ (string >> PersonRole.Parse) reader.Value
/// Converter for the type
type PodcastMediumConverter() =
@@ -180,8 +196,10 @@ module Json =
ExplicitRatingConverter()
MarkupTextConverter()
OpenGraphTypeConverter()
- PermalinkConverter()
PageIdConverter()
+ PermalinkConverter()
+ PersonGroupConverter()
+ PersonRoleConverter()
PodcastMediumConverter()
PostIdConverter()
TagMapIdConverter()
diff --git a/src/MyWebLog.Domain/DataTypes.fs b/src/MyWebLog.Domain/DataTypes.fs
index b161704..bb25151 100644
--- a/src/MyWebLog.Domain/DataTypes.fs
+++ b/src/MyWebLog.Domain/DataTypes.fs
@@ -457,7 +457,7 @@ type WebLogUser = {
PreferredName = ""
PasswordHash = ""
Url = None
- AccessLevel = Author
+ AccessLevel = ContentAuthor
CreatedOn = Noda.epoch
LastSeenOn = None }
diff --git a/src/MyWebLog.Domain/SupportTypes.fs b/src/MyWebLog.Domain/SupportTypes.fs
index de6917e..cd9a68d 100644
--- a/src/MyWebLog.Domain/SupportTypes.fs
+++ b/src/MyWebLog.Domain/SupportTypes.fs
@@ -61,18 +61,22 @@ module Noda =
/// A user's access level
[]
type AccessLevel =
+
/// The user may create and publish posts and edit the ones they have created
- | Author
+ | ContentAuthor
+
/// The user may edit posts they did not create, but may not delete them
| Editor
+
/// The user may delete posts and configure web log settings
| WebLogAdmin
+
/// The user may manage themes (which affects all web logs for an installation)
| Administrator
/// Weights applied to each access level
static member private Weights =
- [ Author, 10
+ [ ContentAuthor, 10
Editor, 20
WebLogAdmin, 30
Administrator, 40 ]
@@ -84,7 +88,7 @@ type AccessLevel =
/// If the string is not valid
static member Parse level =
match level with
- | "Author" -> Author
+ | "Author" -> ContentAuthor
| "Editor" -> Editor
| "WebLogAdmin" -> WebLogAdmin
| "Administrator" -> Administrator
@@ -93,7 +97,7 @@ type AccessLevel =
///
override this.ToString() =
match this with
- | Author -> "Author"
+ | ContentAuthor -> "Author"
| Editor -> "Editor"
| WebLogAdmin -> "WebLogAdmin"
| Administrator -> "Administrator"
@@ -269,6 +273,331 @@ type Chapter = {
EndTime = None
Location = None }
+/// A group to which a podcast person can belong
+type PersonGroup =
+ | Administration
+ | AudioPostProduction
+ | AudioProduction
+ | Cast
+ | Community
+ | CreativeDirection
+ | Misc
+ | VideoPostProduction
+ | VideoProduction
+ | Visuals
+ | Writing
+
+ /// Parse a string into a group
+ /// The string representation of the group
+ /// The PersonGroup instance parsed from the string
+ /// If the string is not valid
+ 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"
+
+ ///
+ 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"
+
+
+/// The role a person plays for a podcast or a podcast episode
+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
+
+ /// Parse a string into a role
+ /// The string representation of the role
+ /// The PersonRole instance parsed from the string
+ /// If the string is not valid
+ 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"
+
+ ///
+ 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)"
+
+ /// Get the display name of the role
+ /// Both Video Production and Writing have an "Editor"; this is how it should be rendered in RSS feeds and
+ /// in the UI
+ member this.ToDisplay() =
+ match this with VideoEditor | WritingEditor -> "Editor" | _ -> string this
+
+ /// Role / group cross-reference
+ 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
diff --git a/src/MyWebLog.Tests/Data/ConvertersTests.fs b/src/MyWebLog.Tests/Data/ConvertersTests.fs
index accfad0..274d749 100644
--- a/src/MyWebLog.Tests/Data/ConvertersTests.fs
+++ b/src/MyWebLog.Tests/Data/ConvertersTests.fs
@@ -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("\"page-test\"", opts)
+ Expect.equal after (PageId "page-test") "Page ID deserialized incorrectly"
+ }
+]
+
/// Unit tests for the PermalinkConverter type
let permalinkConverterTests = testList "PermalinkConverter" [
let opts = JsonSerializerSettings()
@@ -150,17 +164,31 @@ let permalinkConverterTests = testList "PermalinkConverter" [
}
]
-/// Unit tests for the PageIdConverter type
-let pageIdConverterTests = testList "PageIdConverter" [
+/// Unit tests for the PersonGroupConverter type
+let personGroupConverterTests = testList "PersonGroupConverter" [
let opts = JsonSerializerSettings()
- opts.Converters.Add(PageIdConverter())
+ opts.Converters.Add(PersonGroupConverter())
test "succeeds when serializing" {
- let after = JsonConvert.SerializeObject(PageId "test-page", opts)
- Expect.equal after "\"test-page\"" "Page ID serialized incorrectly"
+ let after = JsonConvert.SerializeObject(Visuals, opts)
+ Expect.equal after "\"Visuals\"" "PersonGroup serialized incorrectly"
}
test "succeeds when deserializing" {
- let after = JsonConvert.DeserializeObject("\"page-test\"", opts)
- Expect.equal after (PageId "page-test") "Page ID deserialized incorrectly"
+ let after = JsonConvert.DeserializeObject("\"Cast\"", opts)
+ 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("\"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) "Explicit rating converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof) "Markup text converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof) "OpenGraph type converter not found"
- Expect.hasCountOf ser.Converters 1u (has typeof) "Permalink converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof) "Page ID converter not found"
+ Expect.hasCountOf ser.Converters 1u (has typeof) "Permalink converter not found"
+ Expect.hasCountOf ser.Converters 1u (has typeof) "PersonGroup converter not found"
+ Expect.hasCountOf ser.Converters 1u (has typeof) "PersonRole converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof) "Podcast medium converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof) "Post ID converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof) "Tag map ID converter not found"
@@ -318,8 +348,10 @@ let all = testList "Converters" [
explicitRatingConverterTests
markupTextConverterTests
openGraphTypeConverterTests
- permalinkConverterTests
pageIdConverterTests
+ permalinkConverterTests
+ personGroupConverterTests
+ personRoleConverterTests
podcastMediumConverterTests
postIdConverterTests
tagMapIdConverterTests
diff --git a/src/MyWebLog.Tests/Data/WebLogUserDataTests.fs b/src/MyWebLog.Tests/Data/WebLogUserDataTests.fs
index ab5aa1f..c808839 100644
--- a/src/MyWebLog.Tests/Data/WebLogUserDataTests.fs
+++ b/src/MyWebLog.Tests/Data/WebLogUserDataTests.fs
@@ -33,7 +33,7 @@ let ``Add succeeds`` (data: IData) = task {
PreferredName = "n00b"
PasswordHash = "hashed-password"
Url = Some "https://example.com/~new"
- AccessLevel = Author
+ AccessLevel = ContentAuthor
CreatedOn = Noda.epoch + Duration.FromDays 365
LastSeenOn = None }
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.PasswordHash "hashed-password" "Password hash 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.isNone it.LastSeenOn "Last seen on should not have had a value"
}
diff --git a/src/MyWebLog.Tests/Domain/SupportTypesTests.fs b/src/MyWebLog.Tests/Domain/SupportTypesTests.fs
index 01bb40c..87701ac 100644
--- a/src/MyWebLog.Tests/Domain/SupportTypesTests.fs
+++ b/src/MyWebLog.Tests/Domain/SupportTypesTests.fs
@@ -31,7 +31,7 @@ let nodaTests = testList "Noda" [
let accessLevelTests = testList "AccessLevel" [
testList "Parse" [
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\"" {
Expect.equal (AccessLevel.Parse "Editor") Editor "Editor not parsed correctly"
@@ -48,8 +48,8 @@ let accessLevelTests = testList "AccessLevel" [
}
]
testList "ToString" [
- test "Author succeeds" {
- Expect.equal (string Author) "Author" "Author string incorrect"
+ test "ContentAuthor succeeds" {
+ Expect.equal (string AccessLevel.ContentAuthor) "Author" "ContentAuthor string incorrect"
}
test "Editor succeeds" {
Expect.equal (string Editor) "Editor" "Editor string incorrect"
@@ -62,20 +62,20 @@ let accessLevelTests = testList "AccessLevel" [
}
]
testList "HasAccess" [
- test "Author has Author access" {
- Expect.isTrue (Author.HasAccess Author) "Author should have Author access"
+ test "ContentAuthor has ContentAuthor access" {
+ Expect.isTrue (ContentAuthor.HasAccess ContentAuthor) "ContentAuthor should have ContentAuthor access"
}
- test "Author does not have Editor access" {
- Expect.isFalse (Author.HasAccess Editor) "Author should not have Editor access"
+ test "ContentAuthor does not have Editor access" {
+ Expect.isFalse (ContentAuthor.HasAccess Editor) "ContentAuthor should not have Editor access"
}
- test "Author does not have WebLogAdmin access" {
- Expect.isFalse (Author.HasAccess WebLogAdmin) "Author should not have WebLogAdmin access"
+ test "ContentAuthor does not have WebLogAdmin access" {
+ Expect.isFalse (ContentAuthor.HasAccess WebLogAdmin) "ContentAuthor should not have WebLogAdmin access"
}
- test "Author does not have Administrator access" {
- Expect.isFalse (Author.HasAccess Administrator) "Author should not have Administrator access"
+ test "ContentAuthor does not have Administrator access" {
+ Expect.isFalse (ContentAuthor.HasAccess Administrator) "ContentAuthor should not have Administrator access"
}
- test "Editor has Author access" {
- Expect.isTrue (Editor.HasAccess Author) "Editor should have Author access"
+ test "Editor has ContentAuthor access" {
+ Expect.isTrue (Editor.HasAccess ContentAuthor) "Editor should have ContentAuthor access"
}
test "Editor has 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" {
Expect.isFalse (Editor.HasAccess Administrator) "Editor should not have Administrator access"
}
- test "WebLogAdmin has Author access" {
- Expect.isTrue (WebLogAdmin.HasAccess Author) "WebLogAdmin should have Author access"
+ test "WebLogAdmin has ContentAuthor access" {
+ Expect.isTrue (WebLogAdmin.HasAccess ContentAuthor) "WebLogAdmin should have ContentAuthor access"
}
test "WebLogAdmin has 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" {
Expect.isFalse (WebLogAdmin.HasAccess Administrator) "WebLogAdmin should not have Administrator access"
}
- test "Administrator has Author access" {
- Expect.isTrue (Administrator.HasAccess Author) "Administrator should have Author access"
+ test "Administrator has ContentAuthor access" {
+ Expect.isTrue (Administrator.HasAccess ContentAuthor) "Administrator should have ContentAuthor access"
}
test "Administrator has 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
+ (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
let podcastMediumTests = testList "PodcastMedium" [
testList "Parse" [
@@ -840,6 +927,7 @@ let all = testList "SupportTypes" [
openGraphVideoTests
openGraphTypeTests
openGraphPropertiesTests
+ personGroupTests
podcastMediumTests
postStatusTests
customFeedSourceTests
diff --git a/src/MyWebLog/Handlers/Admin.fs b/src/MyWebLog/Handlers/Admin.fs
index 05bddcc..b86dca5 100644
--- a/src/MyWebLog/Handlers/Admin.fs
+++ b/src/MyWebLog/Handlers/Admin.fs
@@ -13,7 +13,7 @@ module Dashboard =
// GET /admin/dashboard
let user : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let getCount (f: WebLogId -> Task) = f ctx.WebLog.Id
let data = ctx.Data
diff --git a/src/MyWebLog/Handlers/Helpers.fs b/src/MyWebLog/Handlers/Helpers.fs
index b0e3402..d4f3235 100644
--- a/src/MyWebLog/Handlers/Helpers.fs
+++ b/src/MyWebLog/Handlers/Helpers.fs
@@ -88,7 +88,7 @@ let private generateViewContext messages viewCtx (ctx: HttpContext) =
CurrentPage = ctx.Request.Path.Value[1..]
Messages = messages
Generator = ctx.Generator
- IsAuthor = ctx.HasAccessLevel Author
+ IsAuthor = ctx.HasAccessLevel ContentAuthor
IsEditor = ctx.HasAccessLevel Editor
IsWebLogAdmin = ctx.HasAccessLevel WebLogAdmin
IsAdministrator = ctx.HasAccessLevel Administrator }
diff --git a/src/MyWebLog/Handlers/Page.fs b/src/MyWebLog/Handlers/Page.fs
index 39c8794..c7ce367 100644
--- a/src/MyWebLog/Handlers/Page.fs
+++ b/src/MyWebLog/Handlers/Page.fs
@@ -8,7 +8,7 @@ open MyWebLog.ViewModels
// GET /admin/pages
// GET /admin/pages/page/{pageNbr}
let all pageNbr : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let! pages = ctx.Data.Page.FindPageOfPages ctx.WebLog.Id pageNbr
let displayPages =
@@ -24,7 +24,7 @@ let all pageNbr : HttpHandler =
// GET /admin/page/{id}/edit
let edit pgId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let! result = task {
match pgId with
@@ -57,7 +57,7 @@ let delete pgId : HttpHandler =
// GET /admin/page/{id}/permalinks
let editPermalinks pgId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
| Some pg when canEdit pg.AuthorId ctx ->
@@ -71,7 +71,7 @@ let editPermalinks pgId : HttpHandler =
// POST /admin/page/permalinks
let savePermalinks : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
let! model = ctx.BindFormAsync()
@@ -90,7 +90,7 @@ let savePermalinks : HttpHandler =
// GET /admin/page/{id}/revisions
let editRevisions pgId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
| Some pg when canEdit pg.AuthorId ctx ->
@@ -104,7 +104,7 @@ let editRevisions pgId : HttpHandler =
// DELETE /admin/page/{id}/revisions
let purgeRevisions pgId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let data = ctx.Data
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
let previewRevision (pgId, revDate) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! findPageRevision pgId revDate ctx with
| 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
let restoreRevision (pgId, revDate) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
match! findPageRevision pgId revDate ctx with
@@ -157,7 +157,7 @@ let restoreRevision (pgId, revDate) : HttpHandler =
// DELETE /admin/page/{id}/revision/{revision-date}
let deleteRevision (pgId, revDate) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! findPageRevision pgId revDate ctx with
| Some pg, Some rev when canEdit pg.AuthorId ctx ->
@@ -171,7 +171,7 @@ let deleteRevision (pgId, revDate) : HttpHandler =
// POST /admin/page/save
let save : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
let! model = ctx.BindFormAsync()
diff --git a/src/MyWebLog/Handlers/Post.fs b/src/MyWebLog/Handlers/Post.fs
index 559e5ca..866e916 100644
--- a/src/MyWebLog/Handlers/Post.fs
+++ b/src/MyWebLog/Handlers/Post.fs
@@ -253,7 +253,7 @@ let chapters (post: Post) : HttpHandler = fun next ctx ->
// GET /admin/posts
// GET /admin/posts/page/{pageNbr}
let all pageNbr : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let data = ctx.Data
let! posts = data.Post.FindPageOfPosts ctx.WebLog.Id pageNbr 25
@@ -263,7 +263,7 @@ let all pageNbr : HttpHandler =
// GET /admin/post/{id}/edit
let edit postId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let data = ctx.Data
let! result = task {
@@ -302,7 +302,7 @@ let delete postId : HttpHandler =
// GET /admin/post/{id}/permalinks
let editPermalinks postId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
| Some post when canEdit post.AuthorId ctx ->
@@ -316,7 +316,7 @@ let editPermalinks postId : HttpHandler =
// POST /admin/post/permalinks
let savePermalinks : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
let! model = ctx.BindFormAsync()
@@ -335,7 +335,7 @@ let savePermalinks : HttpHandler =
// GET /admin/post/{id}/revisions
let editRevisions postId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
| Some post when canEdit post.AuthorId ctx ->
@@ -349,7 +349,7 @@ let editRevisions postId : HttpHandler =
// DELETE /admin/post/{id}/revisions
let purgeRevisions postId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let data = ctx.Data
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
let previewRevision (postId, revDate) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! findPostRevision postId revDate ctx with
| 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
let restoreRevision (postId, revDate) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
match! findPostRevision postId revDate ctx with
@@ -403,7 +403,7 @@ let restoreRevision (postId, revDate) : HttpHandler =
// DELETE /admin/post/{id}/revision/{revision-date}
let deleteRevision (postId, revDate) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! findPostRevision postId revDate ctx with
| Some post, Some rev when canEdit post.AuthorId ctx ->
@@ -417,7 +417,7 @@ let deleteRevision (postId, revDate) : HttpHandler =
// GET /admin/post/{id}/chapters
let manageChapters postId : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! ctx.Data.Post.FindById (PostId postId) ctx.WebLog.Id with
| Some post
@@ -432,7 +432,7 @@ let manageChapters postId : HttpHandler =
// GET /admin/post/{id}/chapter/{idx}
let editChapter (postId, index) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! ctx.Data.Post.FindById (PostId postId) ctx.WebLog.Id with
| Some post
@@ -455,7 +455,7 @@ let editChapter (postId, index) : HttpHandler =
// POST /admin/post/{id}/chapter/{idx}
let saveChapter (postId, index) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
let data = ctx.Data
@@ -488,7 +488,7 @@ let saveChapter (postId, index) : HttpHandler =
// DELETE /admin/post/{id}/chapter/{idx}
let deleteChapter (postId, index) : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let data = ctx.Data
match! data.Post.FindById (PostId postId) ctx.WebLog.Id with
@@ -512,7 +512,7 @@ let deleteChapter (postId, index) : HttpHandler =
// POST /admin/post/save
let save : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
let! model = ctx.BindFormAsync()
diff --git a/src/MyWebLog/Handlers/Upload.fs b/src/MyWebLog/Handlers/Upload.fs
index 6c4fc62..79e061b 100644
--- a/src/MyWebLog/Handlers/Upload.fs
+++ b/src/MyWebLog/Handlers/Upload.fs
@@ -91,7 +91,7 @@ let makeSlug it = (Regex """\s+""").Replace((Regex "[^A-z0-9 -]").Replace(it, ""
// GET /admin/uploads
let list : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
let webLog = ctx.WebLog
let! dbUploads = ctx.Data.Upload.FindByWebLog webLog.Id
@@ -127,12 +127,12 @@ let list : HttpHandler =
// GET /admin/upload/new
let showNew : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> adminPage "Upload a File" next ctx Views.WebLog.uploadNew
// POST /admin/upload/save
let save : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
if ctx.Request.HasFormContentType && ctx.Request.Form.Files.Count > 0 then
diff --git a/src/MyWebLog/Handlers/User.fs b/src/MyWebLog/Handlers/User.fs
index ebcc4f6..2e2dba0 100644
--- a/src/MyWebLog/Handlers/User.fs
+++ b/src/MyWebLog/Handlers/User.fs
@@ -142,7 +142,7 @@ let delete userId : HttpHandler =
// GET /admin/my-info
let myInfo : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> fun next ctx -> task {
match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with
| Some user ->
@@ -154,7 +154,7 @@ let myInfo : HttpHandler =
// POST /admin/my-info
let saveMyInfo : HttpHandler =
- requireAccess Author
+ requireAccess ContentAuthor
>=> validateCsrf
>=> fun next ctx -> task {
let! model = ctx.BindFormAsync()
diff --git a/src/MyWebLog/Views/User.fs b/src/MyWebLog/Views/User.fs
index 908b147..cab18ac 100644
--- a/src/MyWebLog/Views/User.fs
+++ b/src/MyWebLog/Views/User.fs
@@ -133,7 +133,7 @@ let userList (model: WebLogUser list) app =
| Administrator -> span [ _class $"{badge}-success" ] [ raw "ADMINISTRATOR" ]
| WebLogAdmin -> span [ _class $"{badge}-primary" ] [ raw "WEB LOG ADMIN" ]
| Editor -> span [ _class $"{badge}-secondary" ] [ raw "EDITOR" ]
- | Author -> span [ _class $"{badge}-dark" ] [ raw "AUTHOR" ]
+ | ContentAuthor -> span [ _class $"{badge}-dark" ] [ raw "AUTHOR" ]
br []
if app.IsAdministrator || (app.IsWebLogAdmin && not (user.AccessLevel = Administrator)) then
let userUrl = relUrl app $"admin/settings/user/{user.Id}"