diff --git a/src/JobsJobsJobs/Citizens/Data.fs b/src/JobsJobsJobs/Citizens/Data.fs index ddee086..6f06e77 100644 --- a/src/JobsJobsJobs/Citizens/Data.fs +++ b/src/JobsJobsJobs/Citizens/Data.fs @@ -197,3 +197,13 @@ let trySecurityByToken token = backgroundTask { |> Sql.executeAsync toDocument return List.tryHead results } + +// ~~~ LEGACY MIGRATION ~~~ // + +/// Get all legacy citizens +let legacy () = backgroundTask { + return! + dataSource () + |> Sql.query $"SELECT * FROM {Table.Citizen} WHERE c.data ->> 'isLegacy' = 'true'" + |> Sql.executeAsync toDocument +} diff --git a/src/JobsJobsJobs/Citizens/Handlers.fs b/src/JobsJobsJobs/Citizens/Handlers.fs index 75e367e..47d5aab 100644 --- a/src/JobsJobsJobs/Citizens/Handlers.fs +++ b/src/JobsJobsJobs/Citizens/Handlers.fs @@ -59,6 +59,15 @@ module private Auth = | PasswordVerificationResult.SuccessRehashNeeded -> Some true | _ -> None + /// Require an administrative user (used for legacy migration endpoints) + let requireAdmin : HttpHandler = requireUser >=> fun next ctx -> task { + // let adminUser = (config ctx)["AdminUser"] + // if adminUser = defaultArg (tryUser ctx) "" then return! next ctx + // else return! Error.notAuthorized next ctx + // TODO: uncomment the above, remove the line below + return! next ctx + } + // GET: /citizen/account let account : HttpHandler = fun next ctx -> task { @@ -324,6 +333,13 @@ let saveAccount : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> let soLong : HttpHandler = requireUser >=> fun next ctx -> Views.deletionOptions (csrf ctx) |> render "Account Deletion Options" next ctx +// ~~~ LEGACY MIGRATION ~~~ // + +// GET: /citizen/legacy/list +let listLegacy : HttpHandler = Auth.requireAdmin >=> fun next ctx -> task { + let! users = Data.legacy () + return! Views.listLegacy users |> render "Migrate Legacy Account" next ctx +} open Giraffe.EndpointRouting @@ -342,6 +358,7 @@ let endpoints = route "/register" register routef "/reset-password/%s" resetPassword route "/so-long" soLong + route "/legacy/list" listLegacy ] POST [ route "/delete" delete diff --git a/src/JobsJobsJobs/Citizens/Views.fs b/src/JobsJobsJobs/Citizens/Views.fs index dffb5b2..78633de 100644 --- a/src/JobsJobsJobs/Citizens/Views.fs +++ b/src/JobsJobsJobs/Citizens/Views.fs @@ -393,3 +393,23 @@ let resetPassword (m : ResetPasswordForm) isHtmx csrf = jsOnLoad $"jjj.citizen.validatePasswords('{nameof m.Password}', 'ConfirmPassword', true)" isHtmx ] ] + +// ~~~ LEGACY MIGRATION ~~~ // + +let listLegacy (m : Citizen list) = + [ table [ _class "table table-sm table-hover" ] [ + thead [] [ + tr [] [ + th [ _scope "col" ] [ txt "Action" ] + th [ _scope "col" ] [ txt "NAS Profile" ] + ] + ] + m |> List.map (fun it -> + tr [] [ + td [] [ a [ _href $"/citizen/legacy/{CitizenId.toString it.Id}/associate" ] [ txt "Migrate" ] ] + td [] [ str it.Email ] + ]) + |> tbody [] + ] + ] + |> pageWithTitle "Migrate Legacy Account" diff --git a/src/JobsJobsJobs/JobsJobsJobs.V3Migration/Program.fs b/src/JobsJobsJobs/JobsJobsJobs.V3Migration/Program.fs index 5ee97c7..e051979 100644 --- a/src/JobsJobsJobs/JobsJobsJobs.V3Migration/Program.fs +++ b/src/JobsJobsJobs/JobsJobsJobs.V3Migration/Program.fs @@ -93,7 +93,7 @@ task { let! _ = pgConn |> Sql.executeTransactionAsync [ - $"INSERT INTO jjj.{Table.SecurityInfo} VALUES (@id, @data)", + $"INSERT INTO {Table.SecurityInfo} VALUES (@id, @data)", newCitizens |> List.map (fun c -> let info = { SecurityInfo.empty with Id = c.Id; AccountLocked = true } [ "@id", Sql.string (CitizenId.toString c.Id) @@ -114,7 +114,7 @@ task { let! _ = pgConn |> Sql.executeTransactionAsync [ - "INSERT INTO jjj.continent VALUES (@id, @data)", + $"INSERT INTO {Table.Continent} VALUES (@id, @data)", newContinents |> List.map (fun c -> [ "@id", Sql.string (ContinentId.toString c.Id) "@data", Sql.jsonb (JsonSerializer.Serialize (c, Json.options)) @@ -204,10 +204,10 @@ task { let! deleted = pgConn |> Sql.query $" - DELETE FROM jjj.{Table.Citizen} - WHERE id NOT IN (SELECT id FROM jjj.{Table.Profile}) - AND id NOT IN (SELECT DISTINCT data->>'citizenId' FROM jjj.{Table.Listing}) - AND id NOT IN (SELECT DISTINCT data->>'citizenId' FROM jjj.{Table.Success})" + DELETE FROM {Table.Citizen} + WHERE id NOT IN (SELECT id FROM {Table.Profile}) + AND id NOT IN (SELECT DISTINCT data ->> 'citizenId' FROM {Table.Listing}) + AND id NOT IN (SELECT DISTINCT data ->> 'citizenId' FROM {Table.Success})" |> Sql.executeNonQueryAsync printfn $"** Deleted {deleted} citizens who had no profile, listings, or success stories"