Huge repo reorganization

In no particular order...
- Created projects using F# generator, using Paket and FAKE
- Split "entities" into their own project, and created interface for
data functions required on those entities
- Renamed "data" project and used it as an implementation of data access
- Created "logic" layer that takes the data interface, and does the
non-persistence-related manipulation of items
- Moved "web" project to "app", and modified Nancy modules to utilize
Logic project and data interface instead of Data project and RethinkDB
connection
- Created test placeholder project; will be filling that out shortly
(TAD?)
This commit is contained in:
Daniel J. Summers 2016-08-06 13:55:49 -05:00
parent 8194649018
commit 710004dfc4
53 changed files with 1418 additions and 572 deletions

4
.gitignore vendored
View File

@ -21,6 +21,8 @@ bld/
[Bb]in/ [Bb]in/
[Oo]bj/ [Oo]bj/
[Ll]og/ [Ll]og/
[Bb]uild/
[Dd]eploy/
# Visual Studio 2015 cache/options directory # Visual Studio 2015 cache/options directory
.vs/ .vs/
@ -241,7 +243,7 @@ FakesAssemblies/
_Pvt_Extensions _Pvt_Extensions
# Paket dependency manager # Paket dependency manager
.paket/paket.exe **/.paket/paket.exe
paket-files/ paket-files/
# FAKE - F# Make # FAKE - F# Make

Binary file not shown.

View File

@ -1,12 +1,13 @@
namespace MyWebLog namespace MyWebLog
open MyWebLog.Data.WebLog open MyWebLog.Data
open MyWebLog.Entities open MyWebLog.Entities
open MyWebLog.Logic.WebLog
open Nancy open Nancy
open RethinkDb.Driver.Net open RethinkDb.Driver.Net
/// Handle /admin routes /// Handle /admin routes
type AdminModule(conn : IConnection) as this = type AdminModule(data : IMyWebLogData) as this =
inherit NancyModule("/admin") inherit NancyModule("/admin")
do do
@ -15,6 +16,6 @@ type AdminModule(conn : IConnection) as this =
/// Admin dashboard /// Admin dashboard
member this.Dashboard () = member this.Dashboard () =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let model = DashboardModel(this.Context, this.WebLog, findDashboardCounts conn this.WebLog.Id) let model = DashboardModel(this.Context, this.WebLog, findDashboardCounts data this.WebLog.Id)
model.PageTitle <- Resources.Dashboard model.PageTitle <- Resources.Dashboard
upcast this.View.["admin/dashboard", model] upcast this.View.["admin/dashboard", model]

View File

@ -2,9 +2,9 @@
open MyWebLog open MyWebLog
open MyWebLog.Data open MyWebLog.Data
open MyWebLog.Data.SetUp open MyWebLog.Data.RethinkDB
open MyWebLog.Data.WebLog
open MyWebLog.Entities open MyWebLog.Entities
open MyWebLog.Logic.WebLog
open Nancy open Nancy
open Nancy.Authentication.Forms open Nancy.Authentication.Forms
open Nancy.Bootstrapper open Nancy.Bootstrapper
@ -13,6 +13,7 @@ open Nancy.Cryptography
open Nancy.Owin open Nancy.Owin
open Nancy.Security open Nancy.Security
open Nancy.Session.Persistable open Nancy.Session.Persistable
//open Nancy.Session.Relational
open Nancy.Session.RethinkDb open Nancy.Session.RethinkDb
open Nancy.TinyIoc open Nancy.TinyIoc
open Nancy.ViewEngines.SuperSimpleViewEngine open Nancy.ViewEngines.SuperSimpleViewEngine
@ -21,6 +22,7 @@ open RethinkDb.Driver.Net
open Suave open Suave
open Suave.Owin open Suave.Owin
open System open System
open System.Configuration
open System.IO open System.IO
open System.Text.RegularExpressions open System.Text.RegularExpressions
@ -28,9 +30,11 @@ open System.Text.RegularExpressions
let cfg = try AppConfig.FromJson (System.IO.File.ReadAllText "config.json") let cfg = try AppConfig.FromJson (System.IO.File.ReadAllText "config.json")
with ex -> raise <| ApplicationException(Resources.ErrBadAppConfig, ex) with ex -> raise <| ApplicationException(Resources.ErrBadAppConfig, ex)
let data : IMyWebLogData = upcast RethinkMyWebLogData(cfg.DataConfig.Conn, cfg.DataConfig)
do do
startUpCheck cfg.DataConfig data.SetUp ()
/// Support RESX lookup via the @Translate SSVE alias /// Support RESX lookup via the @Translate SSVE alias
type TranslateTokenViewEngineMatcher() = type TranslateTokenViewEngineMatcher() =
static let regex = Regex("@Translate\.(?<TranslationKey>[a-zA-Z0-9-_]+);?", RegexOptions.Compiled) static let regex = Regex("@Translate\.(?<TranslationKey>[a-zA-Z0-9-_]+);?", RegexOptions.Compiled)
@ -83,10 +87,10 @@ type MyWebLogBootstrapper() =
override this.ApplicationStartup (container, pipelines) = override this.ApplicationStartup (container, pipelines) =
base.ApplicationStartup (container, pipelines) base.ApplicationStartup (container, pipelines)
// Data configuration (both config and the connection; Nancy modules just need the connection) // Application configuration
container.Register<AppConfig>(cfg) container.Register<AppConfig>(cfg)
|> ignore |> ignore
container.Register<IConnection>(cfg.DataConfig.Conn) container.Register<IMyWebLogData>(data)
|> ignore |> ignore
// NodaTime // NodaTime
container.Register<IClock>(SystemClock.Instance) container.Register<IClock>(SystemClock.Instance)
@ -110,6 +114,7 @@ type MyWebLogBootstrapper() =
// Sessions // Sessions
let sessions = RethinkDbSessionConfiguration(cfg.DataConfig.Conn) let sessions = RethinkDbSessionConfiguration(cfg.DataConfig.Conn)
sessions.Database <- cfg.DataConfig.Database sessions.Database <- cfg.DataConfig.Database
//let sessions = RelationalSessionConfiguration(ConfigurationManager.ConnectionStrings.["SessionStore"].ConnectionString)
PersistableSessions.Enable (pipelines, sessions) PersistableSessions.Enable (pipelines, sessions)
() ()
@ -127,7 +132,7 @@ type RequestEnvironment() =
member this.Initialize (pipelines, context) = member this.Initialize (pipelines, context) =
let establishEnv (ctx : NancyContext) = let establishEnv (ctx : NancyContext) =
ctx.Items.[Keys.RequestStart] <- DateTime.Now.Ticks ctx.Items.[Keys.RequestStart] <- DateTime.Now.Ticks
match tryFindWebLogByUrlBase cfg.DataConfig.Conn ctx.Request.Url.HostName with match tryFindWebLogByUrlBase data ctx.Request.Url.HostName with
| Some webLog -> ctx.Items.[Keys.WebLog] <- webLog | Some webLog -> ctx.Items.[Keys.WebLog] <- webLog
| None -> // TODO: redirect to domain set up page | None -> // TODO: redirect to domain set up page
ApplicationException (sprintf "%s %s" ctx.Request.Url.HostName Resources.ErrNotConfigured) ApplicationException (sprintf "%s %s" ctx.Request.Url.HostName Resources.ErrNotConfigured)

View File

@ -1,6 +1,6 @@
namespace MyWebLog namespace MyWebLog
open MyWebLog.Data open MyWebLog.Data.RethinkDB
open Newtonsoft.Json open Newtonsoft.Json
open System.Text open System.Text

View File

@ -1,6 +1,7 @@
namespace MyWebLog namespace MyWebLog
open MyWebLog.Data.Category open MyWebLog.Data
open MyWebLog.Logic.Category
open MyWebLog.Entities open MyWebLog.Entities
open Nancy open Nancy
open Nancy.ModelBinding open Nancy.ModelBinding
@ -8,7 +9,7 @@ open Nancy.Security
open RethinkDb.Driver.Net open RethinkDb.Driver.Net
/// Handle /category and /categories URLs /// Handle /category and /categories URLs
type CategoryModule(conn : IConnection) as this = type CategoryModule(data : IMyWebLogData) as this =
inherit NancyModule() inherit NancyModule()
do do
@ -21,7 +22,7 @@ type CategoryModule(conn : IConnection) as this =
member this.CategoryList () = member this.CategoryList () =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let model = CategoryListModel(this.Context, this.WebLog, let model = CategoryListModel(this.Context, this.WebLog,
(getAllCategories conn this.WebLog.Id (findAllCategories data this.WebLog.Id
|> List.map (fun cat -> IndentedCategory.Create cat (fun _ -> false)))) |> List.map (fun cat -> IndentedCategory.Create cat (fun _ -> false))))
upcast this.View.["/admin/category/list", model] upcast this.View.["/admin/category/list", model]
@ -31,13 +32,13 @@ type CategoryModule(conn : IConnection) as this =
let catId = parameters.["id"].ToString () let catId = parameters.["id"].ToString ()
match (match catId with match (match catId with
| "new" -> Some Category.Empty | "new" -> Some Category.Empty
| _ -> tryFindCategory conn this.WebLog.Id catId) with | _ -> tryFindCategory data this.WebLog.Id catId) with
| Some cat -> let model = CategoryEditModel(this.Context, this.WebLog, cat) | Some cat -> let model = CategoryEditModel(this.Context, this.WebLog, cat)
model.Categories <- getAllCategories conn this.WebLog.Id model.Categories <- findAllCategories data this.WebLog.Id
|> List.map (fun cat -> IndentedCategory.Create cat |> List.map (fun cat -> IndentedCategory.Create cat
(fun c -> c = defaultArg (fst cat).ParentId "")) (fun c -> c = defaultArg (fst cat).ParentId ""))
upcast this.View.["admin/category/edit", model] upcast this.View.["admin/category/edit", model]
| None -> this.NotFound () | _ -> this.NotFound ()
/// Save a category /// Save a category
member this.SaveCategory (parameters : DynamicDictionary) = member this.SaveCategory (parameters : DynamicDictionary) =
@ -45,41 +46,43 @@ type CategoryModule(conn : IConnection) as this =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let catId = parameters.["id"].ToString () let catId = parameters.["id"].ToString ()
let form = this.Bind<CategoryForm> () let form = this.Bind<CategoryForm> ()
let oldCat = match catId with "new" -> Some Category.Empty | _ -> tryFindCategory conn this.WebLog.Id catId let oldCat = match catId with
| "new" -> Some { Category.Empty with WebLogId = this.WebLog.Id }
| _ -> tryFindCategory data this.WebLog.Id catId
match oldCat with match oldCat with
| Some old -> let cat = { old with Name = form.Name | Some old -> let cat = { old with Name = form.Name
Slug = form.Slug Slug = form.Slug
Description = match form.Description with "" -> None | d -> Some d Description = match form.Description with "" -> None | d -> Some d
ParentId = match form.ParentId with "" -> None | p -> Some p } ParentId = match form.ParentId with "" -> None | p -> Some p }
let newCatId = saveCategory conn this.WebLog.Id cat let newCatId = saveCategory data cat
match old.ParentId = cat.ParentId with match old.ParentId = cat.ParentId with
| true -> () | true -> ()
| _ -> match old.ParentId with | _ -> match old.ParentId with
| Some parentId -> removeCategoryFromParent conn this.WebLog.Id parentId newCatId | Some parentId -> removeCategoryFromParent data this.WebLog.Id parentId newCatId
| None -> () | _ -> ()
match cat.ParentId with match cat.ParentId with
| Some parentId -> addCategoryToParent conn this.WebLog.Id parentId newCatId | Some parentId -> addCategoryToParent data this.WebLog.Id parentId newCatId
| None -> () | _ -> ()
let model = MyWebLogModel(this.Context, this.WebLog) let model = MyWebLogModel(this.Context, this.WebLog)
{ UserMessage.Empty with { UserMessage.Empty with
Level = Level.Info Level = Level.Info
Message = System.String.Format Message = System.String.Format
(Resources.MsgCategoryEditSuccess, (Resources.MsgCategoryEditSuccess,
(match catId with | "new" -> Resources.Added | _ -> Resources.Updated)) } (match catId with "new" -> Resources.Added | _ -> Resources.Updated)) }
|> model.AddMessage |> model.AddMessage
this.Redirect (sprintf "/category/%s/edit" newCatId) model this.Redirect (sprintf "/category/%s/edit" newCatId) model
| None -> this.NotFound () | _ -> this.NotFound ()
/// Delete a category /// Delete a category
member this.DeleteCategory (parameters : DynamicDictionary) = member this.DeleteCategory (parameters : DynamicDictionary) =
this.ValidateCsrfToken () this.ValidateCsrfToken ()
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let catId = parameters.["id"].ToString () let catId = parameters.["id"].ToString ()
match tryFindCategory conn this.WebLog.Id catId with match tryFindCategory data this.WebLog.Id catId with
| Some cat -> deleteCategory conn cat | Some cat -> deleteCategory data cat
let model = MyWebLogModel(this.Context, this.WebLog) let model = MyWebLogModel(this.Context, this.WebLog)
{ UserMessage.Empty with Level = Level.Info { UserMessage.Empty with Level = Level.Info
Message = System.String.Format(Resources.MsgCategoryDeleted, cat.Name) } Message = System.String.Format(Resources.MsgCategoryDeleted, cat.Name) }
|> model.AddMessage |> model.AddMessage
this.Redirect "/categories" model this.Redirect "/categories" model
| None -> this.NotFound () | _ -> this.NotFound ()

