Version 3 #67
@ -116,20 +116,20 @@ module private Helpers =
|
||||
|
||||
|
||||
/// Retrieve a request, including its history and notes, by its ID and user ID
|
||||
let tryFullRequestById reqId userId (db : LiteDatabase) = task {
|
||||
let tryFullRequestById reqId userId (db : LiteDatabase) = backgroundTask {
|
||||
let! req = db.requests.Find (Query.EQ ("_id", RequestId.toString reqId)) |> firstAsync
|
||||
return match box req with null -> None | _ when req.userId = userId -> Some req | _ -> None
|
||||
}
|
||||
|
||||
/// Add a history entry
|
||||
let addHistory reqId userId hist db = task {
|
||||
let addHistory reqId userId hist db = backgroundTask {
|
||||
match! tryFullRequestById reqId userId db with
|
||||
| Some req -> do! doUpdate db { req with history = hist :: req.history }
|
||||
| None -> invalidOp $"{RequestId.toString reqId} not found"
|
||||
}
|
||||
|
||||
/// Add a note
|
||||
let addNote reqId userId note db = task {
|
||||
let addNote reqId userId note db = backgroundTask {
|
||||
match! tryFullRequestById reqId userId db with
|
||||
| Some req -> do! doUpdate db { req with notes = note :: req.notes }
|
||||
| None -> invalidOp $"{RequestId.toString reqId} not found"
|
||||
@ -140,7 +140,7 @@ let addRequest (req : Request) (db : LiteDatabase) =
|
||||
db.requests.Insert req |> ignore
|
||||
|
||||
/// Retrieve all answered requests for the given user
|
||||
let answeredRequests userId (db : LiteDatabase) = task {
|
||||
let answeredRequests userId (db : LiteDatabase) = backgroundTask {
|
||||
let! reqs = db.requests.Find (Query.EQ ("userId", UserId.toString userId)) |> toListAsync
|
||||
return
|
||||
reqs
|
||||
@ -151,7 +151,7 @@ let answeredRequests userId (db : LiteDatabase) = task {
|
||||
}
|
||||
|
||||
/// Retrieve the user's current journal
|
||||
let journalByUserId userId (db : LiteDatabase) = task {
|
||||
let journalByUserId userId (db : LiteDatabase) = backgroundTask {
|
||||
let! jrnl = db.requests.Find (Query.EQ ("userId", UserId.toString userId)) |> toListAsync
|
||||
return
|
||||
jrnl
|
||||
@ -162,38 +162,38 @@ let journalByUserId userId (db : LiteDatabase) = task {
|
||||
}
|
||||
|
||||
/// Retrieve a request by its ID and user ID (without notes and history)
|
||||
let tryRequestById reqId userId db = task {
|
||||
let tryRequestById reqId userId db = backgroundTask {
|
||||
let! req = tryFullRequestById reqId userId db
|
||||
return req |> Option.map (fun r -> { r with history = []; notes = [] })
|
||||
}
|
||||
|
||||
/// Retrieve notes for a request by its ID and user ID
|
||||
let notesById reqId userId (db : LiteDatabase) = task {
|
||||
let notesById reqId userId (db : LiteDatabase) = backgroundTask {
|
||||
match! tryFullRequestById reqId userId db with | Some req -> return req.notes | None -> return []
|
||||
}
|
||||
|
||||
/// Retrieve a journal request by its ID and user ID
|
||||
let tryJournalById reqId userId (db : LiteDatabase) = task {
|
||||
let tryJournalById reqId userId (db : LiteDatabase) = backgroundTask {
|
||||
let! req = tryFullRequestById reqId userId db
|
||||
return req |> Option.map JournalRequest.ofRequestLite
|
||||
}
|
||||
|
||||
/// Update the recurrence for a request
|
||||
let updateRecurrence reqId userId recurType recurCount db = task {
|
||||
let updateRecurrence reqId userId recurType recurCount db = backgroundTask {
|
||||
match! tryFullRequestById reqId userId db with
|
||||
| Some req -> do! doUpdate db { req with recurType = recurType; recurCount = recurCount }
|
||||
| None -> invalidOp $"{RequestId.toString reqId} not found"
|
||||
}
|
||||
|
||||
/// Update a snoozed request
|
||||
let updateSnoozed reqId userId until db = task {
|
||||
let updateSnoozed reqId userId until db = backgroundTask {
|
||||
match! tryFullRequestById reqId userId db with
|
||||
| Some req -> do! doUpdate db { req with snoozedUntil = until; showAfter = until }
|
||||
| None -> invalidOp $"{RequestId.toString reqId} not found"
|
||||
}
|
||||
|
||||
/// Update the "show after" timestamp for a request
|
||||
let updateShowAfter reqId userId showAfter db = task {
|
||||
let updateShowAfter reqId userId showAfter db = backgroundTask {
|
||||
match! tryFullRequestById reqId userId db with
|
||||
| Some req -> do! doUpdate db { req with showAfter = showAfter }
|
||||
| None -> invalidOp $"{RequestId.toString reqId} not found"
|
||||
|
@ -17,7 +17,7 @@ module private LogOnHelpers =
|
||||
|
||||
/// Log on, optionally specifying a redirected URL once authentication is complete
|
||||
let logOn url : HttpHandler =
|
||||
fun next ctx -> task {
|
||||
fun next ctx -> backgroundTask {
|
||||
match url with
|
||||
| Some it ->
|
||||
do! ctx.ChallengeAsync ("Auth0", AuthenticationProperties (RedirectUri = it))
|
||||
@ -102,7 +102,7 @@ module private Helpers =
|
||||
/// Render a component result
|
||||
let renderComponent nodes : HttpHandler =
|
||||
noResponseCaching
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
return! ctx.WriteHtmlStringAsync (ViewEngine.RenderView.AsString.htmlNodes nodes)
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ module private Helpers =
|
||||
|
||||
/// Composable handler to write a view to the output
|
||||
let writeView view : HttpHandler =
|
||||
fun next ctx -> task {
|
||||
fun next ctx -> backgroundTask {
|
||||
return! ctx.WriteHtmlViewAsync view
|
||||
}
|
||||
|
||||
@ -166,6 +166,10 @@ module private Helpers =
|
||||
/// Add a success message header to the response
|
||||
let withSuccessMessage : string -> HttpHandler =
|
||||
sprintf "success|||%s" >> setHttpHeader "X-Toast"
|
||||
|
||||
/// Hide a modal window when the response is sent
|
||||
let hideModal (name : string) : HttpHandler =
|
||||
setHttpHeader "X-Hide-Modal" name
|
||||
|
||||
|
||||
/// Strongly-typed models for post requests
|
||||
@ -213,7 +217,7 @@ module Components =
|
||||
// GET /components/journal-items
|
||||
let journalItems : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let shouldShow now r = now > Ticks.toLong r.snoozedUntil && now > Ticks.toLong r.showAfter
|
||||
let! jrnl = Data.journalByUserId (userId ctx) (db ctx)
|
||||
let shown = jrnl |> List.filter (shouldShow ((jsNow >> Ticks.toLong) ()))
|
||||
@ -223,7 +227,7 @@ module Components =
|
||||
// GET /components/request-item/[req-id]
|
||||
let requestItem reqId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
match! Data.tryJournalById (RequestId.ofString reqId) (userId ctx) (db ctx) with
|
||||
| Some req -> return! renderComponent [ Views.Request.reqListItem req ] next ctx
|
||||
| None -> return! Error.notFound next ctx
|
||||
@ -237,7 +241,7 @@ module Components =
|
||||
/// GET /components/request/[req-id]/notes
|
||||
let notes requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let! notes = Data.notesById (RequestId.ofString requestId) (userId ctx) (db ctx)
|
||||
return! renderComponent (Views.Request.notes notes) next ctx
|
||||
}
|
||||
@ -257,7 +261,7 @@ module Journal =
|
||||
// GET /journal
|
||||
let journal : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let usr =
|
||||
ctx.User.Claims
|
||||
|> Seq.tryFind (fun c -> c.Type = ClaimTypes.GivenName)
|
||||
@ -286,7 +290,7 @@ module Request =
|
||||
// GET /request/[req-id]/edit
|
||||
let edit requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let returnTo =
|
||||
match ctx.Request.Headers.Referer.[0] with
|
||||
| it when it.EndsWith "/active" -> "active"
|
||||
@ -305,7 +309,7 @@ module Request =
|
||||
// PATCH /request/[req-id]/prayed
|
||||
let prayed requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let db = db ctx
|
||||
let usrId = userId ctx
|
||||
let reqId = RequestId.ofString requestId
|
||||
@ -323,26 +327,26 @@ module Request =
|
||||
| None -> return! Error.notFound next ctx
|
||||
}
|
||||
|
||||
/// POST /api/request/[req-id]/note
|
||||
/// POST /request/[req-id]/note
|
||||
let addNote requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let db = db ctx
|
||||
let usrId = userId ctx
|
||||
let reqId = RequestId.ofString requestId
|
||||
match! Data.tryRequestById reqId usrId db with
|
||||
| Some _ ->
|
||||
let! notes = ctx.BindJsonAsync<Models.NoteEntry> ()
|
||||
let! notes = ctx.BindFormAsync<Models.NoteEntry> ()
|
||||
do! Data.addNote reqId usrId { asOf = jsNow (); notes = notes.notes } db
|
||||
do! db.saveChanges ()
|
||||
return! created next ctx
|
||||
return! (withSuccessMessage "Added Notes" >=> hideModal "notes" >=> created) next ctx
|
||||
| None -> return! Error.notFound next ctx
|
||||
}
|
||||
|
||||
// GET /requests/active
|
||||
let active : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let! reqs = Data.journalByUserId (userId ctx) (db ctx)
|
||||
return! partial "Active Requests" (Views.Request.active reqs) next ctx
|
||||
}
|
||||
@ -350,7 +354,7 @@ module Request =
|
||||
// GET /requests/snoozed
|
||||
let snoozed : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let! reqs = Data.journalByUserId (userId ctx) (db ctx)
|
||||
let now = (jsNow >> Ticks.toLong) ()
|
||||
let snoozed = reqs |> List.filter (fun r -> Ticks.toLong r.snoozedUntil > now)
|
||||
@ -360,7 +364,7 @@ module Request =
|
||||
// GET /requests/answered
|
||||
let answered : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let! reqs = Data.answeredRequests (userId ctx) (db ctx)
|
||||
return! partial "Answered Requests" (Views.Request.answered reqs) next ctx
|
||||
}
|
||||
@ -368,7 +372,7 @@ module Request =
|
||||
/// GET /api/request/[req-id]
|
||||
let get requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
match! Data.tryJournalById (RequestId.ofString requestId) (userId ctx) (db ctx) with
|
||||
| Some req -> return! json req next ctx
|
||||
| None -> return! Error.notFound next ctx
|
||||
@ -377,7 +381,7 @@ module Request =
|
||||
// GET /request/[req-id]/full
|
||||
let getFull requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
match! Data.tryFullRequestById (RequestId.ofString requestId) (userId ctx) (db ctx) with
|
||||
| Some req -> return! partial "Prayer Request" (Views.Request.full req) next ctx
|
||||
| None -> return! Error.notFound next ctx
|
||||
@ -386,7 +390,7 @@ module Request =
|
||||
// PATCH /request/[req-id]/show
|
||||
let show requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let db = db ctx
|
||||
let usrId = userId ctx
|
||||
let reqId = RequestId.ofString requestId
|
||||
@ -401,7 +405,7 @@ module Request =
|
||||
/// PATCH /api/request/[req-id]/snooze
|
||||
let snooze requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let db = db ctx
|
||||
let usrId = userId ctx
|
||||
let reqId = RequestId.ofString requestId
|
||||
@ -417,7 +421,7 @@ module Request =
|
||||
// PATCH /request/[req-id]/cancel-snooze
|
||||
let cancelSnooze requestId : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let db = db ctx
|
||||
let usrId = userId ctx
|
||||
let reqId = RequestId.ofString requestId
|
||||
@ -437,7 +441,7 @@ module Request =
|
||||
// POST /request
|
||||
let add : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let! form = ctx.BindModelAsync<Models.Request> ()
|
||||
let db = db ctx
|
||||
let usrId = userId ctx
|
||||
@ -466,7 +470,7 @@ module Request =
|
||||
// PATCH /request
|
||||
let update : HttpHandler =
|
||||
requiresAuthentication Error.notAuthorized
|
||||
>=> fun next ctx -> task {
|
||||
>=> fun next ctx -> backgroundTask {
|
||||
let! form = ctx.BindModelAsync<Models.Request> ()
|
||||
let db = db ctx
|
||||
let usrId = userId ctx
|
||||
@ -552,7 +556,8 @@ let routes =
|
||||
routef "/%s/show" Request.show
|
||||
]
|
||||
POST [
|
||||
route "" Request.add
|
||||
route "" Request.add
|
||||
routef "/%s/note" Request.addNote
|
||||
]
|
||||
]
|
||||
subRoute "/user/" [
|
||||
@ -572,10 +577,5 @@ let routes =
|
||||
routef "/%s/snooze" Request.snooze
|
||||
]
|
||||
]
|
||||
POST [
|
||||
subRoute "request" [
|
||||
routef "/%s/note" Request.addNote
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
@ -75,7 +75,9 @@ let journal user = article [ _class "container-fluid mt-3" ] [
|
||||
]
|
||||
div [ _class "modal-body"; _id "notesBody" ] [ ]
|
||||
div [ _class "modal-footer" ] [
|
||||
button [ _type "button"; _class "btn btn-secondary"; _data "bs-dismiss" "modal" ] [ str "Close" ]
|
||||
button [ _type "button"; _id "notesDismiss"; _class "btn btn-secondary"; _data "bs-dismiss" "modal" ] [
|
||||
str "Close"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
@ -102,12 +104,23 @@ let journalItems items =
|
||||
/// The notes edit modal body
|
||||
let notesEdit requestId =
|
||||
let reqId = RequestId.toString requestId
|
||||
[ form [ _hxPost $"/request/{reqId}/note"; _hxTarget "#top" ] [
|
||||
str "TODO"
|
||||
button [ _type "submit"; _class "btn btn-primary" ] [ str "Add Notes" ]
|
||||
[ form [ _hxPost $"/request/{reqId}/note" ] [
|
||||
div [ _class "form-floating pb-3" ] [
|
||||
textarea [
|
||||
_id "notes"
|
||||
_name "notes"
|
||||
_class "form-control"
|
||||
_style "min-height: 8rem;"
|
||||
_placeholder "Notes"
|
||||
_autofocus; _required
|
||||
] [ ]
|
||||
label [ _for "notes" ] [ str "Notes" ]
|
||||
]
|
||||
p [ _class "text-end" ] [ button [ _type "submit"; _class "btn btn-primary" ] [ str "Add Notes" ] ]
|
||||
]
|
||||
hr [ _style "margin: .5rem -1rem" ]
|
||||
div [ _id "priorNotes" ] [
|
||||
p [ _class "text-center pt-5" ] [
|
||||
p [ _class "text-center pt-3" ] [
|
||||
button [
|
||||
_type "button"
|
||||
_class "btn btn-secondary"
|
||||
|
@ -263,8 +263,7 @@ let edit (req : JournalRequest) returnTo isNew =
|
||||
/// Display a list of notes for a request
|
||||
let notes notes =
|
||||
let toItem (note : Note) = p [] [ small [ _class "text-muted" ] [ relativeDate note.asOf ]; br []; str note.notes ]
|
||||
[ hr [ _style "margin: .5rem -1rem" ]
|
||||
p [ _class "text-center" ] [ strong [] [ str "Prior Notes for This Request" ] ]
|
||||
[ p [ _class "text-center" ] [ strong [] [ str "Prior Notes for This Request" ] ]
|
||||
match notes with
|
||||
| [] -> p [ _class "text-center text-muted" ] [ str "There are no prior notes for this request" ]
|
||||
| _ -> yield! notes |> List.map toItem
|
||||
|
@ -66,19 +66,6 @@ const mpj = {
|
||||
const isDisabled = target.value === "Immediate"
|
||||
;["recurCount", "recurInterval"].forEach(it => document.getElementById(it).disabled = isDisabled)
|
||||
}
|
||||
},
|
||||
/** Script for the journal page */
|
||||
journal: {
|
||||
/**
|
||||
* Set up the journal page modals
|
||||
*/
|
||||
setUp () {
|
||||
document.getElementById("notesModal").addEventListener("show.bs.modal", function (event) {
|
||||
const reqId = event.relatedTarget.getAttribute("data-request-id")
|
||||
document.getElementById("notesForm").setAttribute("action", `/request/${reqId}/note`)
|
||||
document.getElementById("notesLoad").setAttribute("hx-get", `/components/request/${reqId}/notes`)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,5 +75,8 @@ htmx.on("htmx:afterOnLoad", function (evt) {
|
||||
if (hdrs.indexOf("x-toast") >= 0) {
|
||||
mpj.showToast(evt.detail.xhr.getResponseHeader("x-toast"))
|
||||
}
|
||||
// Hide a modal window if requested
|
||||
if (hdrs.indexOf("x-hide-modal") >= 0) {
|
||||
document.getElementById(evt.detail.xhr.getResponseHeader("x-hide-modal") + "Dismiss").click()
|
||||
}
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user