From 8c2ff4c84d97c25ee335a845cc3a4038693fc549 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 28 Aug 2021 22:21:39 -0400 Subject: [PATCH] WIP on listing expire/success story (#18) --- src/JobsJobsJobs/Api/Data.fs | 13 +++ src/JobsJobsJobs/Api/Handlers.fs | 30 +++++ src/JobsJobsJobs/App/src/api/index.ts | 12 ++ src/JobsJobsJobs/App/src/api/types.ts | 8 ++ src/JobsJobsJobs/App/src/router/index.ts | 5 + .../App/src/views/listing/ListingExpire.vue | 110 ++++++++++++++++++ .../App/src/views/listing/index.ts | 2 +- src/JobsJobsJobs/Domain/SharedTypes.fs | 9 ++ 8 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 src/JobsJobsJobs/App/src/views/listing/ListingExpire.vue diff --git a/src/JobsJobsJobs/Api/Data.fs b/src/JobsJobsJobs/Api/Data.fs index a7b955e..30a499c 100644 --- a/src/JobsJobsJobs/Api/Data.fs +++ b/src/JobsJobsJobs/Api/Data.fs @@ -429,6 +429,8 @@ module Continent = [] module Listing = + open NodaTime + /// Find all job listings posted by the given citizen let findByCitizen (citizenId : CitizenId) conn = withReconn(conn).ExecuteAsync(fun () -> @@ -481,6 +483,17 @@ module Listing = () }) + /// Expire a listing + let expire (listingId : ListingId) (fromHere : bool) (now : Instant) conn = + withReconn(conn).ExecuteAsync(fun () -> task { + let! _ = + r.Table(Table.Listing) + .Get(listingId) + .Update(r.HashMap("isExpired", true).With("fromHere", fromHere).With("updatedOn", now)) + .RunWriteAsync conn + () + }) + /// Search job listings let search (srch : ListingSearch) conn = withReconn(conn).ExecuteAsync(fun () -> diff --git a/src/JobsJobsJobs/Api/Handlers.fs b/src/JobsJobsJobs/Api/Handlers.fs index 555ef1a..bc24dd4 100644 --- a/src/JobsJobsJobs/Api/Handlers.fs +++ b/src/JobsJobsJobs/Api/Handlers.fs @@ -257,6 +257,33 @@ module Listing = return! ok next ctx | None -> return! Error.notFound next ctx } + + // PATCH: /api/listing/[id] + let expire listingId : HttpHandler = + authorize + >=> fun next ctx -> task { + let dbConn = conn ctx + let now = clock(ctx).GetCurrentInstant () + match! Data.Listing.findById (ListingId listingId) dbConn with + | Some listing when listing.citizenId <> (currentCitizenId ctx) -> return! Error.notAuthorized next ctx + | Some listing -> + let! form = ctx.BindJsonAsync () + do! Data.Listing.expire listing.id form.fromHere now dbConn + match form.successStory with + | Some storyText -> + do! Data.Success.save + { id = SuccessId.create() + citizenId = currentCitizenId ctx + recordedOn = now + fromHere = form.fromHere + source = "listing" + story = (Text >> Some) storyText + } dbConn + | None -> () + return! ok next ctx + | None -> return! Error.notFound next ctx + + } // GET: /api/listing/search let search : HttpHandler = @@ -475,6 +502,9 @@ let allEndpoints = [ routef "/%O/view" Listing.view route "s/mine" Listing.mine ] + PATCH [ + routef "/%O" Listing.expire + ] POST [ route "s" Listing.add ] diff --git a/src/JobsJobsJobs/App/src/api/index.ts b/src/JobsJobsJobs/App/src/api/index.ts index 3ee02a1..5b2c297 100644 --- a/src/JobsJobsJobs/App/src/api/index.ts +++ b/src/JobsJobsJobs/App/src/api/index.ts @@ -3,6 +3,7 @@ import { Continent, Count, Listing, + ListingExpireForm, ListingForm, ListingForView, ListingSearch, @@ -153,6 +154,17 @@ export default { add: async (listing : ListingForm, user : LogOnSuccess) : Promise => apiSend(await fetch(apiUrl("listings"), reqInit("POST", user, listing)), "adding job listing"), + /** + * Expire a job listing + * + * @param id The ID of the job listing to be expired + * @param form The information needed to expire the form + * @param user The currently logged-on user + * @returns True if the action was successful, an error string if not + */ + expire: async (id : string, listing : ListingExpireForm, user : LogOnSuccess) : Promise => + apiSend(await fetch(apiUrl(`listing/${id}`), reqInit("PATCH", user, listing)), "expiring job listing"), + /** * Retrieve the job listings posted by the current citizen * diff --git a/src/JobsJobsJobs/App/src/api/types.ts b/src/JobsJobsJobs/App/src/api/types.ts index 0f465aa..b9cbd87 100644 --- a/src/JobsJobsJobs/App/src/api/types.ts +++ b/src/JobsJobsJobs/App/src/api/types.ts @@ -77,6 +77,14 @@ export class ListingForm { neededBy : string | undefined } +/** The form submitted to expire a listing */ +export class ListingExpireForm { + /** Whether the job was filled from here */ + fromHere = false + /** The success story written by the user */ + successStory : string | undefined +} + /** The data required to view a listing */ export interface ListingForView { /** The listing itself */ diff --git a/src/JobsJobsJobs/App/src/router/index.ts b/src/JobsJobsJobs/App/src/router/index.ts index 560c6c4..77f39c2 100644 --- a/src/JobsJobsJobs/App/src/router/index.ts +++ b/src/JobsJobsJobs/App/src/router/index.ts @@ -83,6 +83,11 @@ const routes: Array = [ name: "EditListing", component: () => import(/* webpackChunkName: "jobedit" */ "../views/listing/ListingEdit.vue") }, + { + path: "/listing/:id/expire", + name: "ExpireListing", + component: () => import(/* webpackChunkName: "jobedit" */ "../views/listing/ListingExpire.vue") + }, { path: "/listing/:id/view", name: "ViewListing", diff --git a/src/JobsJobsJobs/App/src/views/listing/ListingExpire.vue b/src/JobsJobsJobs/App/src/views/listing/ListingExpire.vue new file mode 100644 index 0000000..03bf76b --- /dev/null +++ b/src/JobsJobsJobs/App/src/views/listing/ListingExpire.vue @@ -0,0 +1,110 @@ + + + diff --git a/src/JobsJobsJobs/App/src/views/listing/index.ts b/src/JobsJobsJobs/App/src/views/listing/index.ts index 3d13176..5d35d8c 100644 --- a/src/JobsJobsJobs/App/src/views/listing/index.ts +++ b/src/JobsJobsJobs/App/src/views/listing/index.ts @@ -7,5 +7,5 @@ import { format } from "date-fns" * @returns The date to display */ export function formatNeededBy (neededBy : string) : string { - return format(Date.parse(neededBy), "PPP") + return format(Date.parse(`${neededBy}T00:00:00`), "PPP") } diff --git a/src/JobsJobsJobs/Domain/SharedTypes.fs b/src/JobsJobsJobs/Domain/SharedTypes.fs index e609d77..30acf2a 100644 --- a/src/JobsJobsJobs/Domain/SharedTypes.fs +++ b/src/JobsJobsJobs/Domain/SharedTypes.fs @@ -34,6 +34,15 @@ type ListingForView = { } +/// The form submitted to expire a listing +type ListingExpireForm = { + /// Whether the job was filled from here + fromHere : bool + /// The success story written by the user + successStory : string option +} + + /// The various ways job listings can be searched [] type ListingSearch = {