View File

@ -0,0 +1,302 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" 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>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>9cea3a8b-e8aa-44e6-9f5f-2095ceed54eb</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>MyWebLog.App</RootNamespace>
<AssemblyName>MyWebLog.App</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
<Name>MyWebLog.App</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\MyWebLog.App.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Release\MyWebLog.App.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.ServiceModel" />
</ItemGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="Keys.fs" />
<Compile Include="AppConfig.fs" />
<Compile Include="ViewModels.fs" />
<Compile Include="ModuleExtensions.fs" />
<Compile Include="AdminModule.fs" />
<Compile Include="CategoryModule.fs" />
<Compile Include="PageModule.fs" />
<Compile Include="PostModule.fs" />
<Compile Include="UserModule.fs" />
<Compile Include="App.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyWebLog.Data.RethinkDB\MyWebLog.Data.RethinkDB.fsproj">
<Name>MyWebLog.Data.RethinkDB</Name>
<Project>{d6c2be5e-883a-4f34-9905-b730543ca380}</Project>
<Private>True</Private>
</ProjectReference>
<ProjectReference Include="..\MyWebLog.Entities\MyWebLog.Entities.fsproj">
<Name>MyWebLog.Entities</Name>
<Project>{a87f3cf5-2189-442b-8acf-929f5153ac22}</Project>
<Private>True</Private>
</ProjectReference>
<ProjectReference Include="..\MyWebLog.Logic\MyWebLog.Logic.fsproj">
<Name>MyWebLog.Logic</Name>
<Project>{29f6eda3-4f43-4bb3-9c63-ae238a9b7f12}</Project>
<Private>True</Private>
</ProjectReference>
<ProjectReference Include="..\MyWebLog.Resources\MyWebLog.Resources.csproj">
<Name>MyWebLog.Resources</Name>
<Project>{a12ea8da-88bc-4447-90cb-a0e2dcc37523}</Project>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '11.0'">
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Common.Logging">
<HintPath>..\packages\Common.Logging\lib\net40\Common.Logging.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Common.Logging.Core">
<HintPath>..\packages\Common.Logging.Core\lib\net40\Common.Logging.Core.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And $(TargetFrameworkVersion) == 'v4.0'">
<ItemGroup>
<Reference Include="FSharp.Compiler.Service">
<HintPath>..\packages\FSharp.Compiler.Service\lib\net40\FSharp.Compiler.Service.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="FSharp.Compiler.Service">
<HintPath>..\packages\FSharp.Compiler.Service\lib\net45\FSharp.Compiler.Service.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="CSharpFormat">
<HintPath>..\packages\FSharp.Formatting\lib\net40\CSharpFormat.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
<Reference Include="FSharp.CodeFormat">
<HintPath>..\packages\FSharp.Formatting\lib\net40\FSharp.CodeFormat.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
<Reference Include="FSharp.Formatting.Common">
<HintPath>..\packages\FSharp.Formatting\lib\net40\FSharp.Formatting.Common.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
<Reference Include="FSharp.Literate">
<HintPath>..\packages\FSharp.Formatting\lib\net40\FSharp.Literate.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
<Reference Include="FSharp.Markdown">
<HintPath>..\packages\FSharp.Formatting\lib\net40\FSharp.Markdown.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
<Reference Include="FSharp.MetadataFormat">
<HintPath>..\packages\FSharp.Formatting\lib\net40\FSharp.MetadataFormat.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
<Reference Include="RazorEngine">
<HintPath>..\packages\FSharp.Formatting\lib\net40\RazorEngine.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
<Reference Include="System.Web.Razor">
<HintPath>..\packages\FSharp.Formatting\lib\net40\System.Web.Razor.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="FSharpVSPowerTools.Core">
<HintPath>..\packages\FSharpVSPowerTools.Core\lib\net45\FSharpVSPowerTools.Core.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Nancy">
<HintPath>..\packages\Nancy\lib\net40\Nancy.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Nancy.Authentication.Forms">
<HintPath>..\packages\Nancy.Authentication.Forms\lib\net40\Nancy.Authentication.Forms.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And $(TargetFrameworkVersion) == 'v4.5.2'">
<ItemGroup>
<Reference Include="Nancy.Session.Persistable">
<HintPath>..\packages\Nancy.Session.Persistable\lib\net452\Nancy.Session.Persistable.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And $(TargetFrameworkVersion) == 'v4.5.2'">
<ItemGroup>
<Reference Include="Nancy.Session.RethinkDb">
<HintPath>..\packages\Nancy.Session.RethinkDB\lib\net452\Nancy.Session.RethinkDb.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And $(TargetFrameworkVersion) == 'v4.0'">
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="NodaTime">
<HintPath>..\packages\NodaTime\lib\net35-Client\NodaTime.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
<Reference Include="System.Xml">
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="RethinkDb.Driver">
<HintPath>..\packages\RethinkDb.Driver\lib\net45\RethinkDb.Driver.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Suave">
<HintPath>..\packages\Suave\lib\net40\Suave.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
</Project>

View File

@ -1,8 +1,9 @@
namespace MyWebLog namespace MyWebLog
open FSharp.Markdown open FSharp.Markdown
open MyWebLog.Data.Page open MyWebLog.Data
open MyWebLog.Entities open MyWebLog.Entities
open MyWebLog.Logic.Page
open Nancy open Nancy
open Nancy.ModelBinding open Nancy.ModelBinding
open Nancy.Security open Nancy.Security
@ -10,7 +11,7 @@ open NodaTime
open RethinkDb.Driver.Net open RethinkDb.Driver.Net
/// Handle /pages and /page URLs /// Handle /pages and /page URLs
type PageModule(conn : IConnection, clock : IClock) as this = type PageModule(data : IMyWebLogData, clock : IClock) as this =
inherit NancyModule() inherit NancyModule()
do do
@ -22,7 +23,7 @@ type PageModule(conn : IConnection, clock : IClock) as this =
/// List all pages /// List all pages
member this.PageList () = member this.PageList () =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let model = PagesModel(this.Context, this.WebLog, (findAllPages conn this.WebLog.Id let model = PagesModel(this.Context, this.WebLog, (findAllPages data this.WebLog.Id
|> List.map (fun p -> PageForDisplay(this.WebLog, p)))) |> List.map (fun p -> PageForDisplay(this.WebLog, p))))
model.PageTitle <- Resources.Pages model.PageTitle <- Resources.Pages
upcast this.View.["admin/page/list", model] upcast this.View.["admin/page/list", model]
@ -33,16 +34,16 @@ type PageModule(conn : IConnection, clock : IClock) as this =
let pageId = parameters.["id"].ToString () let pageId = parameters.["id"].ToString ()
match (match pageId with match (match pageId with
| "new" -> Some Page.Empty | "new" -> Some Page.Empty
| _ -> tryFindPage conn this.WebLog.Id pageId) with | _ -> tryFindPage data this.WebLog.Id pageId) with
| Some page -> let rev = match page.Revisions | Some page -> let rev = match page.Revisions
|> List.sortByDescending (fun r -> r.AsOf) |> List.sortByDescending (fun r -> r.AsOf)
|> List.tryHead with |> List.tryHead with
| Some r -> r | Some r -> r
| None -> Revision.Empty | _ -> Revision.Empty
let model = EditPageModel(this.Context, this.WebLog, page, rev) let model = EditPageModel(this.Context, this.WebLog, page, rev)
model.PageTitle <- match pageId with "new" -> Resources.AddNewPage | _ -> Resources.EditPage model.PageTitle <- match pageId with "new" -> Resources.AddNewPage | _ -> Resources.EditPage
upcast this.View.["admin/page/edit", model] upcast this.View.["admin/page/edit", model]
| None -> this.NotFound () | _ -> this.NotFound ()
/// Save a page /// Save a page
member this.SavePage (parameters : DynamicDictionary) = member this.SavePage (parameters : DynamicDictionary) =
@ -51,7 +52,7 @@ type PageModule(conn : IConnection, clock : IClock) as this =
let pageId = parameters.["id"].ToString () let pageId = parameters.["id"].ToString ()
let form = this.Bind<EditPageForm> () let form = this.Bind<EditPageForm> ()
let now = clock.Now.Ticks let now = clock.Now.Ticks
match (match pageId with "new" -> Some Page.Empty | _ -> tryFindPage conn this.WebLog.Id pageId) with match (match pageId with "new" -> Some Page.Empty | _ -> tryFindPage data this.WebLog.Id pageId) with
| Some p -> let page = match pageId with "new" -> { p with WebLogId = this.WebLog.Id } | _ -> p | Some p -> let page = match pageId with "new" -> { p with WebLogId = this.WebLog.Id } | _ -> p
let pId = { p with let pId = { p with
Title = form.Title Title = form.Title
@ -64,7 +65,7 @@ type PageModule(conn : IConnection, clock : IClock) as this =
Revisions = { AsOf = now Revisions = { AsOf = now
SourceType = form.Source SourceType = form.Source
Text = form.Text } :: page.Revisions } Text = form.Text } :: page.Revisions }
|> savePage conn |> savePage data
let model = MyWebLogModel(this.Context, this.WebLog) let model = MyWebLogModel(this.Context, this.WebLog)
{ UserMessage.Empty with { UserMessage.Empty with
Level = Level.Info Level = Level.Info
@ -73,18 +74,18 @@ type PageModule(conn : IConnection, clock : IClock) as this =
(match pageId with "new" -> Resources.Added | _ -> Resources.Updated)) } (match pageId with "new" -> Resources.Added | _ -> Resources.Updated)) }
|> model.AddMessage |> model.AddMessage
this.Redirect (sprintf "/page/%s/edit" pId) model this.Redirect (sprintf "/page/%s/edit" pId) model
| None -> this.NotFound () | _ -> this.NotFound ()
/// Delete a page /// Delete a page
member this.DeletePage (parameters : DynamicDictionary) = member this.DeletePage (parameters : DynamicDictionary) =
this.ValidateCsrfToken () this.ValidateCsrfToken ()
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let pageId = parameters.["id"].ToString () let pageId = parameters.["id"].ToString ()
match tryFindPageWithoutRevisions conn this.WebLog.Id pageId with match tryFindPageWithoutRevisions data this.WebLog.Id pageId with
| Some page -> deletePage conn page.WebLogId page.Id | Some page -> deletePage data page.WebLogId page.Id
let model = MyWebLogModel(this.Context, this.WebLog) let model = MyWebLogModel(this.Context, this.WebLog)
{ UserMessage.Empty with Level = Level.Info { UserMessage.Empty with Level = Level.Info
Message = Resources.MsgPageDeleted } Message = Resources.MsgPageDeleted }
|> model.AddMessage |> model.AddMessage
this.Redirect "/pages" model this.Redirect "/pages" model
| None -> this.NotFound () | _ -> this.NotFound ()

