Switched to Elm / Suave
It's like F# on the client side - sweet!
This commit is contained in:
parent
d3a80b9ceb
commit
bde45c8554
4
.gitignore
vendored
4
.gitignore
vendored
@ -252,5 +252,5 @@ paket-files/
|
|||||||
.idea/
|
.idea/
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
|
|
||||||
# wwwroot/lib
|
# Elm temporary files
|
||||||
src/MyPrayerJournal/wwwroot/lib
|
src/elm-stuff
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"directory": "wwwroot/lib"
|
|
||||||
}
|
|
234
src/MyPrayerJournal/.gitignore
vendored
234
src/MyPrayerJournal/.gitignore
vendored
@ -1,234 +0,0 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
build/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUNIT
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# DNX
|
|
||||||
project.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_i.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding add-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/packages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/packages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/packages/repositories.config
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Microsoft Azure ApplicationInsights config file
|
|
||||||
ApplicationInsights.config
|
|
||||||
|
|
||||||
# Windows Store app package directory
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
node_modules/
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
@ -1,92 +0,0 @@
|
|||||||
module MyPrayerJournal.App
|
|
||||||
|
|
||||||
open Microsoft.AspNetCore.Builder
|
|
||||||
open Microsoft.AspNetCore.Hosting
|
|
||||||
open Microsoft.AspNetCore.Http
|
|
||||||
open Microsoft.AspNetCore.Localization
|
|
||||||
open Microsoft.Extensions.Configuration
|
|
||||||
open Microsoft.Extensions.DependencyInjection
|
|
||||||
open Microsoft.Extensions.Logging
|
|
||||||
open Microsoft.Extensions.Options
|
|
||||||
open RethinkDB.DistributedCache
|
|
||||||
open System
|
|
||||||
open System.IO
|
|
||||||
|
|
||||||
/// Startup class for myPrayerJournal
|
|
||||||
type Startup(env : IHostingEnvironment) =
|
|
||||||
|
|
||||||
/// Configuration for this application
|
|
||||||
member this.Configuration =
|
|
||||||
let builder =
|
|
||||||
ConfigurationBuilder()
|
|
||||||
.SetBasePath(env.ContentRootPath)
|
|
||||||
.AddJsonFile("appsettings.json", optional = true, reloadOnChange = true)
|
|
||||||
.AddJsonFile(sprintf "appsettings.%s.json" env.EnvironmentName, optional = true)
|
|
||||||
// For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
|
|
||||||
match env.IsDevelopment () with true -> ignore <| builder.AddUserSecrets () | _ -> ()
|
|
||||||
ignore <| builder.AddEnvironmentVariables ()
|
|
||||||
builder.Build ()
|
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
|
||||||
member this.ConfigureServices (services : IServiceCollection) =
|
|
||||||
services.AddOptions () |> ignore
|
|
||||||
services.Configure<AppConfig> (this.Configuration.GetSection "MyPrayerJournal") |> ignore
|
|
||||||
services.AddLocalization (fun opt -> opt.ResourcesPath <- "Resources") |> ignore
|
|
||||||
services.AddMvc () |> ignore
|
|
||||||
//ignore <| services.AddDistributedMemoryCache ()
|
|
||||||
// RethinkDB connection
|
|
||||||
async {
|
|
||||||
let cfg = services.BuildServiceProvider().GetService<IOptions<AppConfig>>().Value
|
|
||||||
let! conn = DataConfig.Connect cfg.DataConfig
|
|
||||||
do! conn.EstablishEnvironment cfg
|
|
||||||
services.AddSingleton conn |> ignore
|
|
||||||
services.AddDistributedRethinkDBCache (fun options ->
|
|
||||||
options.Database <- match cfg.DataConfig.Database with null -> "" | db -> db
|
|
||||||
options.TableName <- "Session") |> ignore
|
|
||||||
services.AddSession () |> ignore
|
|
||||||
} |> Async.RunSynchronously
|
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
|
||||||
member this.Configure (app : IApplicationBuilder, env : IHostingEnvironment, loggerFactory : ILoggerFactory) =
|
|
||||||
loggerFactory.AddConsole(this.Configuration.GetSection "Logging") |> ignore
|
|
||||||
loggerFactory.AddDebug () |> ignore
|
|
||||||
|
|
||||||
match env.IsDevelopment () with
|
|
||||||
| true -> app.UseDeveloperExceptionPage () |> ignore
|
|
||||||
app.UseBrowserLink () |> ignore
|
|
||||||
| _ -> app.UseExceptionHandler "/error" |> ignore
|
|
||||||
|
|
||||||
app.UseStaticFiles () |> ignore
|
|
||||||
|
|
||||||
app.UseCookieAuthentication(
|
|
||||||
CookieAuthenticationOptions(
|
|
||||||
AuthenticationScheme = Keys.Authentication,
|
|
||||||
LoginPath = PathString "/user/log-on",
|
|
||||||
AutomaticAuthenticate = true,
|
|
||||||
AutomaticChallenge = true,
|
|
||||||
ExpireTimeSpan = TimeSpan (2, 0, 0),
|
|
||||||
SlidingExpiration = true)) |> ignore
|
|
||||||
app.UseMvc(fun routes ->
|
|
||||||
routes.MapRoute(name = "default", template = "{controller=Home}/{action=Index}/{id?}") |> ignore) |> ignore
|
|
||||||
|
|
||||||
/// Default to Development environment
|
|
||||||
let defaults = seq { yield WebHostDefaults.EnvironmentKey, "Development" }
|
|
||||||
|> dict
|
|
||||||
|
|
||||||
[<EntryPoint>]
|
|
||||||
let main argv =
|
|
||||||
let cfg =
|
|
||||||
ConfigurationBuilder()
|
|
||||||
.AddInMemoryCollection(defaults)
|
|
||||||
.AddEnvironmentVariables("ASPNETCORE_")
|
|
||||||
.AddCommandLine(argv)
|
|
||||||
.Build()
|
|
||||||
use host =
|
|
||||||
WebHostBuilder()
|
|
||||||
.UseConfiguration(cfg)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseStartup<Startup>()
|
|
||||||
.Build()
|
|
||||||
host.Run()
|
|
||||||
0
|
|
@ -1,45 +0,0 @@
|
|||||||
namespace MyPrayerJournal
|
|
||||||
|
|
||||||
open RethinkDb.Driver
|
|
||||||
open RethinkDb.Driver.Net
|
|
||||||
open System
|
|
||||||
open System.Text
|
|
||||||
|
|
||||||
/// Data configuration
|
|
||||||
type DataConfig() =
|
|
||||||
/// The hostname for the RethinkDB server
|
|
||||||
member val Hostname = "" with get, set
|
|
||||||
/// The port for the RethinkDB server
|
|
||||||
member val Port = 0 with get, set
|
|
||||||
/// The authorization key to use when connecting to the server
|
|
||||||
member val AuthKey = "" with get, set
|
|
||||||
/// How long an attempt to connect to the server should wait before giving up
|
|
||||||
member val Timeout = 0 with get, set
|
|
||||||
/// The name of the default database to use on the connection
|
|
||||||
member val Database = "" with get, set
|
|
||||||
|
|
||||||
/// Use RethinkDB defaults for non-provided options, and connect to the server
|
|
||||||
static member Connect (cfg : DataConfig) =
|
|
||||||
async {
|
|
||||||
let builder =
|
|
||||||
seq<Connection.Builder -> Connection.Builder> {
|
|
||||||
yield fun b -> if String.IsNullOrEmpty cfg.Hostname then b else b.Hostname cfg.Hostname
|
|
||||||
yield fun b -> if String.IsNullOrEmpty cfg.AuthKey then b else b.AuthKey cfg.AuthKey
|
|
||||||
yield fun b -> if String.IsNullOrEmpty cfg.Database then b else b.Db cfg.Database
|
|
||||||
yield fun b -> if 0 = cfg.Port then b else b.Port cfg.Port
|
|
||||||
yield fun b -> if 0 = cfg.Timeout then b else b.Timeout cfg.Timeout
|
|
||||||
}
|
|
||||||
|> Seq.fold (fun curr block -> block curr) (RethinkDB.R.Connection())
|
|
||||||
let! conn = builder.ConnectAsync()
|
|
||||||
return conn :> IConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Application configuration
|
|
||||||
type AppConfig() =
|
|
||||||
/// The text from which to derive salt to use for passwords
|
|
||||||
member val PasswordSalt = "" with get, set
|
|
||||||
/// The data configuration
|
|
||||||
member val DataConfig = DataConfig() with get, set
|
|
||||||
/// The salt to use for passwords
|
|
||||||
member this.PasswordSaltBytes = Encoding.UTF8.GetBytes this.PasswordSalt
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
|||||||
/// Magic strings? Look behind the curtain...
|
|
||||||
[<RequireQualifiedAccess>]
|
|
||||||
module MyPrayerJournal.Keys
|
|
||||||
|
|
||||||
/// Instance name for cookie authentication
|
|
||||||
let Authentication = "mpj-authentication"
|
|
||||||
|
|
||||||
/// The current user
|
|
||||||
let CurrentUser = "mpj-user"
|
|
||||||
|
|
||||||
/// The page generator
|
|
||||||
let Generator = "mpj-generator"
|
|
||||||
|
|
||||||
/// The request start ticks
|
|
||||||
let RequestTimer = "mpj-request-timer"
|
|
@ -1,30 +0,0 @@
|
|||||||
namespace MyPrayerJournal.Controllers
|
|
||||||
|
|
||||||
open Microsoft.AspNetCore.Mvc
|
|
||||||
open Microsoft.AspNetCore.Mvc.Filters
|
|
||||||
open Microsoft.Extensions.Localization
|
|
||||||
open MyPrayerJournal
|
|
||||||
open RethinkDb.Driver.Net
|
|
||||||
open System
|
|
||||||
open System.Reflection
|
|
||||||
|
|
||||||
/// Base controller for all myPrayerJournal controllers
|
|
||||||
type ApplicationController(data : IConnection) =
|
|
||||||
inherit Controller()
|
|
||||||
|
|
||||||
let version =
|
|
||||||
let v = typeof<ApplicationController>.GetType().GetTypeInfo().Assembly.GetName().Version
|
|
||||||
match v.Build with
|
|
||||||
| 0 -> match v.Minor with 0 -> string v.Major | _ -> sprintf "%d.%d" v.Major v.Minor
|
|
||||||
| _ -> sprintf "%d.%d.%d" v.Major v.Minor v.Build
|
|
||||||
|> sprintf "v%s"
|
|
||||||
|
|
||||||
/// Fill common items for every request
|
|
||||||
override this.OnActionExecuting (context : ActionExecutingContext) =
|
|
||||||
let sw = System.Diagnostics.Stopwatch()
|
|
||||||
sw.Start()
|
|
||||||
base.OnActionExecuting context
|
|
||||||
this.ViewData.[Keys.CurrentUser] <- Option<User>.None
|
|
||||||
this.ViewData.[Keys.Generator] <- sprintf "myPrayerJournal %s" version
|
|
||||||
this.ViewData.[Keys.RequestTimer] <- sw
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
|||||||
namespace MyPrayerJournal.Controllers
|
|
||||||
|
|
||||||
open Microsoft.AspNetCore.Authorization
|
|
||||||
open Microsoft.AspNetCore.Mvc
|
|
||||||
open Microsoft.Extensions.Logging
|
|
||||||
open MyPrayerJournal
|
|
||||||
open RethinkDb.Driver.Net
|
|
||||||
|
|
||||||
/// Home controller
|
|
||||||
[<Authorize>]
|
|
||||||
[<Route("")>]
|
|
||||||
type HomeController(data : IConnection, logger : ILogger<HomeController>) =
|
|
||||||
inherit ApplicationController(data)
|
|
||||||
|
|
||||||
[<AllowAnonymous>]
|
|
||||||
[<HttpGet("")>]
|
|
||||||
member this.Index() =
|
|
||||||
logger.LogDebug(Newtonsoft.Json.JsonConvert.SerializeObject this.HttpContext.User)
|
|
||||||
async {
|
|
||||||
match this.HttpContext.User with
|
|
||||||
| :? AppUser as user -> return this.View "Dashboard" :> IActionResult
|
|
||||||
| _ -> return upcast this.View ()
|
|
||||||
}
|
|
||||||
|> Async.StartAsTask
|
|
@ -1,49 +0,0 @@
|
|||||||
namespace MyPrayerJournal.Controllers
|
|
||||||
|
|
||||||
open Microsoft.AspNetCore.Authorization
|
|
||||||
open Microsoft.AspNetCore.Mvc
|
|
||||||
open Microsoft.Extensions.Options
|
|
||||||
open MyPrayerJournal
|
|
||||||
open MyPrayerJournal.ViewModels
|
|
||||||
open RethinkDb.Driver.Net
|
|
||||||
|
|
||||||
/// Controller for all /user URLs
|
|
||||||
[<Authorize>]
|
|
||||||
[<Route("user")>]
|
|
||||||
type UserController(data : IConnection, cfg : IOptions<AppConfig>) =
|
|
||||||
inherit ApplicationController(data)
|
|
||||||
|
|
||||||
[<AllowAnonymous>]
|
|
||||||
[<HttpGet("log-on")>]
|
|
||||||
member this.ShowLogOn () =
|
|
||||||
this.View(LogOnViewModel())
|
|
||||||
|
|
||||||
[<AllowAnonymous>]
|
|
||||||
[<HttpPost("log-on")>]
|
|
||||||
[<ValidateAntiForgeryToken>]
|
|
||||||
member this.DoLogOn (form : LogOnViewModel) =
|
|
||||||
async {
|
|
||||||
let! user = data.LogOnUser form.Email (User.HashPassword form.Password cfg.Value.PasswordSaltBytes)
|
|
||||||
match user with
|
|
||||||
| Some usr -> do! this.HttpContext.Authentication.SignInAsync (Keys.Authentication, AppUser user)
|
|
||||||
// TODO: welcome message
|
|
||||||
(* this.Session.[Keys.User] <- usr
|
|
||||||
{ UserMessage.Empty with Level = Level.Info
|
|
||||||
Message = Strings.get "LogOnSuccess" }
|
|
||||||
|> model.AddMessage *)
|
|
||||||
return this.Redirect "/" :> IActionResult
|
|
||||||
| _ -> (*{ UserMessage.Empty with Level = Level.Error
|
|
||||||
Message = Strings.get "LogOnFailure" }
|
|
||||||
|> model.AddMessage
|
|
||||||
return this.Redirect "/user/log-on" model *)
|
|
||||||
return upcast this.RedirectToAction "ShowLogOn"
|
|
||||||
}
|
|
||||||
|> Async.StartAsTask
|
|
||||||
|
|
||||||
[<HttpGet("log-off")>]
|
|
||||||
member this.LogOff () =
|
|
||||||
async {
|
|
||||||
do! this.HttpContext.Authentication.SignOutAsync Keys.Authentication
|
|
||||||
// TODO: goodbye message
|
|
||||||
return this.LocalRedirect "/"
|
|
||||||
} |> Async.StartAsTask
|
|
@ -1,106 +0,0 @@
|
|||||||
[<AutoOpen>]
|
|
||||||
module MyPrayerJournal.Data
|
|
||||||
|
|
||||||
open Newtonsoft.Json
|
|
||||||
open RethinkDb.Driver
|
|
||||||
open RethinkDb.Driver.Ast
|
|
||||||
open RethinkDb.Driver.Net
|
|
||||||
open System
|
|
||||||
|
|
||||||
let private r = RethinkDB.R
|
|
||||||
|
|
||||||
/// Tables for data storage
|
|
||||||
module DataTable =
|
|
||||||
/// The table for prayer requests
|
|
||||||
[<Literal>]
|
|
||||||
let Request = "Request"
|
|
||||||
/// The table for users
|
|
||||||
[<Literal>]
|
|
||||||
let User = "User"
|
|
||||||
|
|
||||||
/// Extensions for the RethinkDB connection
|
|
||||||
type IConnection with
|
|
||||||
|
|
||||||
/// Log on a user
|
|
||||||
member this.LogOnUser (email : string) (passwordHash : string) =
|
|
||||||
async {
|
|
||||||
let! user = r.Table(DataTable.User)
|
|
||||||
.GetAll(email).OptArg("index", "Email")
|
|
||||||
.Filter(ReqlFunction1(fun usr -> upcast usr.["PasswordHash"].Eq(passwordHash)))
|
|
||||||
.RunResultAsync<User list>(this)
|
|
||||||
return user |> List.tryHead
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up the environment for MyPrayerJournal
|
|
||||||
member this.EstablishEnvironment (cfg : AppConfig) =
|
|
||||||
/// Shorthand for the database
|
|
||||||
let db () = r.Db("MyPrayerJournal")
|
|
||||||
// Be chatty about what we're doing
|
|
||||||
let mkStep step = sprintf "[MyPrayerJournal] %s" step
|
|
||||||
let logStep step = mkStep step |> Console.WriteLine
|
|
||||||
let logStepStart step = mkStep step |> Console.Write
|
|
||||||
let logStepEnd () = Console.WriteLine " done"
|
|
||||||
/// Ensure the database exists
|
|
||||||
let checkDatabase () =
|
|
||||||
async {
|
|
||||||
logStep "|> Checking database"
|
|
||||||
let! dbList = r.DbList().RunResultAsync<string list>(this)
|
|
||||||
match dbList |> List.contains "MyPrayerJournal" with
|
|
||||||
| true -> ()
|
|
||||||
| _ -> logStepStart " Database not found - creating..."
|
|
||||||
do! r.DbCreate("MyPrayerJournal").RunResultAsync(this)
|
|
||||||
logStepEnd ()
|
|
||||||
}
|
|
||||||
/// Ensure all tables exit
|
|
||||||
let checkTables () =
|
|
||||||
async {
|
|
||||||
logStep "|> Checking tables"
|
|
||||||
let! tables = db().TableList().RunResultAsync<string list>(this)
|
|
||||||
[ DataTable.Request; DataTable.User ]
|
|
||||||
|> List.filter (fun tbl -> not (tables |> List.contains tbl))
|
|
||||||
|> List.map (fun tbl ->
|
|
||||||
async {
|
|
||||||
logStepStart <| sprintf " %s table not found - creating..." tbl
|
|
||||||
do! db().TableCreate(tbl).RunResultAsync(this)
|
|
||||||
logStepEnd()
|
|
||||||
})
|
|
||||||
|> List.iter Async.RunSynchronously
|
|
||||||
// Seed the user table if it is empty
|
|
||||||
let! userCount = db().Table(DataTable.User).Count().RunResultAsync<int64>(this)
|
|
||||||
match int64 0 = userCount with
|
|
||||||
| true -> logStepStart " No users found - seeding..."
|
|
||||||
do! db().Table(DataTable.User).Insert(
|
|
||||||
{ User.Empty with
|
|
||||||
Id = Guid.NewGuid().ToString ()
|
|
||||||
Email = "test@example.com"
|
|
||||||
PasswordHash = User.HashPassword "password" cfg.PasswordSaltBytes
|
|
||||||
Name = "Default User"
|
|
||||||
TimeZone = "America/Chicago"
|
|
||||||
}).RunResultAsync(this)
|
|
||||||
logStepEnd ()
|
|
||||||
| _ -> ()
|
|
||||||
}
|
|
||||||
/// Ensure the proper indexes exist
|
|
||||||
let checkIndexes () =
|
|
||||||
async {
|
|
||||||
logStep "|> Checking indexes"
|
|
||||||
let! reqIdx = db().Table(DataTable.Request).IndexList().RunResultAsync<string list>(this)
|
|
||||||
match reqIdx |> List.contains "UserId" with
|
|
||||||
| true -> ()
|
|
||||||
| _ -> logStepStart <| sprintf " %s.UserId index not found - creating..." DataTable.Request
|
|
||||||
do! db().Table(DataTable.Request).IndexCreate("UserId").RunResultAsync(this)
|
|
||||||
logStepEnd ()
|
|
||||||
let! usrIdx = db().Table(DataTable.User).IndexList().RunResultAsync<string list>(this)
|
|
||||||
match usrIdx |> List.contains "Email" with
|
|
||||||
| true -> ()
|
|
||||||
| _ -> logStepStart <| sprintf " %s.Email index not found - creating..." DataTable.User
|
|
||||||
do! db().Table(DataTable.User).IndexCreate("Email").RunResultAsync(this)
|
|
||||||
logStepEnd ()
|
|
||||||
}
|
|
||||||
async {
|
|
||||||
logStep "Database checks starting"
|
|
||||||
do! checkDatabase ()
|
|
||||||
do! checkTables ()
|
|
||||||
do! checkIndexes ()
|
|
||||||
logStep "Database checks complete"
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
namespace MyPrayerJournal
|
|
||||||
|
|
||||||
open Newtonsoft.Json
|
|
||||||
open System.Security.Claims
|
|
||||||
open System.Security.Cryptography
|
|
||||||
|
|
||||||
/// A user
|
|
||||||
type User = {
|
|
||||||
/// The Id of the user
|
|
||||||
[<JsonProperty("id")>]
|
|
||||||
Id : string
|
|
||||||
/// The user's e-mail address
|
|
||||||
Email : string
|
|
||||||
/// A hash of the user's password
|
|
||||||
PasswordHash : string
|
|
||||||
/// The user's name
|
|
||||||
Name : string
|
|
||||||
/// The time zone in which the user resides
|
|
||||||
TimeZone : string
|
|
||||||
/// The last time the user logged on
|
|
||||||
LastSeenOn : int64
|
|
||||||
}
|
|
||||||
with
|
|
||||||
/// An empty User
|
|
||||||
static member Empty =
|
|
||||||
{ Id = ""
|
|
||||||
Email = ""
|
|
||||||
PasswordHash = ""
|
|
||||||
Name = ""
|
|
||||||
TimeZone = ""
|
|
||||||
LastSeenOn = int64 0 }
|
|
||||||
/// Hash a user's password
|
|
||||||
static member HashPassword (pw : string) (salt : byte[]) =
|
|
||||||
use hash = new Rfc2898DeriveBytes(pw, salt, 4096)
|
|
||||||
hash.GetBytes 512
|
|
||||||
|> Seq.fold (fun acc byt -> sprintf "%s%s" acc (byt.ToString "x2")) ""
|
|
||||||
|
|
||||||
|
|
||||||
/// Request history entry
|
|
||||||
type History = {
|
|
||||||
/// The instant at which the update was made
|
|
||||||
AsOf : int64
|
|
||||||
/// The action that was taken on the request
|
|
||||||
Action : string list
|
|
||||||
/// The status of the request (filled if it changed)
|
|
||||||
Status : string option
|
|
||||||
/// The text of the request (filled if it changed)
|
|
||||||
Text : string option
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A prayer request
|
|
||||||
type Request = {
|
|
||||||
/// The Id of the request
|
|
||||||
[<JsonProperty("id")>]
|
|
||||||
Id : string
|
|
||||||
/// The Id of the user to whom this request belongs
|
|
||||||
UserId : string
|
|
||||||
/// The instant this request was entered
|
|
||||||
EnteredOn : int64
|
|
||||||
/// The history for this request
|
|
||||||
History : History list
|
|
||||||
}
|
|
||||||
with
|
|
||||||
/// The current status of the prayer request
|
|
||||||
member this.Status =
|
|
||||||
this.History
|
|
||||||
|> List.sortBy (fun item -> -item.AsOf)
|
|
||||||
|> List.map (fun item -> item.Status)
|
|
||||||
|> List.filter Option.isSome
|
|
||||||
|> List.map Option.get
|
|
||||||
|> List.head
|
|
||||||
/// The current text of the prayer request
|
|
||||||
member this.Text =
|
|
||||||
this.History
|
|
||||||
|> List.sortBy (fun item -> -item.AsOf)
|
|
||||||
|> List.map (fun item -> item.Text)
|
|
||||||
|> List.filter Option.isSome
|
|
||||||
|> List.map Option.get
|
|
||||||
|> List.head
|
|
||||||
member this.LastActionOn =
|
|
||||||
this.History
|
|
||||||
|> List.sortBy (fun item -> -item.AsOf)
|
|
||||||
|> List.map (fun item -> item.AsOf)
|
|
||||||
|> List.head
|
|
||||||
|
|
||||||
/// The user for use with identity
|
|
||||||
[<AllowNullLiteral>]
|
|
||||||
type AppUser(user : User option) =
|
|
||||||
inherit ClaimsPrincipal()
|
|
||||||
|
|
||||||
/// The current user
|
|
||||||
member val User = user with get
|
|
@ -1,19 +0,0 @@
|
|||||||
[<AutoOpen>]
|
|
||||||
module MyPrayerJournal.Extensions
|
|
||||||
|
|
||||||
open Microsoft.FSharp.Control
|
|
||||||
open System.Threading.Tasks
|
|
||||||
|
|
||||||
// H/T: Suave
|
|
||||||
type AsyncBuilder with
|
|
||||||
/// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
|
|
||||||
/// a standard .NET task
|
|
||||||
member x.Bind(t : Task<'T>, f:'T -> Async<'R>) : Async<'R> = async.Bind(Async.AwaitTask t, f)
|
|
||||||
|
|
||||||
/// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
|
|
||||||
/// a standard .NET task which does not commpute a value
|
|
||||||
member x.Bind(t : Task, f : unit -> Async<'R>) : Async<'R> = async.Bind(Async.AwaitTask t, f)
|
|
||||||
|
|
||||||
/// Object to which common IStringLocalizer calls cal be made
|
|
||||||
type I18N() =
|
|
||||||
member this.X = ()
|
|
@ -1,49 +0,0 @@
|
|||||||
namespace MyPrayerJournal.ViewModels
|
|
||||||
|
|
||||||
//open MyPrayerJournal
|
|
||||||
|
|
||||||
/// Parent view model for all myPrayerJournal view models
|
|
||||||
type AppViewModel() =
|
|
||||||
member this.Q = "X"
|
|
||||||
(*
|
|
||||||
/// User messages
|
|
||||||
member val Messages = getMessages () with get, set
|
|
||||||
/// The currently logged in user
|
|
||||||
member val User = Option<User>.None with get, set
|
|
||||||
/// The title of the page
|
|
||||||
member val PageTitle = "" with get, set
|
|
||||||
/// The name and version of the application
|
|
||||||
member this.Generator = sprintf "myPrayerJournal %s" (ctx.Items.[Keys.Version].ToString ())
|
|
||||||
/// The request start time
|
|
||||||
member this.RequestStart = ctx.Items.[Keys.RequestStart] :?> int64
|
|
||||||
/// Is a user authenticated for this request?
|
|
||||||
member this.IsAuthenticated = "" <> this.User.Id
|
|
||||||
/// Add a message to the output
|
|
||||||
member this.AddMessage message = this.Messages <- message :: this.Messages
|
|
||||||
|
|
||||||
/// Display a long date
|
|
||||||
member this.DisplayLongDate ticks = FormatDateTime.longDate this.User.TimeZone ticks
|
|
||||||
/// Display a short date
|
|
||||||
member this.DisplayShortDate ticks = FormatDateTime.shortDate this.User.TimeZone ticks
|
|
||||||
/// Display the time
|
|
||||||
member this.DisplayTime ticks = FormatDateTime.time this.User.TimeZone ticks
|
|
||||||
/// The page title with the application name appended
|
|
||||||
member this.DisplayPageTitle =
|
|
||||||
match this.PageTitle with
|
|
||||||
| "" -> Strings.get "myPrayerJournal"
|
|
||||||
| pt -> sprintf "%s | %s" pt (Strings.get "myPrayerJournal")
|
|
||||||
|
|
||||||
/// An image with the version and load time in the tool tip
|
|
||||||
member this.FooterLogo =
|
|
||||||
seq {
|
|
||||||
yield "<span title=\""
|
|
||||||
yield sprintf "%s %s • " (Strings.get "PoweredBy") this.Generator
|
|
||||||
yield Strings.get "LoadedIn"
|
|
||||||
yield " "
|
|
||||||
yield TimeSpan(System.DateTime.Now.Ticks - this.RequestStart).TotalSeconds.ToString "f3"
|
|
||||||
yield " "
|
|
||||||
yield (Strings.get "Seconds").ToLower ()
|
|
||||||
yield "\"><span style=\"font-weight:100;\">my</span><span style=\"font-weight:600;\">Prayer</span><span style=\"font-weight:700;\">Journal</span></span>"
|
|
||||||
}
|
|
||||||
|> Seq.reduce (+)
|
|
||||||
*)
|
|
@ -1,16 +0,0 @@
|
|||||||
namespace MyPrayerJournal.ViewModels
|
|
||||||
|
|
||||||
open System.ComponentModel.DataAnnotations
|
|
||||||
|
|
||||||
type LogOnViewModel() =
|
|
||||||
inherit AppViewModel()
|
|
||||||
|
|
||||||
[<Required>]
|
|
||||||
[<DataType(DataType.EmailAddress)>]
|
|
||||||
[<Display(Name = "E-mail Address")>]
|
|
||||||
member val Email = "" with get, set
|
|
||||||
|
|
||||||
[<Required>]
|
|
||||||
[<DataType(DataType.Password)>]
|
|
||||||
[<Display(Name = "Password")>]
|
|
||||||
member val Password = "" with get, set
|
|
@ -1,14 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "";
|
|
||||||
}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<p> </p>
|
|
||||||
<p>myPrayerJournal is a place where individuals can record their prayer requests, record that they prayed for them,
|
|
||||||
update them as God moves in the situation, and record a final answer received on that request. It will also
|
|
||||||
allow individuals to review their answered prayers.</p>
|
|
||||||
<p>This site is currently in very limited alpha, as it is being developed with a core group of test
|
|
||||||
users. If this is something you are interested in using, check back around mid-November 2016 to check on the
|
|
||||||
development progress.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,14 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "Error";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1 class="text-danger">Error.</h1>
|
|
||||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
|
||||||
|
|
||||||
<h3>Development Mode</h3>
|
|
||||||
<p>
|
|
||||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
|
|
||||||
</p>
|
|
@ -1,80 +0,0 @@
|
|||||||
@{
|
|
||||||
var title = ViewData["Title"] as string;
|
|
||||||
var pageTitle = string.IsNullOrEmpty(title) ? "myPrayerJournal" : String.Format("{0} - myPrayerJournal", title);
|
|
||||||
var sw = ViewData[Keys.RequestTimer] as System.Diagnostics.Stopwatch;
|
|
||||||
}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta name="generator" content="@ViewData[Keys.Generator]" />
|
|
||||||
<title>@pageTitle</title>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
|
||||||
<environment names="Development">
|
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
|
|
||||||
<link rel="stylesheet" href="~/css/site.css" />
|
|
||||||
</environment>
|
|
||||||
<environment names="Staging,Production">
|
|
||||||
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css"
|
|
||||||
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
|
|
||||||
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
|
|
||||||
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
|
|
||||||
</environment>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<div class="navbar-header">
|
|
||||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
|
||||||
<span class="sr-only">Toggle navigation</span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
</button>
|
|
||||||
<a asp-controller="Home" asp-action="Index" class="navbar-brand"><span style="font-weight:100;">my</span><span style="font-weight:600;">Prayer</span><span style="font-weight:700;">Journal</span></a>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-collapse collapse">
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
|
||||||
@if (OptionModule.IsSome(ViewData[Keys.CurrentUser] as FSharpOption<User>)) {
|
|
||||||
<li><a asp-controller="User" asp-action="ShowChangePassword">Change Your Password</a></li>
|
|
||||||
<li><a asp-controller="User" asp-action="LogOff">Log Off</a></li>
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
<li><a asp-controller="User" asp-action="ShowLogOn">Log On</a></li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container body-content">
|
|
||||||
@if (!string.IsNullOrEmpty(ViewData["Title"] as string)) {
|
|
||||||
<h1 class="mpj-page-title">@ViewData["Title"]</h1>
|
|
||||||
}
|
|
||||||
@RenderBody()
|
|
||||||
<footer class="mpj-footer">
|
|
||||||
<p class="text-right" title="Loaded in @sw.Elapsed.ToString(@"s\.fff") seconds">@ViewData[Keys.Generator]</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<environment names="Development">
|
|
||||||
<script src="~/lib/jquery/dist/jquery.js"></script>
|
|
||||||
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
|
|
||||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
|
||||||
</environment>
|
|
||||||
<environment names="Staging,Production">
|
|
||||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.3.min.js"
|
|
||||||
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
|
|
||||||
asp-fallback-test="window.jQuery">
|
|
||||||
</script>
|
|
||||||
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js"
|
|
||||||
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
|
|
||||||
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
|
|
||||||
</script>
|
|
||||||
<script src="~/js/site.min.js" asp-append-version="true"></script>
|
|
||||||
</environment>
|
|
||||||
|
|
||||||
@RenderSection("Scripts", required: false)
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,14 +0,0 @@
|
|||||||
<environment names="Development">
|
|
||||||
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
|
|
||||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
|
|
||||||
</environment>
|
|
||||||
<environment names="Staging,Production">
|
|
||||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.15.0/jquery.validate.min.js"
|
|
||||||
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
|
|
||||||
asp-fallback-test="window.jQuery && window.jQuery.validator">
|
|
||||||
</script>
|
|
||||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
|
|
||||||
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
|
|
||||||
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive">
|
|
||||||
</script>
|
|
||||||
</environment>
|
|
@ -1,40 +0,0 @@
|
|||||||
@model LogOnViewModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Log On";
|
|
||||||
}
|
|
||||||
<form asp-action="DoLogOn" asp-controller="User" method="post">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-offset-1 col-sm-8 col-md-offset-3 col-md-6">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon" title="E-mail Address"><i class="material-icons md-18">email</i></span>
|
|
||||||
<input asp-for="Email" class="form-control" placeholder="E-mail Address" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-offset-1 col-sm-8 col-md-offset-3 col-md-6">
|
|
||||||
<br />
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon" title="Password"><i class="material-icons md-18">security</i></span>
|
|
||||||
<input asp-for="Password" class="form-control" placeholder="Password" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 text-center">
|
|
||||||
<p>
|
|
||||||
<br />
|
|
||||||
<button class="btn btn-primary"><i class="material-icons md-18">verified_user</i> Log On</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
@section Scripts
|
|
||||||
{
|
|
||||||
<script type="text/javascript">
|
|
||||||
/* <![CDATA[ */
|
|
||||||
$(document).ready(function () { $("#Email").focus() })
|
|
||||||
/* ]]> */
|
|
||||||
</script>
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
@using Microsoft.Extensions.Localization
|
|
||||||
@using Microsoft.FSharp.Core
|
|
||||||
@using MyPrayerJournal
|
|
||||||
@using MyPrayerJournal.ViewModels
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
|
@ -1,3 +0,0 @@
|
|||||||
@{
|
|
||||||
Layout = "_Layout";
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"IncludeScopes": false,
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Debug",
|
|
||||||
"System": "Information",
|
|
||||||
"Microsoft": "Information"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"MyPrayerJournal": {
|
|
||||||
/*
|
|
||||||
* myPrayerJournal Configuration
|
|
||||||
*
|
|
||||||
* *** SECURITY OPTIONS ***
|
|
||||||
*
|
|
||||||
* https://www.grc.com/passwords.htm is a great source of high-entropy passwords. Although what is here looks
|
|
||||||
* strong, keep in mind that it's what's in source control, so all instances of myPrayerJournal could be using this
|
|
||||||
* value; that severly decreases its usefulness. :)
|
|
||||||
*
|
|
||||||
* WARNING: Changing this will render every single user's login inaccessible, including yours. Do not change it
|
|
||||||
* once you have started this instance for the first time.
|
|
||||||
*/
|
|
||||||
"PasswordSalt": "oIvatPlrBh5DjeBVWvX3vvePHAgbbzUm7BazZM2IKlUsTtDuPJFbF3KvIiQPdLt",
|
|
||||||
/*
|
|
||||||
* *** DATA OPTIONS ***
|
|
||||||
*
|
|
||||||
* Configure RethinkDB options here; any options not specified will take the driver's default. Available options are
|
|
||||||
* Hostname (string), Port (int), Database (string), AuthKey (string), and Timeout (int).
|
|
||||||
*/
|
|
||||||
"DataConfig": {
|
|
||||||
"Database": "MyPrayerJournal",
|
|
||||||
"Hostname": "severus-server"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "myprayerjournal",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"bootstrap": "3.3.6",
|
|
||||||
"jquery": "2.2.3",
|
|
||||||
"jquery-validation": "1.15.0",
|
|
||||||
"jquery-validation-unobtrusive": "3.2.6"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
/// <binding Clean='clean' />
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var gulp = require("gulp"),
|
|
||||||
rimraf = require("rimraf"),
|
|
||||||
concat = require("gulp-concat"),
|
|
||||||
cssmin = require("gulp-cssmin"),
|
|
||||||
uglify = require("gulp-uglify");
|
|
||||||
|
|
||||||
var webroot = "./wwwroot/";
|
|
||||||
|
|
||||||
var paths = {
|
|
||||||
js: webroot + "js/**/*.js",
|
|
||||||
minJs: webroot + "js/**/*.min.js",
|
|
||||||
css: webroot + "css/**/*.css",
|
|
||||||
minCss: webroot + "css/**/*.min.css",
|
|
||||||
concatJsDest: webroot + "js/site.min.js",
|
|
||||||
concatCssDest: webroot + "css/site.min.css"
|
|
||||||
};
|
|
||||||
|
|
||||||
gulp.task("clean:js", function (cb) {
|
|
||||||
rimraf(paths.concatJsDest, cb);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("clean:css", function (cb) {
|
|
||||||
rimraf(paths.concatCssDest, cb);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("clean", ["clean:js", "clean:css"]);
|
|
||||||
|
|
||||||
gulp.task("min:js", function () {
|
|
||||||
return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
|
|
||||||
.pipe(concat(paths.concatJsDest))
|
|
||||||
.pipe(uglify())
|
|
||||||
.pipe(gulp.dest("."));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("min:css", function () {
|
|
||||||
return gulp.src([paths.css, "!" + paths.minCss])
|
|
||||||
.pipe(concat(paths.concatCssDest))
|
|
||||||
.pipe(cssmin())
|
|
||||||
.pipe(gulp.dest("."));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("min", ["min:js", "min:css"]);
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "myprayerjournal",
|
|
||||||
"version": "0.8.0",
|
|
||||||
"private": true,
|
|
||||||
"devDependencies": {
|
|
||||||
"gulp": "3.9.1",
|
|
||||||
"gulp-concat": "2.6.0",
|
|
||||||
"gulp-cssmin": "0.1.7",
|
|
||||||
"gulp-uglify": "1.5.3",
|
|
||||||
"rimraf": "2.5.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
{
|
|
||||||
"userSecretsId": "aspnet-WebApplication-0799fe3e-6eaf-4c5f-b40e-7c6bfd5dfa9a",
|
|
||||||
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.NETCore.App": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"type": "platform"
|
|
||||||
},
|
|
||||||
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
|
|
||||||
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
|
|
||||||
"Microsoft.AspNetCore.Mvc": "1.0.1",
|
|
||||||
"Microsoft.AspNetCore.Razor.Tools": {
|
|
||||||
"version": "1.0.0-preview2-final",
|
|
||||||
"type": "build"
|
|
||||||
},
|
|
||||||
"Microsoft.AspNetCore.Routing": "1.0.1",
|
|
||||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
|
|
||||||
"Microsoft.AspNetCore.Session": "1.0.0",
|
|
||||||
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Configuration.Json": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Logging": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Logging.Console": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Logging.Debug": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
|
|
||||||
"Microsoft.FSharp.Core.netcore": "1.0.0-alpha-160831",
|
|
||||||
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",
|
|
||||||
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
|
|
||||||
"version": "1.0.0-preview2-update1",
|
|
||||||
"type": "build"
|
|
||||||
},
|
|
||||||
"Microsoft.VisualStudio.Web.CodeGenerators.Mvc": {
|
|
||||||
"version": "1.0.0-preview2-update1",
|
|
||||||
"type": "build"
|
|
||||||
},
|
|
||||||
"Newtonsoft.Json": "9.0.1",
|
|
||||||
"NodaTime": "2.0.0-alpha20160729",
|
|
||||||
"RethinkDB.DistributedCache": "0.9.0-alpha01",
|
|
||||||
"RethinkDb.Driver": "2.3.15"
|
|
||||||
},
|
|
||||||
|
|
||||||
"tools": {
|
|
||||||
"dotnet-compile-fsc":"1.0.0-preview2-*",
|
|
||||||
"Microsoft.AspNetCore.Razor.Tools": {
|
|
||||||
"version": "1.0.0-preview2-final",
|
|
||||||
"imports": "portable-net45+win8+dnxcore50"
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.SecretManager.Tools": {
|
|
||||||
"version": "1.0.0-preview2-final",
|
|
||||||
"imports": "portable-net45+win8+dnxcore50"
|
|
||||||
},
|
|
||||||
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
|
|
||||||
"version": "1.0.0-preview2-final",
|
|
||||||
"imports": [
|
|
||||||
"portable-net45+win8+dnxcore50",
|
|
||||||
"portable-net45+win8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"frameworks": {
|
|
||||||
"netcoreapp1.0": {
|
|
||||||
"imports": [
|
|
||||||
"dotnet5.6",
|
|
||||||
"dnxcore50",
|
|
||||||
"portable-net45+win8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"buildOptions": {
|
|
||||||
"compile": {
|
|
||||||
"exclude": "**/*",
|
|
||||||
"includeFiles": [
|
|
||||||
"Helpers/Extensions.fs",
|
|
||||||
"Configuration/Configuration.fs",
|
|
||||||
"Configuration/Keys.fs",
|
|
||||||
"Data/Entities.fs",
|
|
||||||
"Data/Data.fs",
|
|
||||||
"Models/ViewModels/AppViewModel.fs",
|
|
||||||
"Models/ViewModels/User/LogOnViewModel.fs",
|
|
||||||
"Controllers/ApplicationController.fs",
|
|
||||||
"Controllers/HomeController.fs",
|
|
||||||
"Controllers/UserController.fs",
|
|
||||||
"App.fs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"compilerName": "fsc",
|
|
||||||
"debugType": "portable",
|
|
||||||
"emitEntryPoint": true,
|
|
||||||
"preserveCompilationContext": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"runtimeOptions": {
|
|
||||||
"configProperties": {
|
|
||||||
"System.GC.Server": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"publishOptions": {
|
|
||||||
"include": [
|
|
||||||
"wwwroot",
|
|
||||||
"**/*.cshtml",
|
|
||||||
"appsettings.json",
|
|
||||||
"web.config"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"scripts": {
|
|
||||||
"prepublish": [ "npm install", "bower install", "gulp clean", "gulp min" ],
|
|
||||||
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
|
||||||
},
|
|
||||||
|
|
||||||
"tooling": {
|
|
||||||
"defaultNamespace": "MyPrayerJournal"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Configure your application settings in appsettings.json. Learn more at https://go.microsoft.com/fwlink/?LinkId=786380
|
|
||||||
-->
|
|
||||||
|
|
||||||
<system.webServer>
|
|
||||||
<handlers>
|
|
||||||
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
|
|
||||||
</handlers>
|
|
||||||
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
|
|
||||||
</system.webServer>
|
|
||||||
</configuration>
|
|
1
src/MyPrayerJournal/wwwroot/css/site.min.css
vendored
1
src/MyPrayerJournal/wwwroot/css/site.min.css
vendored
@ -1 +0,0 @@
|
|||||||
body{padding-top:50px;padding-bottom:20px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.btn-bracketed::before{display:inline-block;content:"[";padding-right:.5em}.btn-bracketed::after{display:inline-block;content:"]";padding-left:.5em}@media screen and (max-width:767px){.carousel-caption{display:none}}.material-icons.md-18{font-size:18px}.material-icons.md-24{font-size:24px}.material-icons.md-36{font-size:36px}.material-icons.md-48{font-size:48px}.material-icons{vertical-align:middle}
|
|
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
@ -1 +0,0 @@
|
|||||||
// Write your Javascript code.
|
|
22
src/Program.fs
Normal file
22
src/Program.fs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Learn more about F# at http://fsharp.org
|
||||||
|
|
||||||
|
open System.IO
|
||||||
|
open Suave
|
||||||
|
open Suave.Filters
|
||||||
|
open Suave.Operators
|
||||||
|
|
||||||
|
let app : WebPart =
|
||||||
|
choose [
|
||||||
|
//GET >=> path "/" >=> Files.file "index.html"
|
||||||
|
//GET >=> path "" >=> Files.file "index.html"
|
||||||
|
GET >=> Files.browseHome
|
||||||
|
GET >=> Files.browseFileHome "index.html"
|
||||||
|
RequestErrors.NOT_FOUND "Page not found."
|
||||||
|
]
|
||||||
|
[<EntryPoint>]
|
||||||
|
let main argv =
|
||||||
|
let config =
|
||||||
|
{ defaultConfig with homeFolder = Some (Path.GetFullPath "./wwwroot/") }
|
||||||
|
|
||||||
|
startWebServer config app
|
||||||
|
0 // return an integer exit code
|
17
src/elm-package.json
Normal file
17
src/elm-package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": "0.8.1",
|
||||||
|
"summary": "A place to record requests, prayers, and answers",
|
||||||
|
"repository": "https://github.com/user/project.git",
|
||||||
|
"license": "MIT",
|
||||||
|
"source-directories": [
|
||||||
|
"wwwroot"
|
||||||
|
],
|
||||||
|
"exposed-modules": [],
|
||||||
|
"dependencies": {
|
||||||
|
"elm-lang/core": "5.0.0 <= v < 6.0.0",
|
||||||
|
"elm-lang/html": "2.0.0 <= v < 3.0.0",
|
||||||
|
"elm-lang/navigation": "2.0.1 <= v < 3.0.0",
|
||||||
|
"evancz/url-parser": "2.0.1 <= v < 3.0.0"
|
||||||
|
},
|
||||||
|
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||||
|
}
|
30
src/project.json
Normal file
30
src/project.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
"buildOptions": {
|
||||||
|
"debugType": "portable",
|
||||||
|
"emitEntryPoint": true,
|
||||||
|
"compilerName": "fsc",
|
||||||
|
"compile": {
|
||||||
|
"includeFiles": [
|
||||||
|
"Program.fs"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"Suave": "2.0.0-rc2"
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"dotnet-compile-fsc": "1.0.0-preview2.1-*"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"netcoreapp1.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"type": "platform",
|
||||||
|
"version": "1.1.0"
|
||||||
|
},
|
||||||
|
"Microsoft.FSharp.Core.netcore": "1.0.0-alpha-161111"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/wwwroot/App.elm
Normal file
26
src/wwwroot/App.elm
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
module App exposing (..)
|
||||||
|
|
||||||
|
import Messages exposing (..)
|
||||||
|
import Models exposing (Model, initialModel)
|
||||||
|
import Navigation exposing (Location)
|
||||||
|
import Routing exposing (Route(..), parseLocation)
|
||||||
|
import Update exposing (update)
|
||||||
|
import View exposing (view)
|
||||||
|
|
||||||
|
|
||||||
|
init : Location -> (Model, Cmd Msg)
|
||||||
|
init location =
|
||||||
|
let
|
||||||
|
currentRoute = Home --parseLocation location
|
||||||
|
in
|
||||||
|
(initialModel currentRoute, Cmd.none)
|
||||||
|
|
||||||
|
|
||||||
|
main : Program Never Model Msg
|
||||||
|
main =
|
||||||
|
Navigation.program OnLocationChange
|
||||||
|
{ init = init
|
||||||
|
, view = view
|
||||||
|
, update = update
|
||||||
|
, subscriptions = \_ -> Sub.none
|
||||||
|
}
|
19
src/wwwroot/Home/Public.elm
Normal file
19
src/wwwroot/Home/Public.elm
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module Home.Public exposing (view)
|
||||||
|
|
||||||
|
import Html exposing (Html, p, text)
|
||||||
|
import Messages exposing (Msg(..))
|
||||||
|
import Models exposing (Model)
|
||||||
|
import Utils.View exposing (fullRow)
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> List (Html Msg)
|
||||||
|
view model =
|
||||||
|
let
|
||||||
|
paragraphs =
|
||||||
|
[ " "
|
||||||
|
, "myPrayerJournal is a place where individuals can record their prayer requests, record that they prayed for them, update them as God moves in the situation, and record a final answer received on that request. It will also allow individuals to review their answered prayers."
|
||||||
|
, "This site is currently in very limited alpha, as it is being developed with a core group of test users. If this is something you are interested in using, check back around mid-November 2016 to check on the development progress."
|
||||||
|
]
|
||||||
|
|> List.map (\para -> p [] [ text para ])
|
||||||
|
in
|
||||||
|
[ fullRow paragraphs ]
|
9
src/wwwroot/Messages.elm
Normal file
9
src/wwwroot/Messages.elm
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module Messages exposing (..)
|
||||||
|
|
||||||
|
import Navigation exposing (Location)
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= OnLocationChange Location
|
||||||
|
| NavTo String
|
||||||
|
| UpdateTitle String
|
16
src/wwwroot/Models.elm
Normal file
16
src/wwwroot/Models.elm
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module Models exposing (..)
|
||||||
|
|
||||||
|
import Routing exposing (Route(..))
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ route : Route
|
||||||
|
, title : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
initialModel : Route -> Model
|
||||||
|
initialModel route =
|
||||||
|
{ route = route
|
||||||
|
, title = "Index"
|
||||||
|
}
|
31
src/wwwroot/Routing.elm
Normal file
31
src/wwwroot/Routing.elm
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
module Routing exposing (..)
|
||||||
|
|
||||||
|
import Navigation exposing (Location)
|
||||||
|
import UrlParser exposing (..)
|
||||||
|
|
||||||
|
|
||||||
|
type Route
|
||||||
|
= Home
|
||||||
|
| ChangePassword
|
||||||
|
| LogOff
|
||||||
|
| LogOn
|
||||||
|
| NotFound
|
||||||
|
|
||||||
|
|
||||||
|
findRoute : Parser (Route -> a) a
|
||||||
|
findRoute =
|
||||||
|
oneOf
|
||||||
|
[ map Home top
|
||||||
|
, map LogOn (s "user" </> s "log-on")
|
||||||
|
, map LogOff (s "user" </> s "log-off")
|
||||||
|
, map ChangePassword (s "user" </> s "password" </> s "change")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
parseLocation : Location -> Route
|
||||||
|
parseLocation location =
|
||||||
|
case (parsePath findRoute location) of
|
||||||
|
Just route ->
|
||||||
|
route
|
||||||
|
Nothing ->
|
||||||
|
NotFound
|
21
src/wwwroot/Update.elm
Normal file
21
src/wwwroot/Update.elm
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module Update exposing (..)
|
||||||
|
|
||||||
|
import Models exposing (Model)
|
||||||
|
import Messages exposing (Msg(..))
|
||||||
|
import Navigation exposing (newUrl)
|
||||||
|
import Routing exposing (parseLocation)
|
||||||
|
import Utils.View exposing (documentTitle)
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
OnLocationChange location ->
|
||||||
|
let
|
||||||
|
newRoute = parseLocation location
|
||||||
|
in
|
||||||
|
({model | route = newRoute}, Cmd.none)
|
||||||
|
NavTo url ->
|
||||||
|
(model, newUrl url)
|
||||||
|
UpdateTitle newTitle ->
|
||||||
|
(model, documentTitle newTitle)
|
47
src/wwwroot/Utils/View.elm
Normal file
47
src/wwwroot/Utils/View.elm
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
port module Utils.View exposing (..)
|
||||||
|
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (class, href, style, title)
|
||||||
|
import Html.Events exposing (defaultOptions, onWithOptions)
|
||||||
|
import Json.Decode as Json
|
||||||
|
import Messages exposing (Msg(..))
|
||||||
|
|
||||||
|
|
||||||
|
-- Set the document title
|
||||||
|
port documentTitle : String -> Cmd a
|
||||||
|
|
||||||
|
|
||||||
|
-- Wrap the given content in a row
|
||||||
|
row : List (Html Msg) -> Html Msg
|
||||||
|
row columns =
|
||||||
|
div [ class "row "] columns
|
||||||
|
|
||||||
|
|
||||||
|
-- Display the given content in a full row
|
||||||
|
fullRow : List (Html Msg) -> Html Msg
|
||||||
|
fullRow content =
|
||||||
|
row
|
||||||
|
[ div
|
||||||
|
[ class "col-xs-12" ]
|
||||||
|
content
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
-- Create a navigation link
|
||||||
|
navLink : String -> String -> List (Attribute Msg) -> Html Msg
|
||||||
|
navLink url linkText attrs =
|
||||||
|
let
|
||||||
|
attributes =
|
||||||
|
List.concat
|
||||||
|
[
|
||||||
|
[ title linkText
|
||||||
|
, onWithOptions
|
||||||
|
"click" { defaultOptions | preventDefault = True }
|
||||||
|
<| Json.succeed
|
||||||
|
<| NavTo url
|
||||||
|
, href url
|
||||||
|
]
|
||||||
|
, attrs
|
||||||
|
]
|
||||||
|
in
|
||||||
|
a attributes [ text linkText ]
|
104
src/wwwroot/View.elm
Normal file
104
src/wwwroot/View.elm
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
module View exposing (view)
|
||||||
|
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (attribute, class)
|
||||||
|
import Messages exposing (Msg(..))
|
||||||
|
import Models exposing (..)
|
||||||
|
import Routing exposing (Route(..))
|
||||||
|
import Utils.View exposing (documentTitle, navLink)
|
||||||
|
|
||||||
|
import Home.Public
|
||||||
|
|
||||||
|
|
||||||
|
-- Layout functions
|
||||||
|
|
||||||
|
navigation : List (Html Msg)
|
||||||
|
navigation =
|
||||||
|
[ navLink "/user/password/change" "Change Your Password" []
|
||||||
|
, navLink "/user/log-off" "Log Off" []
|
||||||
|
, navLink "/user/log-on" "Log On" []
|
||||||
|
]
|
||||||
|
|> List.map (\anchor -> li [] [ anchor ])
|
||||||
|
|
||||||
|
|
||||||
|
pageHeader : Html Msg
|
||||||
|
pageHeader =
|
||||||
|
div
|
||||||
|
[ class "navbar navbar-inverse navbar-fixed-top" ]
|
||||||
|
[ div
|
||||||
|
[ class "container" ]
|
||||||
|
[ div
|
||||||
|
[ class "navbar-header" ]
|
||||||
|
[ button
|
||||||
|
[ class "navbar-toggle"
|
||||||
|
, attribute "data-toggle" "collapse"
|
||||||
|
, attribute "data-target" ".navbar-collapse"
|
||||||
|
]
|
||||||
|
[ span [ class "sr-only" ] [ text "Toggle navigation" ]
|
||||||
|
, span [ class "icon-bar" ] []
|
||||||
|
, span [ class "icon-bar" ] []
|
||||||
|
, span [ class "icon-bar" ] []
|
||||||
|
]
|
||||||
|
, navLink "/" "myPrayerJournal" [ class "navbar-brand" ]
|
||||||
|
]
|
||||||
|
, div
|
||||||
|
[ class "navbar-collapse collapse" ]
|
||||||
|
[ ul
|
||||||
|
[ class "nav navbar-nav navbar-right" ]
|
||||||
|
navigation
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
pageTitle : String -> Html Msg
|
||||||
|
pageTitle title =
|
||||||
|
let
|
||||||
|
x = documentTitle <| title ++ " | myPrayerJournal"
|
||||||
|
in
|
||||||
|
h2 [ class "page-title" ] [ text title ]
|
||||||
|
|
||||||
|
|
||||||
|
pageFooter : Html Msg
|
||||||
|
pageFooter =
|
||||||
|
footer
|
||||||
|
[ class "mpj-footer" ]
|
||||||
|
[ p
|
||||||
|
[ class "text-right" ]
|
||||||
|
[ text "myPrayerJournal v0.8.1" ]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
layout : Model -> String -> List (Html Msg) -> Html Msg
|
||||||
|
layout model pgTitle contents =
|
||||||
|
let
|
||||||
|
pageContent =
|
||||||
|
[ [ pageTitle pgTitle ]
|
||||||
|
, contents
|
||||||
|
, [ pageFooter ]
|
||||||
|
]
|
||||||
|
|> List.concat
|
||||||
|
in
|
||||||
|
div []
|
||||||
|
[ pageHeader
|
||||||
|
, div
|
||||||
|
[ class "container body-content" ]
|
||||||
|
pageContent
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
-- View functions
|
||||||
|
|
||||||
|
view : Model -> Html Msg
|
||||||
|
view model =
|
||||||
|
case model.route of
|
||||||
|
ChangePassword ->
|
||||||
|
layout model "Change Your Password" [ text "password change page goes here" ]
|
||||||
|
Home ->
|
||||||
|
layout model "Welcome" (Home.Public.view model)
|
||||||
|
LogOff ->
|
||||||
|
layout model "Log Off" [ text "Log off page goes hwere" ]
|
||||||
|
LogOn ->
|
||||||
|
layout model "Log On" [ text "Log On page goes here" ]
|
||||||
|
NotFound ->
|
||||||
|
layout model "Page Not Found" [ text "404, dude" ]
|
10330
src/wwwroot/app.js
Normal file
10330
src/wwwroot/app.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,39 +10,6 @@ body {
|
|||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set widths on the form inputs since otherwise they're 100% wide */
|
|
||||||
/*input,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
max-width: 280px;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/* Carousel */
|
|
||||||
.carousel-caption p {
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* buttons and links extension to use brackets: [ click me ] */
|
|
||||||
.btn-bracketed::before {
|
|
||||||
display:inline-block;
|
|
||||||
content: "[";
|
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
.btn-bracketed::after {
|
|
||||||
display:inline-block;
|
|
||||||
content: "]";
|
|
||||||
padding-left: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide/rearrange for smaller screens */
|
|
||||||
@media screen and (max-width: 767px) {
|
|
||||||
/* Hide captions */
|
|
||||||
.carousel-caption {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.material-icons.md-18 {
|
.material-icons.md-18 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
24
src/wwwroot/index.html
Normal file
24
src/wwwroot/index.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>myPrayerJournal</title>
|
||||||
|
<base href="/">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/content/styles.css">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||||
|
<script src="/app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script>
|
||||||
|
var app = Elm.App.embed(document.getElementById('app'))
|
||||||
|
app.ports.documentTitle.subscribe(function (title)
|
||||||
|
{
|
||||||
|
alert("Setting title to " + title)
|
||||||
|
document.title = title
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user