namespace PrayerTracker.Entities (*-- SUPPORT TYPES --*) /// How as-of dates should (or should not) be displayed with requests type AsOfDateDisplay = /// No as-of date should be displayed | NoDisplay /// The as-of date should be displayed in the culture's short date format | ShortDate /// The as-of date should be displayed in the culture's long date format | LongDate /// Convert this to a single-character code override this.ToString() = match this with | NoDisplay -> "N" | ShortDate -> "S" | LongDate -> "L" /// Create an AsOfDateDisplay from a single-character code static member Parse code = match code with | "N" -> NoDisplay | "S" -> ShortDate | "L" -> LongDate | _ -> invalidArg "code" $"Unknown code {code}" /// Acceptable e-mail formats type EmailFormat = /// HTML e-mail | HtmlFormat /// Plain-text e-mail | PlainTextFormat /// Convert this to a single-character code override this.ToString() = match this with | HtmlFormat -> "H" | PlainTextFormat -> "P" /// Create an EmailFormat from a single-character code static member Parse code = match code with | "H" -> HtmlFormat | "P" -> PlainTextFormat | _ -> invalidArg "code" $"Unknown code {code}" /// Expiration for requests type Expiration = /// Follow the rules for normal expiration | Automatic /// Do not expire via rules | Manual /// Force immediate expiration | Forced /// Convert this to a single-character code override this.ToString() = match this with | Automatic -> "A" | Manual -> "M" | Forced -> "F" /// Create an Expiration from a single-character code static member Parse code = match code with | "A" -> Automatic | "M" -> Manual | "F" -> Forced | _ -> invalidArg "code" $"Unknown code {code}" /// Types of prayer requests type PrayerRequestType = /// Current requests | CurrentRequest /// Long-term/ongoing request | LongTermRequest /// Expectant couples | Expecting /// Praise reports | PraiseReport /// Announcements | Announcement /// Convert this to a single-character code override this.ToString() = match this with | CurrentRequest -> "C" | LongTermRequest -> "L" | Expecting -> "E" | PraiseReport -> "P" | Announcement -> "A" /// Create a PrayerRequestType from a single-character code static member Parse code = match code with | "C" -> CurrentRequest | "L" -> LongTermRequest | "E" -> Expecting | "P" -> PraiseReport | "A" -> Announcement | _ -> invalidArg "code" $"Unknown code {code}" /// How requests should be sorted type RequestSort = /// Sort by date, then by requestor/subject | SortByDate /// Sort by requestor/subject, then by date | SortByRequestor /// Convert this to a single-character code override this.ToString() = match this with | SortByDate -> "D" | SortByRequestor -> "R" /// Create a RequestSort from a single-character code static member Parse code = match code with | "D" -> SortByDate | "R" -> SortByRequestor | _ -> invalidArg "code" $"Unknown code {code}" /// Type for a time zone ID type TimeZoneId = | TimeZoneId of string override this.ToString() = match this with | TimeZoneId it -> it open System /// PK type for the Church entity type ChurchId = | ChurchId of Guid /// The GUID value of the church ID member this.Value = this |> function | ChurchId guid -> guid override this.ToString() = this.Value.ToString "N" /// PK type for the Member entity type MemberId = | MemberId of Guid /// The GUID value of the member ID member this.Value = this |> function | MemberId guid -> guid override this.ToString() = this.Value.ToString "N" /// PK type for the PrayerRequest entity type PrayerRequestId = | PrayerRequestId of Guid /// The GUID value of the prayer request ID member this.Value = this |> function | PrayerRequestId guid -> guid override this.ToString() = this.Value.ToString "N" /// PK type for the SmallGroup entity type SmallGroupId = | SmallGroupId of Guid /// The GUID value of the small group ID member this.Value = this |> function | SmallGroupId guid -> guid override this.ToString() = this.Value.ToString "N" /// PK type for the User entity type UserId = | UserId of Guid /// The GUID value of the user ID member this.Value = this |> function | UserId guid -> guid override this.ToString() = this.Value.ToString "N" (*-- SPECIFIC VIEW TYPES --*) open Microsoft.Data.Sqlite /// Statistics for churches [] type ChurchStats = { /// The number of small groups in the church SmallGroups: int /// The number of prayer requests in the church PrayerRequests: int /// The number of users who can access small groups in the church Users: int } /// Information needed to display the public/protected request list and small group maintenance pages [] type SmallGroupInfo = { /// The ID of the small group Id: string /// The name of the small group Name: string /// The name of the church to which the small group belongs ChurchName: string /// The ID of the time zone for the small group TimeZoneId: TimeZoneId /// Whether the small group has a publicly-available request list IsPublic: bool } /// Map a row to a Small Group information set static member FromReader (rdr: SqliteDataReader) = { Id = Giraffe.ShortGuid.fromGuid ((rdr.GetOrdinal >> rdr.GetString >> Guid.Parse) "id") Name = (rdr.GetOrdinal >> rdr.GetString) "groupName" ChurchName = (rdr.GetOrdinal >> rdr.GetString) "churchName" TimeZoneId = (rdr.GetOrdinal >> rdr.GetString >> TimeZoneId) "timeZoneId" IsPublic = (rdr.GetOrdinal >> rdr.GetBoolean) "isPublic" } (*-- ENTITIES --*) open NodaTime /// This represents a church [] type Church = { /// The ID of this church Id: ChurchId /// The name of the church Name: string /// The city where the church is City: string /// The 2-letter state or province code for the church's location State: string /// Does this church have an active interface with Virtual Prayer Space? HasVpsInterface: bool /// The address for the interface InterfaceAddress: string option } /// An empty church // aww... how sad :( static member Empty = { Id = ChurchId Guid.Empty Name = "" City = "" State = "" HasVpsInterface = false InterfaceAddress = None } /// Preferences for the form and format of the prayer request list [] type ListPreferences = { /// The days after which regular requests expire DaysToExpire: int /// The number of days a new or updated request is considered new DaysToKeepNew: int /// The number of weeks after which long-term requests are flagged for follow-up LongTermUpdateWeeks: int /// The name from which e-mails are sent EmailFromName: string /// The e-mail address from which e-mails are sent EmailFromAddress: string /// The fonts to use in generating the list of prayer requests Fonts: string /// The color for the prayer request list headings HeadingColor: string /// The color for the lines offsetting the prayer request list headings LineColor: string /// The font size for the headings on the prayer request list HeadingFontSize: int /// The font size for the text on the prayer request list TextFontSize: int /// The order in which the prayer requests are sorted RequestSort: RequestSort /// The password used for "small group login" (view-only request list) GroupPassword: string /// The default e-mail type for this class DefaultEmailType: EmailFormat /// Whether this class makes its request list public IsPublic: bool /// The time zone which this class uses (use tzdata names) TimeZoneId: TimeZoneId /// The number of requests displayed per page PageSize: int /// How the as-of date should be automatically displayed AsOfDateDisplay: AsOfDateDisplay } /// The list of fonts to use when displaying request lists (converts "native" to native font stack) member this.FontStack = if this.Fonts = "native" then """system-ui,-apple-system,"Segoe UI",Roboto,Ubuntu,"Liberation Sans",Cantarell,"Helvetica Neue",sans-serif""" else this.Fonts /// A set of preferences with their default values static member Empty = { DaysToExpire = 14 DaysToKeepNew = 7 LongTermUpdateWeeks = 4 EmailFromName = "PrayerTracker" EmailFromAddress = "prayer@bitbadger.solutions" Fonts = "native" HeadingColor = "maroon" LineColor = "navy" HeadingFontSize = 16 TextFontSize = 12 RequestSort = SortByDate GroupPassword = "" DefaultEmailType = HtmlFormat IsPublic = false TimeZoneId = TimeZoneId "America/Denver" PageSize = 100 AsOfDateDisplay = NoDisplay } /// A member of a small group [] type Member = { /// The ID of the small group member Id: MemberId /// The Id of the small group to which this member belongs SmallGroupId: SmallGroupId /// The name of the member Name: string /// The e-mail address for the member Email: string /// The type of e-mail preferred by this member Format: EmailFormat option } /// An empty member static member Empty = { Id = MemberId Guid.Empty SmallGroupId = SmallGroupId Guid.Empty Name = "" Email = "" Format = None } /// This represents a small group (Sunday School class, Bible study group, etc.) [] type SmallGroup = { /// The ID of this small group Id: SmallGroupId /// The church to which this group belongs ChurchId: ChurchId /// The name of the group Name: string /// The preferences for the request list Preferences: ListPreferences } /// The DateTimeZone for the time zone ID for this small group member this.TimeZone = let tzId = string this.Preferences.TimeZoneId if DateTimeZoneProviders.Tzdb.Ids.Contains tzId then DateTimeZoneProviders.Tzdb[tzId] else DateTimeZone.Utc /// Get the local date/time for this group member this.LocalTimeNow(clock: IClock) = if isNull clock then nullArg (nameof clock) clock.GetCurrentInstant().InZone(this.TimeZone).LocalDateTime /// Get the local date for this group member this.LocalDateNow clock = this.LocalTimeNow(clock).Date /// An empty small group static member Empty = { Id = SmallGroupId Guid.Empty ChurchId = ChurchId Guid.Empty Name = "" Preferences = ListPreferences.Empty } /// This represents a single prayer request [] type PrayerRequest = { /// The ID of this request Id: PrayerRequestId /// The type of the request RequestType: PrayerRequestType /// The ID of the user who entered the request UserId: UserId /// The small group to which this request belongs SmallGroupId: SmallGroupId /// The date/time on which this request was entered EnteredDate: Instant /// The date/time this request was last updated UpdatedDate: Instant /// The name of the requestor or subject, or title of announcement Requestor: string option /// The text of the request Text: string /// Whether the chaplain should be notified for this request NotifyChaplain: bool /// Is this request expired? Expiration: Expiration } /// Is this request expired? member this.IsExpired (asOf: LocalDate) (group: SmallGroup) = match this.Expiration, this.RequestType with | Forced, _ -> true | Manual, _ | Automatic, LongTermRequest | Automatic, Expecting -> false | Automatic, _ -> // Automatic expiration Period .Between(this.UpdatedDate.InZone(group.TimeZone).Date, asOf, PeriodUnits.Days) .Days >= group.Preferences.DaysToExpire /// Is an update required for this long-term request? member this.UpdateRequired asOf group = if this.IsExpired asOf group then false else asOf.PlusWeeks -group.Preferences.LongTermUpdateWeeks >= this.UpdatedDate.InZone(group.TimeZone).Date /// An empty request static member Empty = { Id = PrayerRequestId Guid.Empty RequestType = CurrentRequest UserId = UserId Guid.Empty SmallGroupId = SmallGroupId Guid.Empty EnteredDate = Instant.MinValue UpdatedDate = Instant.MinValue Requestor = None Text = "" NotifyChaplain = false Expiration = Automatic } /// This represents a user of PrayerTracker [] type User = { /// The ID of this user Id: UserId /// The first name of this user FirstName: string /// The last name of this user LastName: string /// The e-mail address of the user Email: string /// Whether this user is a PrayerTracker system administrator IsAdmin: bool /// The user's hashed password PasswordHash: string /// The last time the user was seen (set whenever the user is loaded into a session) LastSeen: Instant option /// The small groups to which this user is authorized SmallGroups: SmallGroupId list } /// The full name of the user member this.Name = $"{this.FirstName} {this.LastName}" /// An empty user static member Empty = { Id = UserId Guid.Empty FirstName = "" LastName = "" Email = "" IsAdmin = false PasswordHash = "" LastSeen = None SmallGroups = [] }