View File

@ -1,10 +1,11 @@
namespace MyWebLog namespace MyWebLog
open FSharp.Markdown open FSharp.Markdown
open MyWebLog.Data.Category open MyWebLog.Data
open MyWebLog.Data.Page
open MyWebLog.Data.Post
open MyWebLog.Entities open MyWebLog.Entities
open MyWebLog.Logic.Category
open MyWebLog.Logic.Page
open MyWebLog.Logic.Post
open Nancy open Nancy
open Nancy.ModelBinding open Nancy.ModelBinding
open Nancy.Security open Nancy.Security
@ -15,7 +16,7 @@ open System
open System.ServiceModel.Syndication open System.ServiceModel.Syndication
/// Routes dealing with posts (including the home page, /tag, /category, RSS, and catch-all routes) /// Routes dealing with posts (including the home page, /tag, /category, RSS, and catch-all routes)
type PostModule(conn : IConnection, clock : IClock) as this = type PostModule(data : IMyWebLogData, clock : IClock) as this =
inherit NancyModule() inherit NancyModule()
/// Get the page number from the dictionary /// Get the page number from the dictionary
@ -27,14 +28,14 @@ type PostModule(conn : IConnection, clock : IClock) as this =
/// Generate an RSS/Atom feed of the latest posts /// Generate an RSS/Atom feed of the latest posts
let generateFeed format : obj = let generateFeed format : obj =
let posts = findFeedPosts conn this.WebLog.Id 10 let posts = findFeedPosts data this.WebLog.Id 10
let feed = let feed =
SyndicationFeed( SyndicationFeed(
this.WebLog.Name, defaultArg this.WebLog.Subtitle null, this.WebLog.Name, defaultArg this.WebLog.Subtitle null,
Uri(sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.UrlBase), null, Uri(sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.UrlBase), null,
(match posts |> List.tryHead with (match posts |> List.tryHead with
| Some (post, _) -> Instant(post.UpdatedOn).ToDateTimeOffset () | Some (post, _) -> Instant(post.UpdatedOn).ToDateTimeOffset ()
| _ -> System.DateTimeOffset(System.DateTime.MinValue)), | _ -> System.DateTimeOffset(System.DateTime.MinValue)),
posts posts
|> List.map (fun (post, user) -> |> List.map (fun (post, user) ->
let item = let item =
@ -76,15 +77,15 @@ type PostModule(conn : IConnection, clock : IClock) as this =
member this.PublishedPostsPage pageNbr = member this.PublishedPostsPage pageNbr =
let model = PostsModel(this.Context, this.WebLog) let model = PostsModel(this.Context, this.WebLog)
model.PageNbr <- pageNbr model.PageNbr <- pageNbr
model.Posts <- findPageOfPublishedPosts conn this.WebLog.Id pageNbr 10 |> forDisplay model.Posts <- findPageOfPublishedPosts data this.WebLog.Id pageNbr 10 |> forDisplay
model.HasNewer <- match pageNbr with model.HasNewer <- match pageNbr with
| 1 -> false | 1 -> false
| _ -> match List.isEmpty model.Posts with | _ -> match List.isEmpty model.Posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindNewerPost conn (List.last model.Posts).Post | _ -> Option.isSome <| tryFindNewerPost data (List.last model.Posts).Post
model.HasOlder <- match List.isEmpty model.Posts with model.HasOlder <- match List.isEmpty model.Posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindOlderPost conn (List.head model.Posts).Post | _ -> Option.isSome <| tryFindOlderPost data (List.head model.Posts).Post
model.UrlPrefix <- "/posts" model.UrlPrefix <- "/posts"
model.PageTitle <- match pageNbr with 1 -> "" | _ -> sprintf "%s%i" Resources.PageHash pageNbr model.PageTitle <- match pageNbr with 1 -> "" | _ -> sprintf "%s%i" Resources.PageHash pageNbr
this.ThemedView "index" model this.ThemedView "index" model
@ -93,59 +94,59 @@ type PostModule(conn : IConnection, clock : IClock) as this =
member this.HomePage () = member this.HomePage () =
match this.WebLog.DefaultPage with match this.WebLog.DefaultPage with
| "posts" -> this.PublishedPostsPage 1 | "posts" -> this.PublishedPostsPage 1
| pageId -> match tryFindPageWithoutRevisions conn this.WebLog.Id pageId with | pageId -> match tryFindPageWithoutRevisions data this.WebLog.Id pageId with
| Some page -> let model = PageModel(this.Context, this.WebLog, page) | Some page -> let model = PageModel(this.Context, this.WebLog, page)
model.PageTitle <- page.Title model.PageTitle <- page.Title
this.ThemedView "page" model this.ThemedView "page" model
| None -> this.NotFound () | _ -> this.NotFound ()
/// Derive a post or page from the URL, or redirect from a prior URL to the current one /// Derive a post or page from the URL, or redirect from a prior URL to the current one
member this.CatchAll (parameters : DynamicDictionary) = member this.CatchAll (parameters : DynamicDictionary) =
let url = parameters.["permalink"].ToString () let url = parameters.["permalink"].ToString ()
match tryFindPostByPermalink conn this.WebLog.Id url with match tryFindPostByPermalink data this.WebLog.Id url with
| Some post -> // Hopefully the most common result; the permalink is a permalink! | Some post -> // Hopefully the most common result; the permalink is a permalink!
let model = PostModel(this.Context, this.WebLog, post) let model = PostModel(this.Context, this.WebLog, post)
model.NewerPost <- tryFindNewerPost conn post model.NewerPost <- tryFindNewerPost data post
model.OlderPost <- tryFindOlderPost conn post model.OlderPost <- tryFindOlderPost data post
model.PageTitle <- post.Title model.PageTitle <- post.Title
this.ThemedView "single" model this.ThemedView "single" model
| None -> // Maybe it's a page permalink instead... | _ -> // Maybe it's a page permalink instead...
match tryFindPageByPermalink conn this.WebLog.Id url with match tryFindPageByPermalink data this.WebLog.Id url with
| Some page -> // ...and it is! | Some page -> // ...and it is!
let model = PageModel(this.Context, this.WebLog, page) let model = PageModel(this.Context, this.WebLog, page)
model.PageTitle <- page.Title model.PageTitle <- page.Title
this.ThemedView "page" model this.ThemedView "page" model
| None -> // Maybe it's an old permalink for a post | _ -> // Maybe it's an old permalink for a post
match tryFindPostByPriorPermalink conn this.WebLog.Id url with match tryFindPostByPriorPermalink data this.WebLog.Id url with
| Some post -> // Redirect them to the proper permalink | Some post -> // Redirect them to the proper permalink
upcast this.Response.AsRedirect(sprintf "/%s" post.Permalink) upcast this.Response.AsRedirect(sprintf "/%s" post.Permalink)
.WithStatusCode HttpStatusCode.MovedPermanently .WithStatusCode HttpStatusCode.MovedPermanently
| None -> this.NotFound () | _ -> this.NotFound ()
/// Display categorized posts /// Display categorized posts
member this.CategorizedPosts (parameters : DynamicDictionary) = member this.CategorizedPosts (parameters : DynamicDictionary) =
let slug = parameters.["slug"].ToString () let slug = parameters.["slug"].ToString ()
match tryFindCategoryBySlug conn this.WebLog.Id slug with match tryFindCategoryBySlug data this.WebLog.Id slug with
| Some cat -> let pageNbr = getPage parameters | Some cat -> let pageNbr = getPage parameters
let model = PostsModel(this.Context, this.WebLog) let model = PostsModel(this.Context, this.WebLog)
model.PageNbr <- pageNbr model.PageNbr <- pageNbr
model.Posts <- findPageOfCategorizedPosts conn this.WebLog.Id cat.Id pageNbr 10 |> forDisplay model.Posts <- findPageOfCategorizedPosts data this.WebLog.Id cat.Id pageNbr 10 |> forDisplay
model.HasNewer <- match List.isEmpty model.Posts with model.HasNewer <- match List.isEmpty model.Posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindNewerCategorizedPost conn cat.Id | _ -> Option.isSome <| tryFindNewerCategorizedPost data cat.Id
(List.head model.Posts).Post (List.head model.Posts).Post
model.HasOlder <- match List.isEmpty model.Posts with model.HasOlder <- match List.isEmpty model.Posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindOlderCategorizedPost conn cat.Id | _ -> Option.isSome <| tryFindOlderCategorizedPost data cat.Id
(List.last model.Posts).Post (List.last model.Posts).Post
model.UrlPrefix <- sprintf "/category/%s" slug model.UrlPrefix <- sprintf "/category/%s" slug
model.PageTitle <- sprintf "\"%s\" Category%s" cat.Name model.PageTitle <- sprintf "\"%s\" Category%s" cat.Name
(match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n) (match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n)
model.Subtitle <- Some <| match cat.Description with model.Subtitle <- Some <| match cat.Description with
| Some desc -> desc | Some desc -> desc
| None -> sprintf "Posts in the \"%s\" category" cat.Name | _ -> sprintf "Posts in the \"%s\" category" cat.Name
this.ThemedView "index" model this.ThemedView "index" model
| None -> this.NotFound () | _ -> this.NotFound ()
/// Display tagged posts /// Display tagged posts
member this.TaggedPosts (parameters : DynamicDictionary) = member this.TaggedPosts (parameters : DynamicDictionary) =
@ -153,13 +154,13 @@ type PostModule(conn : IConnection, clock : IClock) as this =
let pageNbr = getPage parameters let pageNbr = getPage parameters
let model = PostsModel(this.Context, this.WebLog) let model = PostsModel(this.Context, this.WebLog)
model.PageNbr <- pageNbr model.PageNbr <- pageNbr
model.Posts <- findPageOfTaggedPosts conn this.WebLog.Id tag pageNbr 10 |> forDisplay model.Posts <- findPageOfTaggedPosts data this.WebLog.Id tag pageNbr 10 |> forDisplay
model.HasNewer <- match List.isEmpty model.Posts with model.HasNewer <- match List.isEmpty model.Posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindNewerTaggedPost conn tag (List.head model.Posts).Post | _ -> Option.isSome <| tryFindNewerTaggedPost data tag (List.head model.Posts).Post
model.HasOlder <- match List.isEmpty model.Posts with model.HasOlder <- match List.isEmpty model.Posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindOlderTaggedPost conn tag (List.last model.Posts).Post | _ -> Option.isSome <| tryFindOlderTaggedPost data tag (List.last model.Posts).Post
model.UrlPrefix <- sprintf "/tag/%s" tag model.UrlPrefix <- sprintf "/tag/%s" tag
model.PageTitle <- sprintf "\"%s\" Tag%s" tag (match pageNbr with 1 -> "" | n -> sprintf " | Page %i" n) model.PageTitle <- sprintf "\"%s\" Tag%s" tag (match pageNbr with 1 -> "" | n -> sprintf " | Page %i" n)
model.Subtitle <- Some <| sprintf "Posts tagged \"%s\"" tag model.Subtitle <- Some <| sprintf "Posts tagged \"%s\"" tag
@ -182,7 +183,7 @@ type PostModule(conn : IConnection, clock : IClock) as this =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let model = PostsModel(this.Context, this.WebLog) let model = PostsModel(this.Context, this.WebLog)
model.PageNbr <- pageNbr model.PageNbr <- pageNbr
model.Posts <- findPageOfAllPosts conn this.WebLog.Id pageNbr 25 |> forDisplay model.Posts <- findPageOfAllPosts data this.WebLog.Id pageNbr 25 |> forDisplay
model.HasNewer <- pageNbr > 1 model.HasNewer <- pageNbr > 1
model.HasOlder <- List.length model.Posts > 24 model.HasOlder <- List.length model.Posts > 24
model.UrlPrefix <- "/posts/list" model.UrlPrefix <- "/posts/list"
@ -193,21 +194,21 @@ type PostModule(conn : IConnection, clock : IClock) as this =
member this.EditPost (parameters : DynamicDictionary) = member this.EditPost (parameters : DynamicDictionary) =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let postId = parameters.["postId"].ToString () let postId = parameters.["postId"].ToString ()
match (match postId with "new" -> Some Post.Empty | _ -> tryFindPost conn this.WebLog.Id postId) with match (match postId with "new" -> Some Post.Empty | _ -> tryFindPost data this.WebLog.Id postId) with
| Some post -> let rev = match post.Revisions | Some post -> let rev = match post.Revisions
|> List.sortByDescending (fun r -> r.AsOf) |> List.sortByDescending (fun r -> r.AsOf)
|> List.tryHead with |> List.tryHead with
| Some r -> r | Some r -> r
| None -> Revision.Empty | None -> Revision.Empty
let model = EditPostModel(this.Context, this.WebLog, post, rev) let model = EditPostModel(this.Context, this.WebLog, post, rev)
model.Categories <- getAllCategories conn this.WebLog.Id model.Categories <- findAllCategories data this.WebLog.Id
|> List.map (fun cat -> string (fst cat).Id, |> List.map (fun cat -> string (fst cat).Id,
sprintf "%s%s" sprintf "%s%s"
(String.replicate (snd cat) " &nbsp; &nbsp; ") (String.replicate (snd cat) " &nbsp; &nbsp; ")
(fst cat).Name) (fst cat).Name)
model.PageTitle <- match post.Id with "new" -> Resources.AddNewPost | _ -> Resources.EditPost model.PageTitle <- match post.Id with "new" -> Resources.AddNewPost | _ -> Resources.EditPost
upcast this.View.["admin/post/edit"] upcast this.View.["admin/post/edit"]
| None -> this.NotFound () | _ -> this.NotFound ()
/// Save a post /// Save a post
member this.SavePost (parameters : DynamicDictionary) = member this.SavePost (parameters : DynamicDictionary) =
@ -216,7 +217,7 @@ type PostModule(conn : IConnection, clock : IClock) as this =
let postId = parameters.["postId"].ToString () let postId = parameters.["postId"].ToString ()
let form = this.Bind<EditPostForm>() let form = this.Bind<EditPostForm>()
let now = clock.Now.Ticks let now = clock.Now.Ticks
match (match postId with "new" -> Some Post.Empty | _ -> tryFindPost conn this.WebLog.Id postId) with match (match postId with "new" -> Some Post.Empty | _ -> tryFindPost data this.WebLog.Id postId) with
| Some p -> let justPublished = p.PublishedOn = int64 0 && form.PublishNow | Some p -> let justPublished = p.PublishedOn = int64 0 && form.PublishNow
let post = match postId with let post = match postId with
| "new" -> { p with | "new" -> { p with
@ -242,14 +243,14 @@ type PostModule(conn : IConnection, clock : IClock) as this =
Revisions = { AsOf = now Revisions = { AsOf = now
SourceType = form.Source SourceType = form.Source
Text = form.Text } :: post.Revisions } Text = form.Text } :: post.Revisions }
|> savePost conn |> savePost data
let model = MyWebLogModel(this.Context, this.WebLog) let model = MyWebLogModel(this.Context, this.WebLog)
{ UserMessage.Empty with { UserMessage.Empty with
Level = Level.Info Level = Level.Info
Message = System.String.Format Message = System.String.Format
(Resources.MsgPostEditSuccess, (Resources.MsgPostEditSuccess,
(match postId with | "new" -> Resources.Added | _ -> Resources.Updated), (match postId with "new" -> Resources.Added | _ -> Resources.Updated),
(match justPublished with | true -> Resources.AndPublished | _ -> "")) } (match justPublished with true -> Resources.AndPublished | _ -> "")) }
|> model.AddMessage |> model.AddMessage
this.Redirect (sprintf "/post/%s/edit" pId) model this.Redirect (sprintf "/post/%s/edit" pId) model
| None -> this.NotFound () | _ -> this.NotFound ()

View File

@ -1,7 +1,8 @@
namespace MyWebLog namespace MyWebLog
open MyWebLog.Data.User open MyWebLog.Data
open MyWebLog.Entities open MyWebLog.Entities
open MyWebLog.Logic.User
open Nancy open Nancy
open Nancy.Authentication.Forms open Nancy.Authentication.Forms
open Nancy.Cryptography open Nancy.Cryptography
@ -12,7 +13,7 @@ open RethinkDb.Driver.Net
open System.Text open System.Text
/// Handle /user URLs /// Handle /user URLs
type UserModule(conn : IConnection, cfg : AppConfig) as this = type UserModule(data : IMyWebLogData, cfg : AppConfig) as this =
inherit NancyModule("/user") inherit NancyModule("/user")
/// Hash the user's password /// Hash the user's password
@ -37,7 +38,7 @@ type UserModule(conn : IConnection, cfg : AppConfig) as this =
this.ValidateCsrfToken () this.ValidateCsrfToken ()
let form = this.Bind<LogOnForm> () let form = this.Bind<LogOnForm> ()
let model = MyWebLogModel(this.Context, this.WebLog) let model = MyWebLogModel(this.Context, this.WebLog)
match tryUserLogOn conn form.Email (pbkdf2 form.Password) with match tryUserLogOn data form.Email (pbkdf2 form.Password) with
| Some user -> this.Session.[Keys.User] <- user | Some user -> this.Session.[Keys.User] <- user
{ UserMessage.Empty with Level = Level.Info { UserMessage.Empty with Level = Level.Info
Message = Resources.MsgLogOnSuccess } Message = Resources.MsgLogOnSuccess }
@ -46,10 +47,10 @@ type UserModule(conn : IConnection, cfg : AppConfig) as this =
// TODO: investigate if addMessage should update the session when it's called // TODO: investigate if addMessage should update the session when it's called
upcast this.LoginAndRedirect (System.Guid.Parse user.Id, upcast this.LoginAndRedirect (System.Guid.Parse user.Id,
fallbackRedirectUrl = defaultArg (Option.ofObj form.ReturnUrl) "/") fallbackRedirectUrl = defaultArg (Option.ofObj form.ReturnUrl) "/")
| None -> { UserMessage.Empty with Level = Level.Error | _ -> { UserMessage.Empty with Level = Level.Error
Message = Resources.ErrBadLogOnAttempt } Message = Resources.ErrBadLogOnAttempt }
|> model.AddMessage |> model.AddMessage
this.Redirect (sprintf "/user/logon?returnUrl=%s" form.ReturnUrl) model this.Redirect (sprintf "/user/logon?returnUrl=%s" form.ReturnUrl) model
/// Log a user off /// Log a user off
member this.LogOff () = member this.LogOff () =

View File

@ -1,7 +1,7 @@
namespace MyWebLog namespace MyWebLog
open MyWebLog.Data.WebLog
open MyWebLog.Entities open MyWebLog.Entities
open MyWebLog.Logic.WebLog
open Nancy open Nancy
open Nancy.Session.Persistable open Nancy.Session.Persistable
open Newtonsoft.Json open Newtonsoft.Json

View File

@ -0,0 +1,7 @@
FSharp.Formatting
Nancy
Nancy.Authentication.Forms
Nancy.Session.RethinkDB
NodaTime
RethinkDb.Driver
Suave

View File

@ -4,17 +4,17 @@ open System.Reflection
open System.Runtime.CompilerServices open System.Runtime.CompilerServices
open System.Runtime.InteropServices open System.Runtime.InteropServices
[<assembly: AssemblyTitle("MyWebLog.Data")>] [<assembly: AssemblyTitle("MyWebLog.Data.RethinkDB")>]
[<assembly: AssemblyDescription("Data access for myWebLog")>] [<assembly: AssemblyDescription("RethinkDB data access for myWebLog")>]
[<assembly: AssemblyConfiguration("")>] [<assembly: AssemblyConfiguration("")>]
[<assembly: AssemblyCompany("DJS Consulting")>] [<assembly: AssemblyCompany("DJS Consulting")>]
[<assembly: AssemblyProduct("MyWebLog.Data")>] [<assembly: AssemblyProduct("MyWebLog.Data.RethinkDB")>]
[<assembly: AssemblyCopyright("Copyright © 2016")>] [<assembly: AssemblyCopyright("Copyright © 2016")>]
[<assembly: AssemblyTrademark("")>] [<assembly: AssemblyTrademark("")>]
[<assembly: AssemblyCulture("")>] [<assembly: AssemblyCulture("")>]
[<assembly: ComVisible(false)>] [<assembly: ComVisible(false)>]
[<assembly: Guid("1fba0b84-b09e-4b16-b9b6-5730dea27192")>] [<assembly: Guid("1fba0b84-b09e-4b16-b9b6-5730dea27192")>]
[<assembly: AssemblyVersion("0.9.1.0")>] [<assembly: AssemblyVersion("0.9.2.0")>]
[<assembly: AssemblyFileVersion("1.0.0.0")>] [<assembly: AssemblyFileVersion("1.0.0.0")>]
do do

View File

@ -1,8 +1,7 @@
module MyWebLog.Data.Category module MyWebLog.Data.RethinkDB.Category
open FSharp.Interop.Dynamic open FSharp.Interop.Dynamic
open MyWebLog.Entities open MyWebLog.Entities
open Rethink
open RethinkDb.Driver.Ast open RethinkDb.Driver.Ast
open System.Dynamic open System.Dynamic
@ -14,20 +13,6 @@ let private category (webLogId : string) (catId : string) =
.Get(catId) .Get(catId)
.Filter(fun c -> c.["WebLogId"].Eq(webLogId)) .Filter(fun c -> c.["WebLogId"].Eq(webLogId))
/// Sort categories by their name, with their children sorted below them, including an indent level
let sortCategories categories =
let rec getChildren (cat : Category) indent =
seq {
yield cat, indent
for child in categories |> List.filter (fun c -> c.ParentId = Some cat.Id) do
yield! getChildren child (indent + 1)
}
categories
|> List.filter (fun c -> c.ParentId.IsNone)
|> List.map (fun c -> getChildren c 0)
|> Seq.collect id
|> Seq.toList
/// Get all categories for a web log /// Get all categories for a web log
let getAllCategories conn (webLogId : string) = let getAllCategories conn (webLogId : string) =
r.Table(Table.Category) r.Table(Table.Category)
@ -36,7 +21,6 @@ let getAllCategories conn (webLogId : string) =
.RunListAsync<Category>(conn) .RunListAsync<Category>(conn)
|> await |> await
|> Seq.toList |> Seq.toList
|> sortCategories
/// Get a specific category by its Id /// Get a specific category by its Id
let tryFindCategory conn webLogId catId : Category option = let tryFindCategory conn webLogId catId : Category option =
@ -45,52 +29,41 @@ let tryFindCategory conn webLogId catId : Category option =
| null -> None | null -> None
| cat -> Some <| unbox cat | cat -> Some <| unbox cat
/// Save a category /// Add a category
let saveCategory conn webLogId (cat : Category) = let addCategory conn (cat : Category) =
match cat.Id with r.Table(Table.Category)
| "new" -> let newCat = { cat with Id = string <| System.Guid.NewGuid() .Insert(cat)
WebLogId = webLogId } .RunResultAsync(conn) |> await |> ignore
r.Table(Table.Category)
.Insert(newCat)
.RunResultAsync(conn) |> await |> ignore
newCat.Id
| _ -> let upd8 = ExpandoObject()
upd8?Name <- cat.Name
upd8?Slug <- cat.Slug
upd8?Description <- cat.Description
upd8?ParentId <- cat.ParentId
(category webLogId cat.Id)
.Update(upd8)
.RunResultAsync(conn) |> await |> ignore
cat.Id
/// Remove a category from a given parent /// Update a category
let removeCategoryFromParent conn webLogId parentId catId = let updateCategory conn (cat : Category) =
match tryFindCategory conn webLogId parentId with let upd8 = ExpandoObject()
| Some parent -> let upd8 = ExpandoObject() upd8?Name <- cat.Name
upd8?Children <- parent.Children upd8?Slug <- cat.Slug
|> List.filter (fun childId -> childId <> catId) upd8?Description <- cat.Description
(category webLogId parentId) upd8?ParentId <- cat.ParentId
.Update(upd8) (category cat.WebLogId cat.Id)
.RunResultAsync(conn) |> await |> ignore .Update(upd8)
| None -> () .RunResultAsync(conn) |> await |> ignore
/// Add a category to a given parent /// Update a category's children
let addCategoryToParent conn webLogId parentId catId = let updateChildren conn webLogId parentId (children : string list) =
match tryFindCategory conn webLogId parentId with let upd8 = ExpandoObject()
| Some parent -> let upd8 = ExpandoObject() upd8?Children <- children
upd8?Children <- catId :: parent.Children (category webLogId parentId)
(category webLogId parentId) .Update(upd8)
.Update(upd8) .RunResultAsync(conn) |> await |> ignore
.RunResultAsync(conn) |> await |> ignore
| None -> ()
/// Delete a category /// Delete a category
let deleteCategory conn cat = let deleteCategory conn cat =
// Remove the category from its parent // Remove the category from its parent
match cat.ParentId with match cat.ParentId with
| Some parentId -> removeCategoryFromParent conn cat.WebLogId parentId cat.Id | Some parentId -> match tryFindCategory conn cat.WebLogId parentId with
| None -> () | Some parent -> parent.Children
|> List.filter (fun childId -> childId <> cat.Id)
|> updateChildren conn cat.WebLogId parentId
| _ -> ()
| _ -> ()
// Move this category's children to its parent // Move this category's children to its parent
let newParent = ExpandoObject() let newParent = ExpandoObject()
newParent?ParentId <- cat.ParentId newParent?ParentId <- cat.ParentId

View File

@ -1,4 +1,4 @@
namespace MyWebLog.Data namespace MyWebLog.Data.RethinkDB
open RethinkDb.Driver open RethinkDb.Driver
open RethinkDb.Driver.Net open RethinkDb.Driver.Net

View File

@ -1,4 +1,5 @@
module MyWebLog.Data.Rethink [<AutoOpen>]
module MyWebLog.Data.RethinkDB.Extensions
open RethinkDb.Driver.Ast open RethinkDb.Driver.Ast
open RethinkDb.Driver.Net open RethinkDb.Driver.Net

View File

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" 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>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>d6c2be5e-883a-4f34-9905-b730543ca380</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>MyWebLog.Data.RethinkDB</RootNamespace>
<AssemblyName>MyWebLog.Data.RethinkDB</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
<Name>MyWebLog.Data.RethinkDB</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\MyWebLog.Data.RethinkDB.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Release\MyWebLog.Data.RethinkDB.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="Extensions.fs" />
<Compile Include="Table.fs" />
<Compile Include="DataConfig.fs" />
<Compile Include="Category.fs" />
<Compile Include="Page.fs" />
<Compile Include="Post.fs" />
<Compile Include="User.fs" />
<Compile Include="WebLog.fs" />
<Compile Include="SetUp.fs" />
<Compile Include="RethinkMyWebLogData.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyWebLog.Entities\MyWebLog.Entities.fsproj">
<Name>MyWebLog.Entities</Name>
<Project>{a87f3cf5-2189-442b-8acf-929f5153ac22}</Project>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '11.0'">
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Common.Logging">
<HintPath>..\packages\Common.Logging\lib\net40\Common.Logging.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Common.Logging.Core">
<HintPath>..\packages\Common.Logging.Core\lib\net40\Common.Logging.Core.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Dynamitey">
<HintPath>..\packages\Dynamitey\lib\net40\Dynamitey.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="FSharp.Interop.Dynamic">
<HintPath>..\packages\FSharp.Interop.Dynamic\lib\portable-net45+sl50+win\FSharp.Interop.Dynamic.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And $(TargetFrameworkVersion) == 'v4.0'">
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="RethinkDb.Driver">
<HintPath>..\packages\RethinkDb.Driver\lib\net45\RethinkDb.Driver.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
</Project>

View File

@ -0,0 +1,70 @@
module MyWebLog.Data.RethinkDB.Page
open FSharp.Interop.Dynamic
open MyWebLog.Entities
open RethinkDb.Driver.Ast
open System.Dynamic
let private r = RethinkDb.Driver.RethinkDB.R
/// Try to find a page by its Id, optionally including revisions
let tryFindPageById conn webLogId (pageId : string) includeRevs =
let pg = r.Table(Table.Page)
.Get(pageId)
match (match includeRevs with
| true -> pg.RunAtomAsync<Page>(conn)
| _ -> pg.Without("Revisions").RunAtomAsync<Page>(conn)
|> await |> box) with
| null -> None
| page -> let pg : Page = unbox page
match pg.WebLogId = webLogId with true -> Some pg | _ -> None
/// Find a page by its permalink
let tryFindPageByPermalink conn (webLogId : string) (permalink : string) =
r.Table(Table.Page)
.GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink")
.Without("Revisions")
.RunCursorAsync<Page>(conn)
|> await
|> Seq.tryHead
/// Get a list of all pages (excludes page text and revisions)
let findAllPages conn (webLogId : string) =
r.Table(Table.Page)
.GetAll(webLogId).OptArg("index", "WebLogId")
.OrderBy("Title")
.Without("Text", "Revisions")
.RunListAsync<Page>(conn)
|> await
|> Seq.toList
/// Add a page
let addPage conn (page : Page) =
r.Table(Table.Page)
.Insert(page)
.RunResultAsync(conn) |> await |> ignore
/// Update a page
let updatePage conn (page : Page) =
match tryFindPageById conn page.WebLogId page.Id false with
| Some _ -> let upd8 = ExpandoObject()
upd8?Title <- page.Title
upd8?Permalink <- page.Permalink
upd8?PublishedOn <- page.PublishedOn
upd8?UpdatedOn <- page.UpdatedOn
upd8?Text <- page.Text
upd8?Revisions <- page.Revisions
r.Table(Table.Page)
.Get(page.Id)
.Update(upd8)
.RunResultAsync(conn) |> await |> ignore
| _ -> ()
/// Delete a page
let deletePage conn webLogId pageId =
match tryFindPageById conn webLogId pageId false with
| Some _ -> r.Table(Table.Page)
.Get(pageId)
.Delete()
.RunResultAsync(conn) |> await |> ignore
| _ -> ()

View File

@ -1,8 +1,7 @@
module MyWebLog.Data.Post module MyWebLog.Data.RethinkDB.Post
open FSharp.Interop.Dynamic open FSharp.Interop.Dynamic
open MyWebLog.Entities open MyWebLog.Entities
open Rethink
open RethinkDb.Driver.Ast open RethinkDb.Driver.Ast
open System.Dynamic open System.Dynamic
@ -146,6 +145,22 @@ let findFeedPosts conn webLogId nbr : (Post * User option) list =
| null -> None | null -> None
| user -> Some <| unbox user) | user -> Some <| unbox user)
/// Add a post
let addPost conn post =
r.Table(Table.Post)
.Insert(post)
.RunResultAsync(conn)
|> ignore
/// Update a post
let updatePost conn post =
r.Table(Table.Post)
.Get(post.Id)
.Replace( { post with Categories = []
Comments = [] } )
.RunResultAsync(conn)
|> ignore
/// Save a post /// Save a post
let savePost conn post = let savePost conn post =
match post.Id with match post.Id with

View File

@ -0,0 +1,47 @@
namespace MyWebLog.Data.RethinkDB
open MyWebLog.Data
open RethinkDb.Driver.Net
/// RethinkDB implementation of myWebLog data persistence
type RethinkMyWebLogData(conn : IConnection, cfg : DataConfig) =
interface IMyWebLogData with
member this.SetUp = fun () -> SetUp.startUpCheck cfg
member this.AllCategories = Category.getAllCategories conn
member this.CategoryById = Category.tryFindCategory conn
member this.CategoryBySlug = Category.tryFindCategoryBySlug conn
member this.AddCategory = Category.addCategory conn
member this.UpdateCategory = Category.updateCategory conn
member this.UpdateChildren = Category.updateChildren conn
member this.DeleteCategory = Category.deleteCategory conn
member this.PageById = Page.tryFindPageById conn
member this.PageByPermalink = Page.tryFindPageByPermalink conn
member this.AllPages = Page.findAllPages conn
member this.AddPage = Page.addPage conn
member this.UpdatePage = Page.updatePage conn
member this.DeletePage = Page.deletePage conn
member this.PageOfPublishedPosts = Post.findPageOfPublishedPosts conn
member this.PageOfCategorizedPosts = Post.findPageOfCategorizedPosts conn
member this.PageOfTaggedPosts = Post.findPageOfTaggedPosts conn
member this.NewerPost = Post.tryFindNewerPost conn
member this.NewerCategorizedPost = Post.tryFindNewerCategorizedPost conn
member this.NewerTaggedPost = Post.tryFindNewerTaggedPost conn
member this.OlderPost = Post.tryFindOlderPost conn
member this.OlderCategorizedPost = Post.tryFindOlderCategorizedPost conn
member this.OlderTaggedPost = Post.tryFindOlderTaggedPost conn
member this.PageOfAllPosts = Post.findPageOfAllPosts conn
member this.PostById = Post.tryFindPost conn
member this.PostByPermalink = Post.tryFindPostByPermalink conn
member this.PostByPriorPermalink = Post.tryFindPostByPriorPermalink conn
member this.FeedPosts = Post.findFeedPosts conn
member this.AddPost = Post.addPost conn
member this.UpdatePost = Post.updatePost conn
member this.LogOn = User.tryUserLogOn conn
member this.WebLogByUrlBase = WebLog.tryFindWebLogByUrlBase conn
member this.DashboardCounts = WebLog.findDashboardCounts conn

View File

@ -1,6 +1,5 @@
module MyWebLog.Data.SetUp module MyWebLog.Data.RethinkDB.SetUp
open Rethink
open RethinkDb.Driver.Ast open RethinkDb.Driver.Ast
open System open System

View File

@ -1,6 +1,6 @@
/// Constants for tables used in myWebLog /// Constants for tables used in myWebLog
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module MyWebLog.Data.Table module MyWebLog.Data.RethinkDB.Table
/// The Category table /// The Category table
let Category = "Category" let Category = "Category"

View File

@ -1,7 +1,6 @@
module MyWebLog.Data.User module MyWebLog.Data.RethinkDB.User
open MyWebLog.Entities open MyWebLog.Entities
open Rethink
let private r = RethinkDb.Driver.RethinkDB.R let private r = RethinkDb.Driver.RethinkDB.R

View File

@ -1,20 +1,10 @@
module MyWebLog.Data.WebLog module MyWebLog.Data.RethinkDB.WebLog
open MyWebLog.Entities open MyWebLog.Entities
open Rethink
open RethinkDb.Driver.Ast open RethinkDb.Driver.Ast
let private r = RethinkDb.Driver.RethinkDB.R let private r = RethinkDb.Driver.RethinkDB.R
/// Counts of items displayed on the admin dashboard
type DashboardCounts =
{ /// The number of pages for the web log
Pages : int
/// The number of pages for the web log
Posts : int
/// The number of categories for the web log
Categories : int }
/// Detemine the web log by the URL base /// Detemine the web log by the URL base
let tryFindWebLogByUrlBase conn (urlBase : string) = let tryFindWebLogByUrlBase conn (urlBase : string) =
r.Table(Table.WebLog) r.Table(Table.WebLog)

View File

@ -0,0 +1,2 @@
FSharp.Interop.Dynamic
RethinkDb.Driver

View File

@ -2,7 +2,7 @@
open Newtonsoft.Json open Newtonsoft.Json
// ---- Constants ---- // --- Constants ---
/// Constants to use for revision source language /// Constants to use for revision source language
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@ -38,7 +38,7 @@ module CommentStatus =
[<Literal>] [<Literal>]
let Spam = "Spam" let Spam = "Spam"
// ---- Entities ---- // --- Entities ---
/// A revision of a post or page /// A revision of a post or page
type Revision = type Revision =
@ -288,3 +288,14 @@ with
Revisions = [] Revisions = []
Categories = [] Categories = []
Comments = [] } Comments = [] }
// --- UI Support ---
/// Counts of items displayed on the admin dashboard
type DashboardCounts =
{ /// The number of pages for the web log
Pages : int
/// The number of pages for the web log
Posts : int
/// The number of categories for the web log
Categories : int }

