Infra changes

Moved projects around; API still builds successfully
This commit is contained in:
Daniel J. Summers 2019-03-30 21:08:02 -05:00
parent d738321dc0
commit a11302afc3
29 changed files with 756 additions and 307 deletions

12
.gitignore vendored
View File

@ -253,11 +253,11 @@ paket-files/
*.sln.iml
# Compiled files / application
src/api/build
src/api/MyPrayerJournal.Api/wwwroot/favicon.ico
src/api/MyPrayerJournal.Api/wwwroot/index.html
src/api/MyPrayerJournal.Api/wwwroot/css
src/api/MyPrayerJournal.Api/wwwroot/js
src/api/MyPrayerJournal.Api/appsettings.development.json
src/build
src/MyPrayerJournal.Api/wwwroot/favicon.ico
src/MyPrayerJournal.Api/wwwroot/index.html
src/MyPrayerJournal.Api/wwwroot/css
src/MyPrayerJournal.Api/wwwroot/js
src/MyPrayerJournal.Api/appsettings.development.json
/build
src/*.exe

View File

@ -0,0 +1,156 @@
namespace MyPrayerJournal
open FSharp.Control.Tasks.V2.ContextInsensitive
open Microsoft.EntityFrameworkCore
open Microsoft.FSharpLu
/// Entity Framework configuration for myPrayerJournal
module internal EFConfig =
open FSharp.EFCore.OptionConverter
open System.Collections.Generic
/// Configure EF properties for all entity types
let configure (mb : ModelBuilder) =
mb.Entity<History> (
fun m ->
m.ToTable "history" |> ignore
m.HasKey ("requestId", "asOf") |> ignore
m.Property(fun e -> e.requestId).IsRequired () |> ignore
m.Property(fun e -> e.asOf).IsRequired () |> ignore
m.Property(fun e -> e.status).IsRequired() |> ignore
m.Property(fun e -> e.text) |> ignore)
|> ignore
mb.Model.FindEntityType(typeof<History>).FindProperty("text").SetValueConverter (OptionConverter<string> ())
mb.Entity<Note> (
fun m ->
m.ToTable "note" |> ignore
m.HasKey ("requestId", "asOf") |> ignore
m.Property(fun e -> e.requestId).IsRequired () |> ignore
m.Property(fun e -> e.asOf).IsRequired () |> ignore
m.Property(fun e -> e.notes).IsRequired () |> ignore)
|> ignore
mb.Entity<Request> (
fun m ->
m.ToTable "request" |> ignore
m.HasKey(fun e -> e.requestId :> obj) |> ignore
m.Property(fun e -> e.requestId).IsRequired () |> ignore
m.Property(fun e -> e.enteredOn).IsRequired () |> ignore
m.Property(fun e -> e.userId).IsRequired () |> ignore
m.Property(fun e -> e.snoozedUntil).IsRequired () |> ignore
m.Property(fun e -> e.showAfter).IsRequired () |> ignore
m.Property(fun e -> e.recurType).IsRequired() |> ignore
m.Property(fun e -> e.recurCount).IsRequired() |> ignore
m.HasMany(fun e -> e.history :> IEnumerable<History>)
.WithOne()
.HasForeignKey(fun e -> e.requestId :> obj)
|> ignore
m.HasMany(fun e -> e.notes :> IEnumerable<Note>)
.WithOne()
.HasForeignKey(fun e -> e.requestId :> obj)
|> ignore)
|> ignore
mb.Query<JournalRequest> (
fun m ->
m.ToView "journal" |> ignore
m.Ignore(fun e -> e.history :> obj) |> ignore
m.Ignore(fun e -> e.notes :> obj) |> ignore)
|> ignore
open System.Linq
/// Data context
type AppDbContext (opts : DbContextOptions<AppDbContext>) =
inherit DbContext (opts)
[<DefaultValue>]
val mutable private history : DbSet<History>
[<DefaultValue>]
val mutable private notes : DbSet<Note>
[<DefaultValue>]
val mutable private requests : DbSet<Request>
[<DefaultValue>]
val mutable private journal : DbQuery<JournalRequest>
member this.History
with get () = this.history
and set v = this.history <- v
member this.Notes
with get () = this.notes
and set v = this.notes <- v
member this.Requests
with get () = this.requests
and set v = this.requests <- v
member this.Journal
with get () = this.journal
and set v = this.journal <- v
override __.OnModelCreating (mb : ModelBuilder) =
base.OnModelCreating mb
EFConfig.configure mb
/// Register a disconnected entity with the context, having the given state
member private this.RegisterAs<'TEntity when 'TEntity : not struct> state e =
this.Entry<'TEntity>(e).State <- state
/// Add an entity instance to the context
member this.AddEntry e =
this.RegisterAs EntityState.Added e
/// Update the entity instance's values
member this.UpdateEntry e =
this.RegisterAs EntityState.Modified e
/// Retrieve all answered requests for the given user
member this.AnsweredRequests userId : JournalRequest seq =
upcast this.Journal
.Where(fun r -> r.userId = userId && r.lastStatus = "Answered")
.OrderByDescending(fun r -> r.asOf)
/// Retrieve the user's current journal
member this.JournalByUserId userId : JournalRequest seq =
upcast this.Journal
.Where(fun r -> r.userId = userId && r.lastStatus <> "Answered")
.OrderBy(fun r -> r.showAfter)
/// Retrieve a request by its ID and user ID
member this.TryRequestById reqId userId =
task {
let! req = this.Requests.AsNoTracking().FirstOrDefaultAsync(fun r -> r.requestId = reqId && r.userId = userId)
return Option.fromObject req
}
/// Retrieve notes for a request by its ID and user ID
member this.NotesById reqId userId =
task {
match! this.TryRequestById reqId userId with
| Some _ -> return this.Notes.AsNoTracking().Where(fun n -> n.requestId = reqId) |> List.ofSeq
| None -> return []
}
/// Retrieve a journal request by its ID and user ID
member this.TryJournalById reqId userId =
task {
let! req = this.Journal.FirstOrDefaultAsync(fun r -> r.requestId = reqId && r.userId = userId)
return Option.fromObject req
}
/// Retrieve a request, including its history and notes, by its ID and user ID
member this.TryFullRequestById requestId userId =
task {
match! this.TryJournalById requestId userId with
| Some req ->
let! fullReq =
this.Requests.AsNoTracking()
.Include(fun r -> r.history)
.Include(fun r -> r.notes)
.FirstOrDefaultAsync(fun r -> r.requestId = requestId && r.userId = userId)
match Option.fromObject fullReq with
| Some _ -> return Some { req with history = List.ofSeq fullReq.history; notes = List.ofSeq fullReq.notes }
| None -> return None
| None -> return None
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
@ -30,4 +30,8 @@
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyPrayerJournal.Domain\MyPrayerJournal.Domain.fsproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,114 @@
[<AutoOpen>]
/// Entities for use in the data model for myPrayerJournal
module MyPrayerJournal.Entities
open System.Collections.Generic
/// Type alias for a Collision-resistant Unique IDentifier
type Cuid = string
/// Request ID is a CUID
type RequestId = Cuid
/// User ID is a string (the "sub" part of the JWT)
type UserId = string
/// History is a record of action taken on a prayer request, including updates to its text
type [<CLIMutable; NoComparison; NoEquality>] History =
{ /// The ID of the request to which this history entry applies
requestId : RequestId
/// The time when this history entry was made
asOf : int64
/// The status for this history entry
status : string
/// The text of the update, if applicable
text : string option
}
with
/// An empty history entry
static member empty =
{ requestId = ""
asOf = 0L
status = ""
text = None
}
/// Note is a note regarding a prayer request that does not result in an update to its text
and [<CLIMutable; NoComparison; NoEquality>] Note =
{ /// The ID of the request to which this note applies
requestId : RequestId
/// The time when this note was made
asOf : int64
/// The text of the notes
notes : string
}
with
/// An empty note
static member empty =
{ requestId = ""
asOf = 0L
notes = ""
}
/// Request is the identifying record for a prayer request
and [<CLIMutable; NoComparison; NoEquality>] Request =
{ /// The ID of the request
requestId : RequestId
/// The time this request was initially entered
enteredOn : int64
/// The ID of the user to whom this request belongs ("sub" from the JWT)
userId : string
/// The time at which this request should reappear in the user's journal by manual user choice
snoozedUntil : int64
/// The time at which this request should reappear in the user's journal by recurrence
showAfter : int64
/// The type of recurrence for this request
recurType : string
/// How many of the recurrence intervals should occur between appearances in the journal
recurCount : int16
/// The history entries for this request
history : ICollection<History>
/// The notes for this request
notes : ICollection<Note>
}
with
/// An empty request
static member empty =
{ requestId = ""
enteredOn = 0L
userId = ""
snoozedUntil = 0L
showAfter = 0L
recurType = "immediate"
recurCount = 0s
history = List<History> ()
notes = List<Note> ()
}
/// JournalRequest is the form of a prayer request returned for the request journal display. It also contains
/// properties that may be filled for history and notes
[<CLIMutable; NoComparison; NoEquality>]
type JournalRequest =
{ /// The ID of the request
requestId : RequestId
/// The ID of the user to whom the request belongs
userId : string
/// The current text of the request
text : string
/// The last time action was taken on the request
asOf : int64
/// The last status for the request
lastStatus : string
/// The time that this request should reappear in the user's journal
snoozedUntil : int64
/// The time after which this request should reappear in the user's journal by configured recurrence
showAfter : int64
/// The type of recurrence for this request
recurType : string
/// How many of the recurrence intervals should occur between appearances in the journal
recurCount : int16
/// History entries for the request
history : History list
/// Note entries for the request
notes : Note list
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Entities.fs" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,19 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories) and given a Build Action of "AndroidAsset".
These files will be deployed with you package and will be accessible using Android's
AssetManager, like this:
public class ReadAsset : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
InputStream input = Assets.Open ("my_asset.txt");
}
}
Additionally, some Android functions will automatically load asset files:
Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");

View File

@ -0,0 +1,33 @@
namespace MyPrayerJournal.Mobile.Android
open System
open Android.App
open Android.Content
open Android.OS
open Android.Runtime
open Android.Views
open Android.Widget
type Resources = MyPrayerJournal.Mobile.Android.Resource
[<Activity (Label = "MyPrayerJournal.Mobile.Android", MainLauncher = true, Icon = "@mipmap/icon")>]
type MainActivity () =
inherit Activity ()
let mutable count:int = 1
override this.OnCreate (bundle) =
base.OnCreate (bundle)
// Set our view from the "main" layout resource
this.SetContentView (Resources.Layout.Main)
// Get our button from the layout resource, and attach an event to it
let button = this.FindViewById<Button>(Resources.Id.myButton)
button.Click.Add (fun args ->
button.Text <- sprintf "%d clicks!" count
count <- count + 1
)

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{F2A71F9B-5D33-465A-A702-920D77279786}</ProjectTypeGuids>
<ProjectGuid>49245e65-f905-4073-b057-3bceed076a1f</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>MyPrayerJournal.Mobile.Android</RootNamespace>
<AssemblyName>MyPrayerJournal.Mobile.Android</AssemblyName>
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
<AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
<Name>MyPrayerJournal.Mobile.Android</Name>
<SelectedDevice>test-5x</SelectedDevice>
<DefaultDevice>test-5x</DefaultDevice>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>True</DebugSymbols>
<DebugType>Full</DebugType>
<Optimize>False</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<AndroidLinkMode>None</AndroidLinkMode>
<ConsolePause>False</ConsolePause>
<Tailcalls>False</Tailcalls>
<PlatformTarget>
</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>PdbOnly</DebugType>
<DebugSymbols>True</DebugSymbols>
<Optimize>True</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<PlatformTarget>
</PlatformTarget>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<ConsolePause>False</ConsolePause>
<Tailcalls>True</Tailcalls>
<DefineConstants>
</DefineConstants>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<GenerateTailCalls>true</GenerateTailCalls>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.FSharp.targets" />
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<AndroidResource Include="Resources\layout\Main.axml" />
<AndroidResource Include="Resources\values\Strings.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\Icon.png" />
<None Include="Properties\AndroidManifest.xml" />
<Compile Include="Properties\AssemblyInfo.fs" />
<Compile Include="MainActivity.fs" />
<AndroidAsset Include="Assets\AboutAssets.txt" />
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Reference Include="FSharp.Core">
<HintPath>..\packages\FSharp.Core.4.0.0.1\lib\portable-net45+monoandroid10+monotouch10+xamarinios10\FSharp.Core.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="mscorlib" />
<Reference Include="Mono.Android" />
<Reference Include="Xamarin.Android.FSharp.ResourceProvider.Runtime">
<HintPath>..\packages\Xamarin.Android.FSharp.ResourceProvider.1.0.0.13\lib\Xamarin.Android.FSharp.ResourceProvider.Runtime.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyPrayerJournal.Domain\MyPrayerJournal.Domain.fsproj">
<Name>MyPrayerJournal.Domain</Name>
<Project>{6236760d-b21e-4187-9d0b-7d5e1c6ad896}</Project>
<Private>True</Private>
</ProjectReference>
<ProjectReference Include="..\MyPrayerJournal.Mobile.Core\MyPrayerJournal.Mobile.Core.fsproj">
<Name>MyPrayerJournal.Mobile.Core</Name>
<Project>{537d56c5-b55b-4f6c-af80-39f3bf4fd903}</Project>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
package="me.prayerjournal.android">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
<application android:allowBackup="true" android:icon="@mipmap/icon" android:label="@string/app_name">
</application>
</manifest>

View File

@ -0,0 +1,32 @@
namespace MyPrayerJournal.Mobile.Android
module AssemblyInfo =
open System.Reflection
open System.Runtime.CompilerServices
open Android.App
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[<assembly: AssemblyTitle("MyPrayerJournal.Mobile.Android")>]
[<assembly: AssemblyDescription("")>]
[<assembly: AssemblyConfiguration("")>]
[<assembly: AssemblyCompany("HP Inc.")>]
[<assembly: AssemblyProduct("MyPrayerJournal.Mobile.Android")>]
[<assembly: AssemblyCopyright("Copyright © HP Inc. 2019")>]
[<assembly: AssemblyTrademark("")>]
[<assembly: AssemblyCulture("")>]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[<assembly: AssemblyVersion("1.0.0.0")>]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[<assembly: AssemblyDelaySign(false)>]
//[<assembly: AssemblyKeyFile("")>]
()

View File

@ -0,0 +1,44 @@
Images, layout descriptions, binary blobs and string dictionaries can be included
in your application as resource files. Various Android APIs are designed to
operate on the resource IDs instead of dealing with images, strings or binary blobs
directly.
For example, a sample Android app that contains a user interface layout (main.axml),
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
would keep its resources in the "Resources" directory of the application:
Resources/
drawable/
icon.png
layout/
main.axml
values/
strings.xml
In order to get the build system to recognize Android resources, set the build action to
"AndroidResource". The native Android APIs do not operate directly with filenames, but
instead operate on resource IDs. When you compile an Android application that uses resources,
the build system will package the resources for distribution and generate a class called "R"
(this is an Android convention) that contains the tokens for each one of the resources
included. For example, for the above Resources layout, this is what the R class would expose:
public class R {
public class drawable {
public const int icon = 0x123;
}
public class layout {
public const int main = 0x456;
}
public class strings {
public const int first_string = 0xabc;
public const int second_string = 0xbcd;
}
}
You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
to reference the layout/main.axml file, or R.strings.first_string to reference the first
string in the dictionary file values/strings.xml.

View File

@ -0,0 +1,112 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
[assembly: global::Android.Runtime.ResourceDesignerAttribute("MyPrayerJournal.Mobile.Android.Resource", IsApplication=true)]
namespace MyPrayerJournal.Mobile.Android
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
static Resource()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
public static void UpdateIdValues()
{
}
public partial class Attribute
{
static Attribute()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Attribute()
{
}
}
public partial class Id
{
// aapt resource value: 0x7f050000
public const int myButton = 2131034112;
static Id()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Id()
{
}
}
public partial class Layout
{
// aapt resource value: 0x7f030000
public const int Main = 2130903040;
static Layout()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Layout()
{
}
}
public partial class Mipmap
{
// aapt resource value: 0x7f020000
public const int Icon = 2130837504;
static Mipmap()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Mipmap()
{
}
}
public partial class String
{
// aapt resource value: 0x7f040001
public const int app_name = 2130968577;
// aapt resource value: 0x7f040000
public const int hello = 2130968576;
static String()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
{
}
}
}
}
#pragma warning restore 1591

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/myButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, Click Me!</string>
<string name="app_name">MyPrayerJournal.Mobile.Android</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="FSharp.Core" version="4.0.0.1" targetFramework="monoandroid60" />
<package id="Xamarin.Android.FSharp.ResourceProvider" version="1.0.0.13" targetFramework="monoandroid60" />
</packages>

View File

@ -0,0 +1,5 @@
namespace MyPrayerJournal.Mobile.Core
module Say =
let hello name =
printfn "Hello %s" name

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
</Project>

85
src/MyPrayerJournal.sln Normal file
View File

@ -0,0 +1,85 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28721.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "MyPrayerJournal.Domain", "MyPrayerJournal.Domain\MyPrayerJournal.Domain.fsproj", "{6236760D-B21E-4187-9D0B-7D5E1C6AD896}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "MyPrayerJournal.Api", "MyPrayerJournal.Api\MyPrayerJournal.Api.fsproj", "{1887D1E1-544A-4F54-B266-38E7867DC842}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MyPrayerJournal.Mobile.Core", "MyPrayerJournal.Mobile.Core\MyPrayerJournal.Mobile.Core.fsproj", "{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MyPrayerJournal.Mobile.Android", "MyPrayerJournal.Mobile.Android\MyPrayerJournal.Mobile.Android.fsproj", "{49245E65-F905-4073-B057-3BCEED076A1F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|iPhone = Debug|iPhone
Debug|iPhoneSimulator = Debug|iPhoneSimulator
Release|Any CPU = Release|Any CPU
Release|iPhone = Release|iPhone
Release|iPhoneSimulator = Release|iPhoneSimulator
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Debug|iPhone.Build.0 = Debug|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Release|Any CPU.Build.0 = Release|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Release|iPhone.ActiveCfg = Release|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Release|iPhone.Build.0 = Release|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{6236760D-B21E-4187-9D0B-7D5E1C6AD896}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Debug|iPhone.Build.0 = Debug|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Release|Any CPU.Build.0 = Release|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Release|iPhone.ActiveCfg = Release|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Release|iPhone.Build.0 = Release|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{1887D1E1-544A-4F54-B266-38E7867DC842}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Debug|Any CPU.Build.0 = Debug|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Debug|iPhone.Build.0 = Debug|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Release|Any CPU.ActiveCfg = Release|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Release|Any CPU.Build.0 = Release|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Release|iPhone.ActiveCfg = Release|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Release|iPhone.Build.0 = Release|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{537D56C5-B55B-4F6C-AF80-39F3BF4FD903}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|iPhone.Build.0 = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|iPhone.Deploy.0 = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|Any CPU.Build.0 = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|Any CPU.Deploy.0 = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|iPhone.ActiveCfg = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|iPhone.Build.0 = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|iPhone.Deploy.0 = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{49245E65-F905-4073-B057-3BCEED076A1F}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8E2447D9-52F0-4A0D-BB61-A83C19353D7C}
EndGlobalSection
EndGlobal

View File

@ -1,275 +0,0 @@
namespace MyPrayerJournal
open FSharp.Control.Tasks.V2.ContextInsensitive
open Microsoft.EntityFrameworkCore
open Microsoft.FSharpLu
/// Entities for use in the data model for myPrayerJournal
[<AutoOpen>]
module Entities =
open FSharp.EFCore.OptionConverter
open System.Collections.Generic
/// Type alias for a Collision-resistant Unique IDentifier
type Cuid = string
/// Request ID is a CUID
type RequestId = Cuid
/// User ID is a string (the "sub" part of the JWT)
type UserId = string
/// History is a record of action taken on a prayer request, including updates to its text
type [<CLIMutable; NoComparison; NoEquality>] History =
{ /// The ID of the request to which this history entry applies
requestId : RequestId
/// The time when this history entry was made
asOf : int64
/// The status for this history entry
status : string
/// The text of the update, if applicable
text : string option
}
with
/// An empty history entry
static member empty =
{ requestId = ""
asOf = 0L
status = ""
text = None
}
static member configureEF (mb : ModelBuilder) =
mb.Entity<History> (
fun m ->
m.ToTable "history" |> ignore
m.HasKey ("requestId", "asOf") |> ignore
m.Property(fun e -> e.requestId).IsRequired () |> ignore
m.Property(fun e -> e.asOf).IsRequired () |> ignore
m.Property(fun e -> e.status).IsRequired() |> ignore
m.Property(fun e -> e.text) |> ignore)
|> ignore
let typ = mb.Model.FindEntityType(typeof<History>)
let prop = typ.FindProperty("text")
mb.Model.FindEntityType(typeof<History>).FindProperty("text").SetValueConverter (OptionConverter<string> ())
/// Note is a note regarding a prayer request that does not result in an update to its text
and [<CLIMutable; NoComparison; NoEquality>] Note =
{ /// The ID of the request to which this note applies
requestId : RequestId
/// The time when this note was made
asOf : int64
/// The text of the notes
notes : string
}
with
/// An empty note
static member empty =
{ requestId = ""
asOf = 0L
notes = ""
}
static member configureEF (mb : ModelBuilder) =
mb.Entity<Note> (
fun m ->
m.ToTable "note" |> ignore
m.HasKey ("requestId", "asOf") |> ignore
m.Property(fun e -> e.requestId).IsRequired () |> ignore
m.Property(fun e -> e.asOf).IsRequired () |> ignore
m.Property(fun e -> e.notes).IsRequired () |> ignore)
|> ignore
/// Request is the identifying record for a prayer request
and [<CLIMutable; NoComparison; NoEquality>] Request =
{ /// The ID of the request
requestId : RequestId
/// The time this request was initially entered
enteredOn : int64
/// The ID of the user to whom this request belongs ("sub" from the JWT)
userId : string
/// The time at which this request should reappear in the user's journal by manual user choice
snoozedUntil : int64
/// The time at which this request should reappear in the user's journal by recurrence
showAfter : int64
/// The type of recurrence for this request
recurType : string
/// How many of the recurrence intervals should occur between appearances in the journal
recurCount : int16
/// The history entries for this request
history : ICollection<History>
/// The notes for this request
notes : ICollection<Note>
}
with
/// An empty request
static member empty =
{ requestId = ""
enteredOn = 0L
userId = ""
snoozedUntil = 0L
showAfter = 0L
recurType = "immediate"
recurCount = 0s
history = List<History> ()
notes = List<Note> ()
}
static member configureEF (mb : ModelBuilder) =
mb.Entity<Request> (
fun m ->
m.ToTable "request" |> ignore
m.HasKey(fun e -> e.requestId :> obj) |> ignore
m.Property(fun e -> e.requestId).IsRequired () |> ignore
m.Property(fun e -> e.enteredOn).IsRequired () |> ignore
m.Property(fun e -> e.userId).IsRequired () |> ignore
m.Property(fun e -> e.snoozedUntil).IsRequired () |> ignore
m.Property(fun e -> e.showAfter).IsRequired () |> ignore
m.Property(fun e -> e.recurType).IsRequired() |> ignore
m.Property(fun e -> e.recurCount).IsRequired() |> ignore
m.HasMany(fun e -> e.history :> IEnumerable<History>)
.WithOne()
.HasForeignKey(fun e -> e.requestId :> obj)
|> ignore
m.HasMany(fun e -> e.notes :> IEnumerable<Note>)
.WithOne()
.HasForeignKey(fun e -> e.requestId :> obj)
|> ignore)
|> ignore
/// JournalRequest is the form of a prayer request returned for the request journal display. It also contains
/// properties that may be filled for history and notes
[<CLIMutable; NoComparison; NoEquality>]
type JournalRequest =
{ /// The ID of the request
requestId : RequestId
/// The ID of the user to whom the request belongs
userId : string
/// The current text of the request
text : string
/// The last time action was taken on the request
asOf : int64
/// The last status for the request
lastStatus : string
/// The time that this request should reappear in the user's journal
snoozedUntil : int64
/// The time after which this request should reappear in the user's journal by configured recurrence
showAfter : int64
/// The type of recurrence for this request
recurType : string
/// How many of the recurrence intervals should occur between appearances in the journal
recurCount : int16
/// History entries for the request
history : History list
/// Note entries for the request
notes : Note list
}
with
static member configureEF (mb : ModelBuilder) =
mb.Query<JournalRequest> (
fun m ->
m.ToView "journal" |> ignore
m.Ignore(fun e -> e.history :> obj) |> ignore
m.Ignore(fun e -> e.notes :> obj) |> ignore)
|> ignore
open System.Linq
/// Data context
type AppDbContext (opts : DbContextOptions<AppDbContext>) =
inherit DbContext (opts)
[<DefaultValue>]
val mutable private history : DbSet<History>
[<DefaultValue>]
val mutable private notes : DbSet<Note>
[<DefaultValue>]
val mutable private requests : DbSet<Request>
[<DefaultValue>]
val mutable private journal : DbQuery<JournalRequest>
member this.History
with get () = this.history
and set v = this.history <- v
member this.Notes
with get () = this.notes
and set v = this.notes <- v
member this.Requests
with get () = this.requests
and set v = this.requests <- v
member this.Journal
with get () = this.journal
and set v = this.journal <- v
override __.OnModelCreating (mb : ModelBuilder) =
base.OnModelCreating mb
[ History.configureEF
Note.configureEF
Request.configureEF
JournalRequest.configureEF
]
|> List.iter (fun x -> x mb)
/// Register a disconnected entity with the context, having the given state
member private this.RegisterAs<'TEntity when 'TEntity : not struct> state e =
this.Entry<'TEntity>(e).State <- state
/// Add an entity instance to the context
member this.AddEntry e =
this.RegisterAs EntityState.Added e
/// Update the entity instance's values
member this.UpdateEntry e =
this.RegisterAs EntityState.Modified e
/// Retrieve all answered requests for the given user
member this.AnsweredRequests userId : JournalRequest seq =
upcast this.Journal
.Where(fun r -> r.userId = userId && r.lastStatus = "Answered")
.OrderByDescending(fun r -> r.asOf)
/// Retrieve the user's current journal
member this.JournalByUserId userId : JournalRequest seq =
upcast this.Journal
.Where(fun r -> r.userId = userId && r.lastStatus <> "Answered")
.OrderBy(fun r -> r.showAfter)
/// Retrieve a request by its ID and user ID
member this.TryRequestById reqId userId =
task {
let! req = this.Requests.AsNoTracking().FirstOrDefaultAsync(fun r -> r.requestId = reqId && r.userId = userId)
return Option.fromObject req
}
/// Retrieve notes for a request by its ID and user ID
member this.NotesById reqId userId =
task {
match! this.TryRequestById reqId userId with
| Some _ -> return this.Notes.AsNoTracking().Where(fun n -> n.requestId = reqId) |> List.ofSeq
| None -> return []
}
/// Retrieve a journal request by its ID and user ID
member this.TryJournalById reqId userId =
task {
let! req = this.Journal.FirstOrDefaultAsync(fun r -> r.requestId = reqId && r.userId = userId)
return Option.fromObject req
}
/// Retrieve a request, including its history and notes, by its ID and user ID
member this.TryFullRequestById requestId userId =
task {
match! this.TryJournalById requestId userId with
| Some req ->
let! fullReq =
this.Requests.AsNoTracking()
.Include(fun r -> r.history)
.Include(fun r -> r.notes)
.FirstOrDefaultAsync(fun r -> r.requestId = requestId && r.userId = userId)
match Option.fromObject fullReq with
| Some _ -> return Some { req with history = List.ofSeq fullReq.history; notes = List.ofSeq fullReq.notes }
| None -> return None
| None -> return None
}

View File

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2035
MinimumVisualStudioVersion = 10.0.40219.1
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "MyPrayerJournal.Api", "MyPrayerJournal.Api\MyPrayerJournal.Api.fsproj", "{E0E5240C-00DC-428A-899A-DA4F06625B8A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E0E5240C-00DC-428A-899A-DA4F06625B8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0E5240C-00DC-428A-899A-DA4F06625B8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0E5240C-00DC-428A-899A-DA4F06625B8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0E5240C-00DC-428A-899A-DA4F06625B8A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7EAB6243-94B3-49A5-BA64-7F01B8BE7CB9}
EndGlobalSection
EndGlobal