3ad6b5a521
- Add auto-OpenGraph field to web log - Only generate properties for posts/pages without them if this flag is set - Set flag to yes on v3 database migration - Add JSON converter for OpenGraph type - Add tests for models
112 lines
5.6 KiB
FSharp
112 lines
5.6 KiB
FSharp
/// <summary>Utility functions for manipulating data</summary>
|
|
[<RequireQualifiedAccess>]
|
|
module internal MyWebLog.Data.Utils
|
|
|
|
open MyWebLog
|
|
open MyWebLog.ViewModels
|
|
|
|
/// <summary>Create a category hierarchy from the given list of categories</summary>
|
|
/// <param name="cats">The categories from which the list should be generated</param>
|
|
/// <param name="parentId">The ID of the parent category for this list</param>
|
|
/// <param name="slugBase">The base URL to use in slugs for categories at this level</param>
|
|
/// <param name="parentNames">The names of parent categories for this level</param>
|
|
/// <returns>An array of <c>DisplayCategory</c> instances sorted alphabetically by parent category</returns>
|
|
let rec orderByHierarchy (cats: Category list) parentId slugBase parentNames = seq {
|
|
for cat in cats |> List.filter (fun c -> c.ParentId = parentId) do
|
|
let fullSlug = (match slugBase with Some it -> $"{it}/" | None -> "") + cat.Slug
|
|
{ Id = string cat.Id
|
|
Slug = fullSlug
|
|
Name = cat.Name
|
|
Description = cat.Description
|
|
ParentNames = Array.ofList parentNames
|
|
// Post counts are filled on a second pass
|
|
PostCount = 0 }
|
|
yield! orderByHierarchy cats (Some cat.Id) (Some fullSlug) ([ cat.Name ] |> List.append parentNames)
|
|
}
|
|
|
|
/// <summary>Get lists of items removed from and added to the given lists</summary>
|
|
/// <typeparam name="T">The type of items in the list</typeparam>
|
|
/// <typeparam name="U">The return type of the comparision function</typeparam>
|
|
/// <param name="oldItems">The prior list</param>
|
|
/// <param name="newItems">The current list</param>
|
|
/// <param name="f">The function to use when comparing items in the list</param>
|
|
/// <returns>A tuple with <c>fst</c> being added items and <c>snd</c> being removed items</returns>
|
|
let diffLists<'T, 'U when 'U: equality> oldItems newItems (f: 'T -> 'U) =
|
|
let diff compList = fun item -> not (compList |> List.exists (fun other -> f item = f other))
|
|
List.filter (diff newItems) oldItems, List.filter (diff oldItems) newItems
|
|
|
|
/// <summary>Find the revisions added and removed</summary>
|
|
/// <param name="oldRevs">The previous revisions</param>
|
|
/// <param name="newRevs">The current revisions</param>
|
|
/// <returns>A tuple with <c>fst</c> being added revisions and <c>snd</c> being removed revisions</returns>
|
|
let diffRevisions (oldRevs: Revision list) newRevs =
|
|
diffLists oldRevs newRevs (fun rev -> $"{rev.AsOf.ToUnixTimeTicks()}|{rev.Text}")
|
|
|
|
open MyWebLog.Converters
|
|
open Newtonsoft.Json
|
|
|
|
/// <summary>Serialize an object to JSON</summary>
|
|
/// <typeparam name="T">The type of the item being serialized</typeparam>
|
|
/// <param name="ser">The JSON serializer whose settings should be used</param>
|
|
/// <param name="item">The item to be serialized</param>
|
|
/// <returns>A string with the given object serialized to JSON</returns>
|
|
let serialize<'T> ser (item: 'T) =
|
|
JsonConvert.SerializeObject(item, Json.settings ser)
|
|
|
|
/// <summary>Deserialize a JSON string</summary>
|
|
/// <typeparam name="T">The type of the item being deserialized</typeparam>
|
|
/// <param name="ser">The JSON serializer whose settings should be used</param>
|
|
/// <param name="value">The string with the JSON representation of the item</param>
|
|
/// <returns>The item deserialized from JSON</returns>
|
|
let deserialize<'T> (ser: JsonSerializer) value =
|
|
JsonConvert.DeserializeObject<'T>(value, Json.settings ser)
|
|
|
|
|
|
open BitBadger.Documents
|
|
|
|
/// <summary>Create a document serializer using the given JsonSerializer</summary>
|
|
/// <param name="ser">The JSON.NET serializer on which the document serializer should be based</param>
|
|
/// <returns>A document serializer instance</returns>
|
|
let createDocumentSerializer ser =
|
|
{ new IDocumentSerializer with
|
|
member _.Serialize<'T>(it: 'T) : string = serialize ser it
|
|
member _.Deserialize<'T>(it: string) : 'T = deserialize ser it
|
|
}
|
|
|
|
/// <summary>Data migration utilities</summary>
|
|
module Migration =
|
|
|
|
open Microsoft.Extensions.Logging
|
|
|
|
/// <summary>The current database version</summary>
|
|
let currentDbVersion = "v3"
|
|
|
|
/// <summary>Log a migration step</summary>
|
|
/// <param name="log">The logger to which the message should be logged</param>
|
|
/// <param name="migration">The migration being run</param>
|
|
/// <param name="message">The log message</param>
|
|
let logStep<'T> (log: ILogger<'T>) migration message =
|
|
log.LogInformation $"Migrating %s{migration}: %s{message}"
|
|
|
|
/// <summary>Notify the user that a backup/restore is required to migrate</summary>
|
|
/// <param name="log">The logger to which the message should be logged</param>
|
|
/// <param name="oldVersion">The old (current) version of the database</param>
|
|
/// <param name="newVersion">The new (application) version required</param>
|
|
/// <param name="webLogs">All web logs contained in the database</param>
|
|
let backupAndRestoreRequired log oldVersion newVersion webLogs =
|
|
logStep log $"%s{oldVersion} to %s{newVersion}" "Requires Using Action"
|
|
|
|
[ "** MANUAL DATABASE UPGRADE REQUIRED **"; ""
|
|
$"The data structure changed between {oldVersion} and {newVersion}."
|
|
"To migrate your data:"
|
|
$" - Use a {oldVersion} executable to back up each web log"
|
|
" - Drop all tables from the database"
|
|
" - Use this executable to restore each backup"; ""
|
|
"Commands to back up all web logs:"
|
|
yield! webLogs |> List.map (fun (url, slug) -> $"./myWebLog backup %s{url} {oldVersion}.%s{slug}.json") ]
|
|
|> String.concat "\n"
|
|
|> log.LogWarning
|
|
|
|
log.LogCritical "myWebLog will now exit"
|
|
exit 1 |> ignore
|