View File

@ -0,0 +1,114 @@
namespace MyWebLog.Data
open MyWebLog.Entities
/// Interface required to provide data to myWebLog's logic layer
type IMyWebLogData =
/// Function to set up the data store
abstract SetUp : (unit -> unit)
// --- Category ---
/// Get all categories for a web log
abstract AllCategories : (string -> Category list)
/// Try to find a category by its Id and web log Id (web log, category Ids)
abstract CategoryById : (string -> string -> Category option)
/// Try to find a category by its slug (web log Id, slug)
abstract CategoryBySlug : (string -> string -> Category option)
/// Add a category
abstract AddCategory : (Category -> unit)
/// Update a category
abstract UpdateCategory : (Category -> unit)
/// Update a category's children
abstract UpdateChildren : (string -> string -> string list -> unit)
/// Delete a Category
abstract DeleteCategory : (Category -> unit)
// --- Page ---
/// Try to find a page by its Id and web log Id (web log, page Ids), choosing whether to include revisions
abstract PageById : (string -> string -> bool -> Page option)
/// Try to find a page by its permalink and web log Id (web log Id, permalink)
abstract PageByPermalink : (string -> string -> Page option)
/// Get all pages for a web log
abstract AllPages : (string -> Page list)
/// Add a page
abstract AddPage : (Page -> unit)
/// Update a page
abstract UpdatePage : (Page -> unit)
/// Delete a page by its Id and web log Id (web log, page Ids)
abstract DeletePage : (string -> string -> unit)
// --- Post ---
/// Find a page of published posts for the given web log (web log Id, page #, # per page)
abstract PageOfPublishedPosts : (string -> int -> int -> Post list)
/// Find a page of published posts within a given category (web log Id, cat Id, page #, # per page)
abstract PageOfCategorizedPosts : (string -> string -> int -> int -> Post list)
/// Find a page of published posts tagged with a given tag (web log Id, tag, page #, # per page)
abstract PageOfTaggedPosts : (string -> string -> int -> int -> Post list)
/// Try to find the next newer published post for the given post
abstract NewerPost : (Post -> Post option)
/// Try to find the next newer published post within a given category
abstract NewerCategorizedPost : (string -> Post -> Post option)
/// Try to find the next newer published post tagged with a given tag
abstract NewerTaggedPost : (string -> Post -> Post option)
/// Try to find the next older published post for the given post
abstract OlderPost : (Post -> Post option)
/// Try to find the next older published post within a given category
abstract OlderCategorizedPost : (string -> Post -> Post option)
/// Try to find the next older published post tagged with a given tag
abstract OlderTaggedPost : (string -> Post -> Post option)
/// Find a page of all posts for the given web log (web log Id, page #, # per page)
abstract PageOfAllPosts : (string -> int -> int -> Post list)
/// Try to find a post by its Id and web log Id (web log, post Ids)
abstract PostById : (string -> string -> Post option)
/// Try to find a post by its permalink (web log Id, permalink)
abstract PostByPermalink : (string -> string -> Post option)
/// Try to find a post by a prior permalink (web log Id, permalink)
abstract PostByPriorPermalink : (string -> string -> Post option)
/// Get posts for the RSS feed for the given web log and number of posts
abstract FeedPosts : (string -> int -> (Post * User option) list)
/// Add a post
abstract AddPost : (Post -> unit)
/// Update a post
abstract UpdatePost : (Post -> unit)
// --- User ---
/// Attempt to log on a user
abstract LogOn : (string -> string -> User option)
// --- WebLog ---
/// Get a web log by its URL base
abstract WebLogByUrlBase : (string -> WebLog option)
/// Get dashboard counts for a web log
abstract DashboardCounts : (string -> DashboardCounts)

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" 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>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>a87f3cf5-2189-442b-8acf-929f5153ac22</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>MyWebLog.Entities</RootNamespace>
<AssemblyName>MyWebLog.Entities</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
<Name>MyWebLog.Entities</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\MyWebLog.Entities.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Release\MyWebLog.Entities.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="Entities.fs" />
<Compile Include="IMyWebLogData.fs" />
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '11.0'">
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And $(TargetFrameworkVersion) == 'v4.0'">
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
</Project>

