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
+1 -1
View File
@@ -457,7 +457,7 @@ type WebLogUser = {
PreferredName = ""
PasswordHash = ""
Url = None
AccessLevel = Author
AccessLevel = ContentAuthor
CreatedOn = Noda.epoch
LastSeenOn = None }
+333 -4
View File
@@ -61,18 +61,22 @@ module Noda =
/// <summary>A user's access level</summary>
[<Struct>]
type AccessLevel =
/// <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>
| Editor
/// <summary>The user may delete posts and configure web log settings</summary>
| WebLogAdmin
/// <summary>The user may manage themes (which affects all web logs for an installation)</summary>
| Administrator
/// <summary>Weights applied to each access level</summary>
static member private Weights =
[ Author, 10
[ ContentAuthor, 10
Editor, 20
WebLogAdmin, 30
Administrator, 40 ]
@@ -84,7 +88,7 @@ type AccessLevel =
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse level =
match level with
| "Author" -> Author
| "Author" -> ContentAuthor
| "Editor" -> Editor
| "WebLogAdmin" -> WebLogAdmin
| "Administrator" -> Administrator
@@ -93,7 +97,7 @@ type AccessLevel =
/// <inheritdoc />
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 }
/// <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