View File

@ -0,0 +1 @@
Newtonsoft.Json

View File

@ -0,0 +1,56 @@
module MyWebLog.Logic.Category
open MyWebLog.Data
open MyWebLog.Entities
/// Sort categories by their name, with their children sorted below them, including an indent level
let sortCategories categories =
let rec getChildren (cat : Category) indent =
seq {
yield cat, indent
for child in categories |> List.filter (fun c -> c.ParentId = Some cat.Id) do
yield! getChildren child (indent + 1)
}
categories
|> List.filter (fun c -> c.ParentId.IsNone)
|> List.map (fun c -> getChildren c 0)
|> Seq.collect id
|> Seq.toList
/// Find all categories for a given web log
let findAllCategories (data : IMyWebLogData) webLogId =
data.AllCategories webLogId
|> sortCategories
/// Try to find a category for a given web log Id and category Id
let tryFindCategory (data : IMyWebLogData) webLogId catId = data.CategoryById webLogId catId
/// Try to find a category by its slug for a given web log
let tryFindCategoryBySlug (data : IMyWebLogData) webLogId slug = data.CategoryBySlug webLogId slug
/// Save a category
let saveCategory (data : IMyWebLogData) (cat : Category) =
match cat.Id with
| "new" -> let newCat = { cat with Id = string <| System.Guid.NewGuid() }
data.AddCategory newCat
newCat.Id
| _ -> data.UpdateCategory cat
cat.Id
/// Remove a category from its parent
let removeCategoryFromParent (data : IMyWebLogData) webLogId parentId catId =
match tryFindCategory data webLogId parentId with
| Some parent -> parent.Children
|> List.filter (fun childId -> childId <> catId)
|> data.UpdateChildren webLogId parentId
| None -> ()
/// Add a category to a given parent
let addCategoryToParent (data : IMyWebLogData) webLogId parentId catId =
match tryFindCategory data webLogId parentId with
| Some parent -> catId :: parent.Children
|> data.UpdateChildren webLogId parentId
| None -> ()
/// Delete a category
let deleteCategory (data : IMyWebLogData) cat = data.DeleteCategory cat

View File

@ -1,18 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>1fba0b84-b09e-4b16-b9b6-5730dea27192</ProjectGuid> <ProjectGuid>29f6eda3-4f43-4bb3-9c63-ae238a9b7f12</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<RootNamespace>myWebLog.Data</RootNamespace> <RootNamespace>MyWebLog.Logic</RootNamespace>
<AssemblyName>MyWebLog.Data</AssemblyName> <AssemblyName>MyWebLog.Logic</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion> <TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <Name>MyWebLog.Logic</Name>
<Name>myWebLog.Data</Name>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -22,7 +21,7 @@
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel> <WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\MyWebLog.Data.xml</DocumentationFile> <DocumentationFile>bin\Debug\MyWebLog.Logic.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -31,8 +30,31 @@
<OutputPath>bin\Release\</OutputPath> <OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel> <WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Release\myWebLog.Data.XML</DocumentationFile> <DocumentationFile>bin\Release\MyWebLog.Logic.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="Category.fs" />
<Compile Include="Page.fs" />
<Compile Include="Post.fs" />
<Compile Include="User.fs" />
<Compile Include="WebLog.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyWebLog.Entities\MyWebLog.Entities.fsproj">
<Name>MyWebLog.Entities</Name>
<Project>{a87f3cf5-2189-442b-8acf-929f5153ac22}</Project>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion> <MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup> </PropertyGroup>
@ -48,61 +70,12 @@
</PropertyGroup> </PropertyGroup>
</Otherwise> </Otherwise>
</Choose> </Choose>
<Import Project="$(FSharpTargetsPath)" /> <Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
<ItemGroup> <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
<Compile Include="AssemblyInfo.fs" />
<Compile Include="Entities.fs" />
<Compile Include="Table.fs" />
<Compile Include="DataConfig.fs" />
<Compile Include="Rethink.fs" />
<Compile Include="SetUp.fs" />
<Compile Include="Category.fs" />
<Compile Include="Page.fs" />
<Compile Include="Post.fs" />
<Compile Include="User.fs" />
<Compile Include="WebLog.fs" />
<None Include="ConvertOld.fsx" />
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Reference Include="Common.Logging">
<HintPath>..\packages\Common.Logging.3.3.1\lib\net40\Common.Logging.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Common.Logging.Core">
<HintPath>..\packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Dynamitey">
<HintPath>..\packages\Dynamitey.1.0.2.0\lib\net40\Dynamitey.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Core">
<HintPath>..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Interop.Dynamic">
<HintPath>..\packages\FSharp.Interop.Dynamic.3.0.0.0\lib\portable-net45+sl50+win\FSharp.Interop.Dynamic.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="mscorlib" />
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RethinkDb.Driver">
<HintPath>..\packages\RethinkDb.Driver.2.3.9\lib\net45\RethinkDb.Driver.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>

View File

@ -0,0 +1,29 @@
/// Logic for manipulating <see cref="Page" /> entities
module MyWebLog.Logic.Page
open MyWebLog.Data
open MyWebLog.Entities
/// Find a page by its Id and web log Id
let tryFindPage (data : IMyWebLogData) webLogId pageId = data.PageById webLogId pageId true
/// Find a page by its Id and web log Id, without the revision list
let tryFindPageWithoutRevisions (data : IMyWebLogData) webLogId pageId = data.PageById webLogId pageId false
/// Find a page by its permalink
let tryFindPageByPermalink (data : IMyWebLogData) webLogId permalink = data.PageByPermalink webLogId permalink
/// Find a list of all pages (excludes text and revisions)
let findAllPages (data : IMyWebLogData) webLogId = data.AllPages webLogId
/// Save a page
let savePage (data : IMyWebLogData) (page : Page) =
match page.Id with
| "new" -> let newPg = { page with Id = string <| System.Guid.NewGuid() }
data.AddPage newPg
newPg.Id
| _ -> data.UpdatePage page
page.Id
/// Delete a page
let deletePage (data : IMyWebLogData) webLogId pageId = data.DeletePage webLogId pageId

View File

@ -0,0 +1,60 @@
/// Logic for manipulating <see cref="Post" /> entities
module MyWebLog.Logic.Post
open MyWebLog.Data
open MyWebLog.Entities
/// Find a page of published posts
let findPageOfPublishedPosts (data : IMyWebLogData) webLogId pageNbr nbrPerPage =
data.PageOfPublishedPosts webLogId pageNbr nbrPerPage
/// Find a pages of published posts in a given category
let findPageOfCategorizedPosts (data : IMyWebLogData) webLogId catId pageNbr nbrPerPage =
data.PageOfCategorizedPosts webLogId catId pageNbr nbrPerPage
/// Find a page of published posts tagged with a given tag
let findPageOfTaggedPosts (data : IMyWebLogData) webLogId tag pageNbr nbrPerPage =
data.PageOfTaggedPosts webLogId tag pageNbr nbrPerPage
/// Find the next newer published post for the given post
let tryFindNewerPost (data : IMyWebLogData) post = data.NewerPost post
/// Find the next newer published post in a given category for the given post
let tryFindNewerCategorizedPost (data : IMyWebLogData) catId post = data.NewerCategorizedPost catId post
/// Find the next newer published post tagged with a given tag for the given post
let tryFindNewerTaggedPost (data : IMyWebLogData) tag post = data.NewerTaggedPost tag post
/// Find the next older published post for the given post
let tryFindOlderPost (data : IMyWebLogData) post = data.OlderPost post
/// Find the next older published post in a given category for the given post
let tryFindOlderCategorizedPost (data : IMyWebLogData) catId post = data.OlderCategorizedPost catId post
/// Find the next older published post tagged with a given tag for the given post
let tryFindOlderTaggedPost (data : IMyWebLogData) tag post = data.OlderTaggedPost tag post
/// Find a page of all posts for a web log
let findPageOfAllPosts (data : IMyWebLogData) webLogId pageNbr nbrPerPage =
data.PageOfAllPosts webLogId pageNbr nbrPerPage
/// Try to find a post by its Id
let tryFindPost (data : IMyWebLogData) webLogId postId = data.PostById webLogId postId
/// Try to find a post by its permalink
let tryFindPostByPermalink (data : IMyWebLogData) webLogId permalink = data.PostByPermalink webLogId permalink
/// Try to find a post by its prior permalink
let tryFindPostByPriorPermalink (data : IMyWebLogData) webLogId permalink = data.PostByPriorPermalink webLogId permalink
/// Find posts for the RSS feed
let findFeedPosts (data : IMyWebLogData) webLogId nbrOfPosts = data.FeedPosts webLogId nbrOfPosts
/// Save a post
let savePost (data : IMyWebLogData) post =
match post.Id with
| "new" -> let newPost = { post with Id = string <| System.Guid.NewGuid() }
data.AddPost newPost
newPost.Id
| _ -> data.UpdatePost post
post.Id

View File

@ -0,0 +1,7 @@
/// Logic for manipulating <see cref="User" /> entities
module MyWebLog.Logic.User
open MyWebLog.Data
/// Try to log on a user
let tryUserLogOn (data : IMyWebLogData) email passwordHash = data.LogOn email passwordHash

View File

@ -0,0 +1,11 @@
/// Logic for manipulating <see cref="WebLog" /> entities
module MyWebLog.Logic.WebLog
open MyWebLog.Data
open MyWebLog.Entities
/// Find a web log by its URL base
let tryFindWebLogByUrlBase (data : IMyWebLogData) urlBase = data.WebLogByUrlBase urlBase
/// Find the counts for the admin dashboard
let findDashboardCounts (data : IMyWebLogData) webLogId = data.DashboardCounts webLogId

View File

@ -0,0 +1,4 @@
namespace MyWebLog.Web
type Web() =
member this.X = "F#"

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" 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>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>07e60874-6cf5-4d53-aee0-f17ef28228dd</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>MyWebLog.Tests</RootNamespace>
<AssemblyName>MyWebLog.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
<Name>MyWebLog.Tests</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\MyWebLog.Tests.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Release\MyWebLog.Tests.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="MyWebLog.Tests.fs" />
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '11.0'">
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

14
src/build.cmd Normal file
View File

@ -0,0 +1,14 @@
@echo off
cls
.paket\paket.bootstrapper.exe
if errorlevel 1 (
exit /b %errorlevel%
)
.paket\paket.exe restore
if errorlevel 1 (
exit /b %errorlevel%
)
packages\FAKE\tools\FAKE.exe build.fsx %*

42
src/build.fsx Normal file
View File

@ -0,0 +1,42 @@
// include Fake libs
#r "./packages/FAKE/tools/FakeLib.dll"
open Fake
// Directories
let buildDir = "./build/"
let deployDir = "./deploy/"
// Filesets
let appReferences =
!! "/**/*.csproj"
++ "/**/*.fsproj"
// version info
let version = "0.1" // or retrieve from CI server
// Targets
Target "Clean" (fun _ ->
CleanDirs [buildDir; deployDir]
)
Target "Build" (fun _ ->
// compile all projects below src/app/
MSBuildDebug buildDir "Build" appReferences
|> Log "AppBuild-Output: "
)
Target "Deploy" (fun _ ->
!! (buildDir + "/**/*.*")
-- "*.zip"
|> Zip buildDir (deployDir + "ApplicationName." + version + ".zip")
)
// Build order
"Clean"
==> "Build"
==> "Deploy"
// start build
RunTargetOrDefault "Build"

33
src/build.sh Normal file
View File

@ -0,0 +1,33 @@
#!/bin/bash
if test "$OS" = "Windows_NT"
then
# use .Net
.paket/paket.bootstrapper.exe
exit_code=$?
if [ $exit_code -ne 0 ]; then
exit $exit_code
fi
.paket/paket.exe restore
exit_code=$?
if [ $exit_code -ne 0 ]; then
exit $exit_code
fi
packages/FAKE/tools/FAKE.exe $@ --fsiargs build.fsx
else
# use mono
mono .paket/paket.bootstrapper.exe
exit_code=$?
if [ $exit_code -ne 0 ]; then
exit $exit_code
fi
mono .paket/paket.exe restore
exit_code=$?
if [ $exit_code -ne 0 ]; then
exit $exit_code
fi
mono packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx
fi

View File

@ -1,77 +0,0 @@
module MyWebLog.Data.Page
open FSharp.Interop.Dynamic
open MyWebLog.Entities
open Rethink
open RethinkDb.Driver.Ast
open System.Dynamic
let private r = RethinkDb.Driver.RethinkDB.R
/// Shorthand to get the page by its Id, filtering on web log Id
let private page (webLogId : string) (pageId : string) =
r.Table(Table.Page)
.Get(pageId)
.Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId)))
/// Get a page by its Id
let tryFindPage conn webLogId pageId =
match r.Table(Table.Page)
.Get(pageId)
.RunAtomAsync<Page>(conn) |> await |> box with
| null -> None
| page -> let pg : Page = unbox page
match pg.WebLogId = webLogId with true -> Some pg | _ -> None
/// Get a page by its Id (excluding revisions)
let tryFindPageWithoutRevisions conn webLogId pageId : Page option =
match (page webLogId pageId)
.Without("Revisions")
.RunAtomAsync<Page>(conn) |> await |> box with
| null -> None
| page -> Some <| unbox page
/// Find a page by its permalink
let tryFindPageByPermalink conn (webLogId : string) (permalink : string) =
r.Table(Table.Page)
.GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink")
.Without("Revisions")
.RunCursorAsync<Page>(conn)
|> await
|> Seq.tryHead
/// Get a list of all pages (excludes page text and revisions)
let findAllPages conn (webLogId : string) =
r.Table(Table.Page)
.GetAll(webLogId).OptArg("index", "WebLogId")
.OrderBy("Title")
.Without("Text", "Revisions")
.RunListAsync<Page>(conn)
|> await
|> Seq.toList
/// Save a page
let savePage conn (pg : Page) =
match pg.Id with
| "new" -> let newPage = { pg with Id = string <| System.Guid.NewGuid() }
r.Table(Table.Page)
.Insert(page)
.RunResultAsync(conn) |> await |> ignore
newPage.Id
| _ -> let upd8 = ExpandoObject()
upd8?Title <- pg.Title
upd8?Permalink <- pg.Permalink
upd8?PublishedOn <- pg.PublishedOn
upd8?UpdatedOn <- pg.UpdatedOn
upd8?Text <- pg.Text
upd8?Revisions <- pg.Revisions
(page pg.WebLogId pg.Id)
.Update(upd8)
.RunResultAsync(conn) |> await |> ignore
pg.Id
/// Delete a page
let deletePage conn webLogId pageId =
(page webLogId pageId)
.Delete()
.RunResultAsync(conn) |> await |> ignore

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Common.Logging" version="3.3.1" targetFramework="net452" />
<package id="Common.Logging.Core" version="3.3.1" targetFramework="net452" />
<package id="Dynamitey" version="1.0.2.0" targetFramework="net452" />
<package id="FSharp.Core" version="4.0.0.1" targetFramework="net452" />
<package id="FSharp.Interop.Dynamic" version="3.0.0.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="RethinkDb.Driver" version="2.3.9" targetFramework="net452" />
</packages>

View File

@ -1,186 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" 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>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>e6ee110a-27a6-4a19-b0cb-d24f48f71b53</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>myWebLog.Web</RootNamespace>
<AssemblyName>MyWebLog.Web</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Name>myWebLog.Web</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\MyWebLog.Web.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Release\myWebLog.Web.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '11.0'">
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Import Project="$(FSharpTargetsPath)" />
<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="Keys.fs" />
<Compile Include="AppConfig.fs" />
<Compile Include="ViewModels.fs" />
<Compile Include="ModuleExtensions.fs" />
<Compile Include="AdminModule.fs" />
<Compile Include="CategoryModule.fs" />
<Compile Include="PageModule.fs" />
<Compile Include="PostModule.fs" />
<Compile Include="UserModule.fs" />
<Compile Include="App.fs" />
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Reference Include="Common.Logging">
<HintPath>..\packages\Common.Logging.3.3.1\lib\net40\Common.Logging.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Common.Logging.Core">
<HintPath>..\packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="CSharpFormat">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\CSharpFormat.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Dynamitey">
<HintPath>..\packages\Dynamitey.1.0.2.0\lib\net40\Dynamitey.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.CodeFormat">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.CodeFormat.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Compiler.Service">
<HintPath>..\packages\FSharp.Compiler.Service.2.0.0.6\lib\net45\FSharp.Compiler.Service.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Core">
<HintPath>..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Formatting.Common">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Formatting.Common.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Interop.Dynamic">
<HintPath>..\packages\FSharp.Interop.Dynamic.3.0.0.0\lib\portable-net45+sl50+win\FSharp.Interop.Dynamic.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Literate">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Literate.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Markdown">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Markdown.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.MetadataFormat">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.MetadataFormat.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharpVSPowerTools.Core">
<HintPath>..\packages\FSharpVSPowerTools.Core.2.3.0\lib\net45\FSharpVSPowerTools.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="mscorlib" />
<Reference Include="Nancy">
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy.Authentication.Forms">
<HintPath>..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy.Session.Persistable">
<HintPath>..\packages\Nancy.Session.Persistable.0.9.0\lib\net452\Nancy.Session.Persistable.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy.Session.RethinkDb">
<HintPath>..\packages\Nancy.Session.RethinkDB.0.9.0\lib\net452\Nancy.Session.RethinkDb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NodaTime">
<HintPath>..\packages\NodaTime.1.3.2\lib\net35-Client\NodaTime.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RazorEngine">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\RazorEngine.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RethinkDb.Driver">
<HintPath>..\packages\RethinkDb.Driver.2.3.9\lib\net45\RethinkDb.Driver.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Suave">
<HintPath>..\packages\Suave.1.1.3\lib\net40\Suave.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Web.Razor">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\System.Web.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\myWebLog.Data\myWebLog.Data.fsproj">
<Name>myWebLog.Data</Name>
<Project>{1fba0b84-b09e-4b16-b9b6-5730dea27192}</Project>
<Private>True</Private>
</ProjectReference>
<ProjectReference Include="..\myWebLog.Resources\myWebLog.Resources.csproj">
<Name>myWebLog.Resources</Name>
<Project>{a12ea8da-88bc-4447-90cb-a0e2dcc37523}</Project>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Common.Logging" version="3.3.1" targetFramework="net452" />
<package id="Common.Logging.Core" version="3.3.1" targetFramework="net452" />
<package id="Dynamitey" version="1.0.2.0" targetFramework="net452" />
<package id="FSharp.Compiler.Service" version="2.0.0.6" targetFramework="net452" />
<package id="FSharp.Core" version="4.0.0.1" targetFramework="net452" />
<package id="FSharp.Formatting" version="2.14.4" targetFramework="net452" />
<package id="FSharp.Interop.Dynamic" version="3.0.0.0" targetFramework="net452" />
<package id="FSharpVSPowerTools.Core" version="2.3.0" targetFramework="net452" />
<package id="Nancy" version="1.4.3" targetFramework="net452" />
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" />
<package id="Nancy.Session.Persistable" version="0.9.0" targetFramework="net452" />
<package id="Nancy.Session.RethinkDB" version="0.9.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="NodaTime" version="1.3.2" targetFramework="net452" />
<package id="RethinkDb.Driver" version="2.3.9" targetFramework="net452" />
<package id="Suave" version="1.1.3" targetFramework="net452" />
</packages>

View File

@ -1,39 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 2013
VisualStudioVersion = 14.0.25420.1 VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "myWebLog", "myWebLog\myWebLog.csproj", "{B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{DF15419B-90C6-4F45-8EC1-7A63C5D3565C}"
EndProject ProjectSection(SolutionItems) = preProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "myWebLog.Web", "myWebLog.Web\myWebLog.Web.fsproj", "{E6EE110A-27A6-4A19-B0CB-D24F48F71B53}" paket.dependencies = paket.dependencies
EndProject EndProjectSection
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "myWebLog.Data", "myWebLog.Data\myWebLog.Data.fsproj", "{1FBA0B84-B09E-4B16-B9B6-5730DEA27192}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "myWebLog.Resources", "myWebLog.Resources\myWebLog.Resources.csproj", "{A12EA8DA-88BC-4447-90CB-A0E2DCC37523}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}.Release|Any CPU.Build.0 = Release|Any CPU
{E6EE110A-27A6-4A19-B0CB-D24F48F71B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6EE110A-27A6-4A19-B0CB-D24F48F71B53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6EE110A-27A6-4A19-B0CB-D24F48F71B53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6EE110A-27A6-4A19-B0CB-D24F48F71B53}.Release|Any CPU.Build.0 = Release|Any CPU
{1FBA0B84-B09E-4B16-B9B6-5730DEA27192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FBA0B84-B09E-4B16-B9B6-5730DEA27192}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FBA0B84-B09E-4B16-B9B6-5730DEA27192}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FBA0B84-B09E-4B16-B9B6-5730DEA27192}.Release|Any CPU.Build.0 = Release|Any CPU
{A12EA8DA-88BC-4447-90CB-A0E2DCC37523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A12EA8DA-88BC-4447-90CB-A0E2DCC37523}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A12EA8DA-88BC-4447-90CB-A0E2DCC37523}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A12EA8DA-88BC-4447-90CB-A0E2DCC37523}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection

View File

@ -15,7 +15,20 @@
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider"
invariant="System.Data.SqlClient"
description=".NET Framework Data Provider for SQL Server"
type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</DbProviderFactories>
</system.data>
<connectionStrings>
<clear />
<add name="SessionStore"
providerName="System.Data.SqlClient"
connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\Users\danie\Documents\Sandbox\myWebLog\src\myWebLog\session.mdf;Integrated Security=True;Connect Timeout=30" />
</connectionStrings>
</configuration> </configuration>
<!-- publicKeyToken="32ab4ba45e0a69a1" <!-- publicKeyToken="32ab4ba45e0a69a1"
culture="neutral" culture="neutral"

View File

@ -56,20 +56,27 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\myWebLog.Data\myWebLog.Data.fsproj"> <ProjectReference Include="..\MyWebLog.App\MyWebLog.App.fsproj">
<Project>{1fba0b84-b09e-4b16-b9b6-5730dea27192}</Project> <Project>{9cea3a8b-e8aa-44e6-9f5f-2095ceed54eb}</Project>
<Name>myWebLog.Data</Name> <Name>MyWebLog.App</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\myWebLog.Resources\myWebLog.Resources.csproj"> <ProjectReference Include="..\MyWebLog.Data.RethinkDB\MyWebLog.Data.RethinkDB.fsproj">
<Project>{d6c2be5e-883a-4f34-9905-b730543ca380}</Project>
<Name>myWebLog.Web</Name>
</ProjectReference>
<ProjectReference Include="..\MyWebLog.Entities\MyWebLog.Entities.fsproj">
<Project>{a87f3cf5-2189-442b-8acf-929f5153ac22}</Project>
<Name>MyWebLog.Entities</Name>
</ProjectReference>
<ProjectReference Include="..\MyWebLog.Logic\MyWebLog.Logic.fsproj">
<Project>{29f6eda3-4f43-4bb3-9c63-ae238a9b7f12}</Project>
<Name>MyWebLog.Entities</Name>
</ProjectReference>
<ProjectReference Include="..\MyWebLog.Resources\MyWebLog.Resources.csproj">
<Project>{a12ea8da-88bc-4447-90cb-a0e2dcc37523}</Project> <Project>{a12ea8da-88bc-4447-90cb-a0e2dcc37523}</Project>
<Name>myWebLog.Resources</Name> <Name>myWebLog.Resources</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\myWebLog.Web\myWebLog.Web.fsproj">
<Project>{e6ee110a-27a6-4a19-b0cb-d24f48f71b53}</Project>
<Name>myWebLog.Web</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup />
<ItemGroup> <ItemGroup>
<Content Include="content\scripts\tinymce-init.js" /> <Content Include="content\scripts\tinymce-init.js" />
<Content Include="content\styles\admin.css" /> <Content Include="content\styles\admin.css" />

View File

@ -12,7 +12,7 @@
<article> <article>
<h1> <h1>
<a href="/@Current.Post.Permalink" <a href="/@Current.Post.Permalink"
title="@Translate.PermanentLinkTo &quot;@Current.Post.Title@quot;">@Current.Post.Title</a> title="@Translate.PermanentLinkTo &quot;@Current.Post.Title&quot;">@Current.Post.Title</a>
</h1> </h1>
<p> <p>
<i class="fa fa-calendar" title="@Translate.Date"></i> @Current.PublishedDate &nbsp; <i class="fa fa-calendar" title="@Translate.Date"></i> @Current.PublishedDate &nbsp;

13
src/paket.dependencies Normal file
View File

@ -0,0 +1,13 @@
framework: net40, net45, net452
source https://www.nuget.org/api/v2
nuget Common.Logging 3.3.0
nuget FAKE
nuget FSharp.Interop.Dynamic
nuget FSharp.Formatting
nuget Nancy
nuget Nancy.Authentication.Forms
nuget Nancy.Session.RethinkDb
nuget Newtonsoft.Json
nuget NodaTime
nuget RethinkDb.Driver
nuget Suave

34
src/paket.lock Normal file
View File

@ -0,0 +1,34 @@
FRAMEWORK: NET40, NET45, NET452
NUGET
remote: https://www.nuget.org/api/v2
Common.Logging (3.3)
Common.Logging.Core (>= 3.3)
Common.Logging.Core (3.3.1)
Dynamitey (1.0.2)
FAKE (4.36)
FSharp.Compiler.Service (2.0.0.6)
FSharp.Core (4.0.0.1)
FSharp.Formatting (2.14.4)
FSharp.Compiler.Service (2.0.0.6)
FSharpVSPowerTools.Core (>= 2.3 < 2.4)
FSharp.Interop.Dynamic (3.0)
Dynamitey (>= 1.0.2)
FSharp.Core (>= 3.1.2.1)
FSharpVSPowerTools.Core (2.3)
FSharp.Compiler.Service (>= 2.0.0.3)
Nancy (1.4.3)
Nancy.Authentication.Forms (1.4.1)
Nancy (>= 1.4.1)
Nancy.Session.Persistable (0.9)
Nancy (>= 1.4.3)
Newtonsoft.Json (>= 9.0.1)
Nancy.Session.RethinkDB (0.9)
Nancy.Session.Persistable (>= 0.9)
RethinkDb.Driver (>= 2.3.9)
Newtonsoft.Json (9.0.1)
NodaTime (1.3.2)
RethinkDb.Driver (2.3.10)
Common.Logging (>= 3.3) - framework: net45, net452
Newtonsoft.Json (>= 9.0.1) - framework: net45, net452
Suave (1.1.3)
FSharp.Core (>= 3.1.2.5)