Release v2.2 #51
							
								
								
									
										2
									
								
								build.fs
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								build.fs
									
									
									
									
									
								
							@ -33,7 +33,7 @@ let zipTheme (name : string) (_ : TargetParameter) =
 | 
				
			|||||||
    |> Zip.zipSpec $"{releasePath}/{name}-theme.zip"
 | 
					    |> Zip.zipSpec $"{releasePath}/{name}-theme.zip"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Frameworks supported by this build
 | 
					/// Frameworks supported by this build
 | 
				
			||||||
let frameworks = [ "net6.0"; "net7.0"; "net8.0" ]
 | 
					let frameworks = [ "net6.0"; "net8.0" ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Publish the project for the given runtime ID    
 | 
					/// Publish the project for the given runtime ID    
 | 
				
			||||||
let publishFor rid (_ : TargetParameter) =
 | 
					let publishFor rid (_ : TargetParameter) =
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
<Project>
 | 
					<Project>
 | 
				
			||||||
  <PropertyGroup>
 | 
					  <PropertyGroup>
 | 
				
			||||||
    <TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
 | 
					    <TargetFrameworks>net6.0;net8.0</TargetFrameworks>
 | 
				
			||||||
    <DebugType>embedded</DebugType>
 | 
					    <DebugType>embedded</DebugType>
 | 
				
			||||||
    <AssemblyVersion>2.1.0.0</AssemblyVersion>
 | 
					    <AssemblyVersion>2.2.0.0</AssemblyVersion>
 | 
				
			||||||
    <FileVersion>2.1.0.0</FileVersion>
 | 
					    <FileVersion>2.2.0.0</FileVersion>
 | 
				
			||||||
    <Version>2.1.0</Version>
 | 
					    <Version>2.2.0</Version>
 | 
				
			||||||
  </PropertyGroup>
 | 
					  </PropertyGroup>
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
 | 
				
			|||||||
@ -5,17 +5,17 @@
 | 
				
			|||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
		<PackageReference Include="BitBadger.Documents.Postgres" Version="3.0.0-rc-2" />
 | 
							<PackageReference Include="BitBadger.Documents.Postgres" Version="3.1.0" />
 | 
				
			||||||
		<PackageReference Include="BitBadger.Documents.Sqlite" Version="3.0.0-rc-2" />
 | 
							<PackageReference Include="BitBadger.Documents.Sqlite" Version="3.1.0" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.3" />
 | 
							<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.6" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
 | 
							<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
 | 
							<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
 | 
							<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
 | 
				
			||||||
		<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.1.0" />
 | 
							<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.1.0" />
 | 
				
			||||||
		<PackageReference Include="Npgsql.NodaTime" Version="8.0.2" />
 | 
							<PackageReference Include="Npgsql.NodaTime" Version="8.0.3" />
 | 
				
			||||||
		<PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
 | 
							<PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
 | 
				
			||||||
		<PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-07" />
 | 
							<PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-07" />
 | 
				
			||||||
		<PackageReference Update="FSharp.Core" Version="8.0.200" />
 | 
							<PackageReference Update="FSharp.Core" Version="8.0.300" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
 | 
				
			|||||||
@ -211,6 +211,16 @@ type PostgresData(log: ILogger<PostgresData>, ser: JsonSerializer) =
 | 
				
			|||||||
        return! setDbVersion "v2.1.1"
 | 
					        return! setDbVersion "v2.1.1"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Migrate from v2.1.1 to v2.2
 | 
				
			||||||
 | 
					    let migrateV2point1point1ToV2point2 () = backgroundTask {
 | 
				
			||||||
 | 
					        Utils.Migration.logStep log "v2.1.1 to v2.2" "Setting e-mail to lowercase"
 | 
				
			||||||
 | 
					        do! Custom.nonQuery
 | 
				
			||||||
 | 
					                $"""UPDATE {Table.WebLogUser} SET data = data || ('{{"Email":"' || lower(data->>'Email') || '"}}')::jsonb"""
 | 
				
			||||||
 | 
					                []
 | 
				
			||||||
 | 
					        Utils.Migration.logStep log "v2.1.1 to v2.2" "Setting database version to v2.2"
 | 
				
			||||||
 | 
					        return! setDbVersion "v2.2"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Do required data migration between versions
 | 
					    /// Do required data migration between versions
 | 
				
			||||||
    let migrate version = backgroundTask {
 | 
					    let migrate version = backgroundTask {
 | 
				
			||||||
        let mutable v = defaultArg version ""
 | 
					        let mutable v = defaultArg version ""
 | 
				
			||||||
@ -226,6 +236,10 @@ type PostgresData(log: ILogger<PostgresData>, ser: JsonSerializer) =
 | 
				
			|||||||
            let! ver = migrateV2ToV2point1point1 ()
 | 
					            let! ver = migrateV2ToV2point1point1 ()
 | 
				
			||||||
            v <- ver
 | 
					            v <- ver
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        if v = "v2.1.1" then
 | 
				
			||||||
 | 
					            let! ver = migrateV2point1point1ToV2point2 ()
 | 
				
			||||||
 | 
					            v <- ver
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        if v <> Utils.Migration.currentDbVersion then
 | 
					        if v <> Utils.Migration.currentDbVersion then
 | 
				
			||||||
            log.LogWarning $"Unknown database version; assuming {Utils.Migration.currentDbVersion}"
 | 
					            log.LogWarning $"Unknown database version; assuming {Utils.Migration.currentDbVersion}"
 | 
				
			||||||
            let! _ = setDbVersion Utils.Migration.currentDbVersion
 | 
					            let! _ = setDbVersion Utils.Migration.currentDbVersion
 | 
				
			||||||
 | 
				
			|||||||
@ -239,11 +239,23 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Migrate from v2.1 to v2.1.1
 | 
					    /// Migrate from v2.1 to v2.1.1
 | 
				
			||||||
    let migrateV2ToV2point1point1 () = backgroundTask {
 | 
					    let migrateV2point1ToV2point1point1 () = backgroundTask {
 | 
				
			||||||
        Utils.Migration.logStep log "v2.1 to v2.1.1" "Setting database version; no migration required"
 | 
					        Utils.Migration.logStep log "v2.1 to v2.1.1" "Setting database version; no migration required"
 | 
				
			||||||
        do! setDbVersion "v2.1.1"
 | 
					        do! setDbVersion "v2.1.1"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Migrate from v2.1.1 to v2.2
 | 
				
			||||||
 | 
					    let migrateV2point1point1ToV2point2 () = backgroundTask {
 | 
				
			||||||
 | 
					        Utils.Migration.logStep log "v2.1.1 to v2.2" "Setting e-mail to lowercase"
 | 
				
			||||||
 | 
					        do! rethink {
 | 
				
			||||||
 | 
					            withTable Table.WebLogUser
 | 
				
			||||||
 | 
					            update (fun row -> {| Email = row[nameof WebLogUser.Empty.Email].Downcase() |})
 | 
				
			||||||
 | 
					            write; withRetryOnce; ignoreResult conn
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Utils.Migration.logStep log "v2.1.1 to v2.2" "Setting database version to v2.2"
 | 
				
			||||||
 | 
					        do! setDbVersion "v2.2"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Migrate data between versions
 | 
					    /// Migrate data between versions
 | 
				
			||||||
    let migrate version = backgroundTask {
 | 
					    let migrate version = backgroundTask {
 | 
				
			||||||
        let mutable v = defaultArg version ""
 | 
					        let mutable v = defaultArg version ""
 | 
				
			||||||
@ -261,9 +273,13 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
 | 
				
			|||||||
            v <- "v2.1"
 | 
					            v <- "v2.1"
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if v = "v2.1" then
 | 
					        if v = "v2.1" then
 | 
				
			||||||
            do! migrateV2ToV2point1point1 ()
 | 
					            do! migrateV2point1ToV2point1point1 ()
 | 
				
			||||||
            v <- "v2.1.1"
 | 
					            v <- "v2.1.1"
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        if v = "v2.1.1" then
 | 
				
			||||||
 | 
					            do! migrateV2point1point1ToV2point2 ()
 | 
				
			||||||
 | 
					            v <- "v2.2"
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        if v <> Utils.Migration.currentDbVersion then
 | 
					        if v <> Utils.Migration.currentDbVersion then
 | 
				
			||||||
            log.LogWarning $"Unknown database version; assuming {Utils.Migration.currentDbVersion}"
 | 
					            log.LogWarning $"Unknown database version; assuming {Utils.Migration.currentDbVersion}"
 | 
				
			||||||
            do! setDbVersion Utils.Migration.currentDbVersion
 | 
					            do! setDbVersion Utils.Migration.currentDbVersion
 | 
				
			||||||
 | 
				
			|||||||
@ -431,11 +431,19 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Migrate from v2.1 to v2.1.1
 | 
					    /// Migrate from v2.1 to v2.1.1
 | 
				
			||||||
    let migrateV2ToV2point1point1 () = backgroundTask {
 | 
					    let migrateV2point1ToV2point1point1 () = backgroundTask {
 | 
				
			||||||
        Utils.Migration.logStep log "v2.1 to v2.1.1" "Setting database version; no migration required"
 | 
					        Utils.Migration.logStep log "v2.1 to v2.1.1" "Setting database version; no migration required"
 | 
				
			||||||
        do! setDbVersion "v2.1.1"
 | 
					        do! setDbVersion "v2.1.1"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    /// Migrate from v2.1.1 to v2.2
 | 
				
			||||||
 | 
					    let migrateV2point1point1ToV2point2 () = backgroundTask {
 | 
				
			||||||
 | 
					        Utils.Migration.logStep log "v2.1.1 to v2.2" "Setting e-mail to lowercase"
 | 
				
			||||||
 | 
					        do! Custom.nonQuery $"UPDATE {Table.WebLogUser} SET data = json_set(data, '$.Email', lower(data->>'Email'))" []
 | 
				
			||||||
 | 
					        Utils.Migration.logStep log "v2.1.1 to v2.2" "Setting database version to v2.2"
 | 
				
			||||||
 | 
					        do! setDbVersion "v2.2"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Migrate data among versions (up only)
 | 
					    /// Migrate data among versions (up only)
 | 
				
			||||||
    let migrate version = backgroundTask {
 | 
					    let migrate version = backgroundTask {
 | 
				
			||||||
        let mutable v = defaultArg version ""
 | 
					        let mutable v = defaultArg version ""
 | 
				
			||||||
@ -453,9 +461,13 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
 | 
				
			|||||||
            v <- "v2.1"
 | 
					            v <- "v2.1"
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if v = "v2.1" then
 | 
					        if v = "v2.1" then
 | 
				
			||||||
            do! migrateV2ToV2point1point1 ()
 | 
					            do! migrateV2point1ToV2point1point1 ()
 | 
				
			||||||
            v <- "v2.1.1"
 | 
					            v <- "v2.1.1"
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        if v = "v2.1.1" then
 | 
				
			||||||
 | 
					            do! migrateV2point1point1ToV2point2 ()
 | 
				
			||||||
 | 
					            v <- "v2.2"
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        if v <> Utils.Migration.currentDbVersion then
 | 
					        if v <> Utils.Migration.currentDbVersion then
 | 
				
			||||||
            log.LogWarning $"Unknown database version; assuming {Utils.Migration.currentDbVersion}"
 | 
					            log.LogWarning $"Unknown database version; assuming {Utils.Migration.currentDbVersion}"
 | 
				
			||||||
            do! setDbVersion Utils.Migration.currentDbVersion
 | 
					            do! setDbVersion Utils.Migration.currentDbVersion
 | 
				
			||||||
 | 
				
			|||||||
@ -54,7 +54,7 @@ module Migration =
 | 
				
			|||||||
    open Microsoft.Extensions.Logging
 | 
					    open Microsoft.Extensions.Logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// The current database version
 | 
					    /// The current database version
 | 
				
			||||||
    let currentDbVersion = "v2.1.1"
 | 
					    let currentDbVersion = "v2.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Log a migration step
 | 
					    /// Log a migration step
 | 
				
			||||||
    let logStep<'T> (log: ILogger<'T>) migration message =
 | 
					    let logStep<'T> (log: ILogger<'T>) migration message =
 | 
				
			||||||
 | 
				
			|||||||
@ -7,11 +7,11 @@
 | 
				
			|||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <PackageReference Include="Markdig" Version="0.36.2" />
 | 
					    <PackageReference Include="Markdig" Version="0.37.0" />
 | 
				
			||||||
    <PackageReference Include="Markdown.ColorCode" Version="2.2.1" />
 | 
					    <PackageReference Include="Markdown.ColorCode" Version="2.2.2" />
 | 
				
			||||||
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
 | 
					    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
 | 
				
			||||||
    <PackageReference Include="NodaTime" Version="3.1.11" />
 | 
					    <PackageReference Include="NodaTime" Version="3.1.11" />
 | 
				
			||||||
    <PackageReference Update="FSharp.Core" Version="8.0.200" />
 | 
					    <PackageReference Update="FSharp.Core" Version="8.0.300" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
 | 
				
			|||||||
@ -154,7 +154,9 @@ type DisplayTheme = {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    /// Create a display theme from a theme
 | 
					    /// Create a display theme from a theme
 | 
				
			||||||
    static member FromTheme inUseFunc (theme: Theme) =
 | 
					    static member FromTheme inUseFunc (theme: Theme) =
 | 
				
			||||||
        let fileName = if string theme.Id = "default" then "default-theme.zip" else $"./themes/{theme.Id}-theme.zip"
 | 
					        let fileName =
 | 
				
			||||||
 | 
					            if string theme.Id = "default" then "default-theme.zip"
 | 
				
			||||||
 | 
					            else Path.Combine(".", "themes", $"{theme.Id}-theme.zip")
 | 
				
			||||||
        { Id            = string theme.Id
 | 
					        { Id            = string theme.Id
 | 
				
			||||||
          Name          = theme.Name
 | 
					          Name          = theme.Name
 | 
				
			||||||
          Version       = theme.Version
 | 
					          Version       = theme.Version
 | 
				
			||||||
@ -937,7 +939,7 @@ type EditUserModel = {
 | 
				
			|||||||
    member this.UpdateUser (user: WebLogUser) =
 | 
					    member this.UpdateUser (user: WebLogUser) =
 | 
				
			||||||
        { user with
 | 
					        { user with
 | 
				
			||||||
            AccessLevel   = AccessLevel.Parse this.AccessLevel
 | 
					            AccessLevel   = AccessLevel.Parse this.AccessLevel
 | 
				
			||||||
            Email         = this.Email
 | 
					            Email         = this.Email.ToLowerInvariant()
 | 
				
			||||||
            Url           = noneIfBlank this.Url
 | 
					            Url           = noneIfBlank this.Url
 | 
				
			||||||
            FirstName     = this.FirstName
 | 
					            FirstName     = this.FirstName
 | 
				
			||||||
            LastName      = this.LastName
 | 
					            LastName      = this.LastName
 | 
				
			||||||
 | 
				
			|||||||
@ -119,7 +119,9 @@ let displayThemeTests = testList "DisplayTheme.FromTheme" [
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    test "succeeds when a non-default theme is not in use and is on disk" {
 | 
					    test "succeeds when a non-default theme is not in use and is on disk" {
 | 
				
			||||||
        let dir = Directory.CreateDirectory "themes"
 | 
					        let dir = Directory.CreateDirectory "themes"
 | 
				
			||||||
        let file = File.Create "./themes/another-theme.zip"
 | 
					        try
 | 
				
			||||||
 | 
					            let testFile = Path.Combine(".", "themes", "another-theme.zip")
 | 
				
			||||||
 | 
					            let file     = File.Create testFile
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
                let model = DisplayTheme.FromTheme (fun _ -> false) { theme with Id = ThemeId "another" }
 | 
					                let model = DisplayTheme.FromTheme (fun _ -> false) { theme with Id = ThemeId "another" }
 | 
				
			||||||
                Expect.isFalse model.IsInUse "IsInUse should not have been set"
 | 
					                Expect.isFalse model.IsInUse "IsInUse should not have been set"
 | 
				
			||||||
@ -127,7 +129,8 @@ let displayThemeTests = testList "DisplayTheme.FromTheme" [
 | 
				
			|||||||
            finally
 | 
					            finally
 | 
				
			||||||
                file.Close()
 | 
					                file.Close()
 | 
				
			||||||
                file.Dispose()
 | 
					                file.Dispose()
 | 
				
			||||||
           File.Delete "./themes/another-theme.zip"
 | 
					                File.Delete testFile
 | 
				
			||||||
 | 
					        finally
 | 
				
			||||||
           dir.Delete()
 | 
					           dir.Delete()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    test "succeeds when the default theme is on disk" {
 | 
					    test "succeeds when the default theme is on disk" {
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,7 @@
 | 
				
			|||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <PackageReference Include="Expecto" Version="10.2.1" />
 | 
					    <PackageReference Include="Expecto" Version="10.2.1" />
 | 
				
			||||||
    <PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
 | 
					    <PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
 | 
				
			||||||
    <PackageReference Update="FSharp.Core" Version="8.0.200" />
 | 
					    <PackageReference Update="FSharp.Core" Version="8.0.300" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
				
			|||||||
@ -399,8 +399,11 @@ module Theme =
 | 
				
			|||||||
                    let! _ = loadFromZip themeId stream data
 | 
					                    let! _ = loadFromZip themeId stream data
 | 
				
			||||||
                    do! ThemeAssetCache.refreshTheme themeId data
 | 
					                    do! ThemeAssetCache.refreshTheme themeId data
 | 
				
			||||||
                    TemplateCache.invalidateTheme themeId
 | 
					                    TemplateCache.invalidateTheme themeId
 | 
				
			||||||
 | 
					                    // Ensure the themes directory exists
 | 
				
			||||||
 | 
					                    let themeDir = Path.Combine(".", "themes")
 | 
				
			||||||
 | 
					                    if not (Directory.Exists themeDir) then Directory.CreateDirectory themeDir |> ignore
 | 
				
			||||||
                    // Save the .zip file
 | 
					                    // Save the .zip file
 | 
				
			||||||
                    use file = new FileStream($"./themes/{themeId}-theme.zip", FileMode.Create)
 | 
					                    use file = new FileStream(Path.Combine(".", "themes", $"{themeId}-theme.zip"), FileMode.Create)
 | 
				
			||||||
                    do! themeFile.CopyToAsync file
 | 
					                    do! themeFile.CopyToAsync file
 | 
				
			||||||
                    do! addMessage ctx
 | 
					                    do! addMessage ctx
 | 
				
			||||||
                            { UserMessage.Success with
 | 
					                            { UserMessage.Success with
 | 
				
			||||||
@ -435,7 +438,7 @@ module Theme =
 | 
				
			|||||||
        | _ ->
 | 
					        | _ ->
 | 
				
			||||||
            match! data.Theme.Delete (ThemeId themeId) with
 | 
					            match! data.Theme.Delete (ThemeId themeId) with
 | 
				
			||||||
            | true ->
 | 
					            | true ->
 | 
				
			||||||
                let zippedTheme = $"./themes/{themeId}-theme.zip"
 | 
					                let zippedTheme = Path.Combine(".", "themes", $"{themeId}-theme.zip")
 | 
				
			||||||
                if File.Exists zippedTheme then File.Delete zippedTheme
 | 
					                if File.Exists zippedTheme then File.Delete zippedTheme
 | 
				
			||||||
                do! addMessage ctx { UserMessage.Success with Message = $"Theme ID {themeId} deleted successfully" }
 | 
					                do! addMessage ctx { UserMessage.Success with Message = $"Theme ID {themeId} deleted successfully" }
 | 
				
			||||||
                return! all next ctx
 | 
					                return! all next ctx
 | 
				
			||||||
 | 
				
			|||||||
@ -46,7 +46,7 @@ open Microsoft.AspNetCore.Authentication.Cookies
 | 
				
			|||||||
let doLogOn : HttpHandler = fun next ctx -> task {
 | 
					let doLogOn : HttpHandler = fun next ctx -> task {
 | 
				
			||||||
    let! model   = ctx.BindFormAsync<LogOnModel>()
 | 
					    let! model   = ctx.BindFormAsync<LogOnModel>()
 | 
				
			||||||
    let  data    = ctx.Data
 | 
					    let  data    = ctx.Data
 | 
				
			||||||
    let! tryUser = data.WebLogUser.FindByEmail model.EmailAddress ctx.WebLog.Id
 | 
					    let! tryUser = data.WebLogUser.FindByEmail (model.EmailAddress.ToLowerInvariant()) ctx.WebLog.Id
 | 
				
			||||||
    match! verifyPassword tryUser model.Password ctx with 
 | 
					    match! verifyPassword tryUser model.Password ctx with 
 | 
				
			||||||
    | Ok _ ->
 | 
					    | Ok _ ->
 | 
				
			||||||
        let user = tryUser.Value
 | 
					        let user = tryUser.Value
 | 
				
			||||||
 | 
				
			|||||||
@ -20,33 +20,31 @@ let private doCreateWebLog (args: string[]) (sp: IServiceProvider) = task {
 | 
				
			|||||||
            | true, ianaId -> ianaId
 | 
					            | true, ianaId -> ianaId
 | 
				
			||||||
            | false, _ -> raise <| TimeZoneNotFoundException $"Cannot find IANA timezone for {local}"
 | 
					            | false, _ -> raise <| TimeZoneNotFoundException $"Cannot find IANA timezone for {local}"
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Create the web log
 | 
					 | 
				
			||||||
    let webLogId   = WebLogId.Create()
 | 
					 | 
				
			||||||
    let userId     = WebLogUserId.Create()
 | 
					 | 
				
			||||||
    let homePageId = PageId.Create()
 | 
					 | 
				
			||||||
    let slug       = Handlers.Upload.makeSlug args[2]
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // If this is the first web log being created, the user will be an installation admin; otherwise, they will be an
 | 
					    // If this is the first web log being created, the user will be an installation admin; otherwise, they will be an
 | 
				
			||||||
    // admin just over their web log
 | 
					    // admin just over their web log
 | 
				
			||||||
    let! webLogs     = data.WebLog.All()
 | 
					    let! webLogs     = data.WebLog.All()
 | 
				
			||||||
    let  accessLevel = if List.isEmpty webLogs then Administrator else WebLogAdmin
 | 
					    let  accessLevel = if List.isEmpty webLogs then Administrator else WebLogAdmin
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    do! data.WebLog.Add
 | 
					    let homePageId = PageId.Create()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Create the web log
 | 
				
			||||||
 | 
					    let webLog =
 | 
				
			||||||
        { WebLog.Empty with
 | 
					        { WebLog.Empty with
 | 
				
			||||||
                Id          = webLogId
 | 
					            Id          = WebLogId.Create()
 | 
				
			||||||
            Name        = args[2]
 | 
					            Name        = args[2]
 | 
				
			||||||
                Slug        = slug
 | 
					            Slug        = Handlers.Upload.makeSlug args[2]
 | 
				
			||||||
                UrlBase     = args[1]
 | 
					            UrlBase     = args[1].ToLowerInvariant()
 | 
				
			||||||
            DefaultPage = string homePageId
 | 
					            DefaultPage = string homePageId
 | 
				
			||||||
            TimeZone    = timeZone }
 | 
					            TimeZone    = timeZone }
 | 
				
			||||||
 | 
					    do! data.WebLog.Add webLog
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Create the admin user
 | 
					    // Create the admin user
 | 
				
			||||||
    let now  = Noda.now ()
 | 
					    let now  = Noda.now ()
 | 
				
			||||||
    let user =
 | 
					    let user =
 | 
				
			||||||
        { WebLogUser.Empty with
 | 
					        { WebLogUser.Empty with
 | 
				
			||||||
            Id            = userId
 | 
					            Id            = WebLogUserId.Create()
 | 
				
			||||||
            WebLogId      = webLogId
 | 
					            WebLogId      = webLog.Id
 | 
				
			||||||
            Email         = args[3]
 | 
					            Email         = args[3].ToLowerInvariant()
 | 
				
			||||||
            FirstName     = "Admin"
 | 
					            FirstName     = "Admin"
 | 
				
			||||||
            LastName      = "User"
 | 
					            LastName      = "User"
 | 
				
			||||||
            PreferredName = "Admin"
 | 
					            PreferredName = "Admin"
 | 
				
			||||||
@ -58,8 +56,8 @@ let private doCreateWebLog (args: string[]) (sp: IServiceProvider) = task {
 | 
				
			|||||||
    do! data.Page.Add
 | 
					    do! data.Page.Add
 | 
				
			||||||
            { Page.Empty with
 | 
					            { Page.Empty with
 | 
				
			||||||
                Id          = homePageId
 | 
					                Id          = homePageId
 | 
				
			||||||
                WebLogId    = webLogId
 | 
					                WebLogId    = webLog.Id
 | 
				
			||||||
                AuthorId    = userId
 | 
					                AuthorId    = user.Id
 | 
				
			||||||
                Title       = "Welcome to myWebLog!"
 | 
					                Title       = "Welcome to myWebLog!"
 | 
				
			||||||
                Permalink   = Permalink "welcome-to-myweblog.html"
 | 
					                Permalink   = Permalink "welcome-to-myweblog.html"
 | 
				
			||||||
                PublishedOn = now
 | 
					                PublishedOn = now
 | 
				
			||||||
@ -70,11 +68,11 @@ let private doCreateWebLog (args: string[]) (sp: IServiceProvider) = task {
 | 
				
			|||||||
                        Text = Html "<p>This is your default home page.</p>" }
 | 
					                        Text = Html "<p>This is your default home page.</p>" }
 | 
				
			||||||
                ] }
 | 
					                ] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    printfn $"Successfully initialized database for {args[2]} with URL base {args[1]}"
 | 
					    printfn $"Successfully initialized database for {webLog.Name} with URL base {webLog.UrlBase}"
 | 
				
			||||||
    match accessLevel with
 | 
					    match accessLevel with
 | 
				
			||||||
    | Administrator -> printfn $"  ({args[3]} is an installation administrator)"
 | 
					    | Administrator -> printfn $"  ({user.Email} is an installation administrator)"
 | 
				
			||||||
    | WebLogAdmin ->
 | 
					    | WebLogAdmin ->
 | 
				
			||||||
        printfn $"  ({args[3]} is a web log administrator;"
 | 
					        printfn $"  ({user.Email} is a web log administrator;"
 | 
				
			||||||
        printfn """   use "upgrade-user" to promote to installation administrator)"""
 | 
					        printfn """   use "upgrade-user" to promote to installation administrator)"""
 | 
				
			||||||
    | _ -> ()
 | 
					    | _ -> ()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -422,8 +420,9 @@ module Backup =
 | 
				
			|||||||
    /// Generate a backup archive
 | 
					    /// Generate a backup archive
 | 
				
			||||||
    let generateBackup (args: string[]) (sp: IServiceProvider) = task {
 | 
					    let generateBackup (args: string[]) (sp: IServiceProvider) = task {
 | 
				
			||||||
        if args.Length > 1 && args.Length < 5 then
 | 
					        if args.Length > 1 && args.Length < 5 then
 | 
				
			||||||
 | 
					            let url  = args[1].ToLowerInvariant()
 | 
				
			||||||
            let data = sp.GetRequiredService<IData>()
 | 
					            let data = sp.GetRequiredService<IData>()
 | 
				
			||||||
            match! data.WebLog.FindByHost args[1] with
 | 
					            match! data.WebLog.FindByHost url with
 | 
				
			||||||
            | Some webLog ->
 | 
					            | Some webLog ->
 | 
				
			||||||
                let fileName =
 | 
					                let fileName =
 | 
				
			||||||
                    if args.Length = 2 || (args.Length = 3 && args[2] = "pretty") then
 | 
					                    if args.Length = 2 || (args.Length = 3 && args[2] = "pretty") then
 | 
				
			||||||
@ -434,7 +433,7 @@ module Backup =
 | 
				
			|||||||
                        $"{args[2]}.json"
 | 
					                        $"{args[2]}.json"
 | 
				
			||||||
                let prettyOutput = (args.Length = 3 && args[2] = "pretty") || (args.Length = 4 && args[3] = "pretty")
 | 
					                let prettyOutput = (args.Length = 3 && args[2] = "pretty") || (args.Length = 4 && args[3] = "pretty")
 | 
				
			||||||
                do! createBackup webLog fileName prettyOutput data
 | 
					                do! createBackup webLog fileName prettyOutput data
 | 
				
			||||||
            | None -> eprintfn $"Error: no web log found for {args[1]}"
 | 
					            | None -> eprintfn $"Error: no web log found for {url}"
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
            eprintfn """Usage: myWebLog backup [url-base] [*backup-file-name] [**"pretty"]"""
 | 
					            eprintfn """Usage: myWebLog backup [url-base] [*backup-file-name] [**"pretty"]"""
 | 
				
			||||||
            eprintfn """          * optional - default is [web-log-slug].json"""
 | 
					            eprintfn """          * optional - default is [web-log-slug].json"""
 | 
				
			||||||
@ -445,7 +444,7 @@ module Backup =
 | 
				
			|||||||
    let restoreFromBackup (args: string[]) (sp: IServiceProvider) = task {
 | 
					    let restoreFromBackup (args: string[]) (sp: IServiceProvider) = task {
 | 
				
			||||||
        if args.Length = 2 || args.Length = 3 then
 | 
					        if args.Length = 2 || args.Length = 3 then
 | 
				
			||||||
            let data       = sp.GetRequiredService<IData>()
 | 
					            let data       = sp.GetRequiredService<IData>()
 | 
				
			||||||
            let newUrlBase = if args.Length = 3 then Some args[2] else None
 | 
					            let newUrlBase = if args.Length = 3 then Some (args[2].ToLowerInvariant()) else None
 | 
				
			||||||
            do! restoreBackup args[1] newUrlBase (args[0] <> "do-restore") true data
 | 
					            do! restoreBackup args[1] newUrlBase (args[0] <> "do-restore") true data
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
            eprintfn "Usage: myWebLog restore [backup-file-name] [*url-base]"
 | 
					            eprintfn "Usage: myWebLog restore [backup-file-name] [*url-base]"
 | 
				
			||||||
@ -472,7 +471,7 @@ let private doUserUpgrade urlBase email (data: IData) = task {
 | 
				
			|||||||
/// Upgrade a WebLogAdmin user to an Administrator user if the command-line arguments are good
 | 
					/// Upgrade a WebLogAdmin user to an Administrator user if the command-line arguments are good
 | 
				
			||||||
let upgradeUser (args: string[]) (sp: IServiceProvider) = task {
 | 
					let upgradeUser (args: string[]) (sp: IServiceProvider) = task {
 | 
				
			||||||
    match args.Length with
 | 
					    match args.Length with
 | 
				
			||||||
    | 3 -> do! doUserUpgrade args[1] args[2] (sp.GetRequiredService<IData>())
 | 
					    | 3 -> do! doUserUpgrade (args[1].ToLowerInvariant()) (args[2].ToLowerInvariant()) (sp.GetRequiredService<IData>())
 | 
				
			||||||
    | _ -> eprintfn "Usage: myWebLog upgrade-user [web-log-url-base] [email-address]"
 | 
					    | _ -> eprintfn "Usage: myWebLog upgrade-user [web-log-url-base] [email-address]"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -491,6 +490,8 @@ let doSetPassword urlBase email password (data: IData) = task {
 | 
				
			|||||||
/// Set a user's password if the command-line arguments are good
 | 
					/// Set a user's password if the command-line arguments are good
 | 
				
			||||||
let setPassword (args: string[]) (sp: IServiceProvider) = task {
 | 
					let setPassword (args: string[]) (sp: IServiceProvider) = task {
 | 
				
			||||||
    match args.Length with
 | 
					    match args.Length with
 | 
				
			||||||
    | 4 -> do! doSetPassword args[1] args[2] args[3] (sp.GetRequiredService<IData>())
 | 
					    | 4 ->
 | 
				
			||||||
 | 
					        do! doSetPassword
 | 
				
			||||||
 | 
					                (args[1].ToLowerInvariant()) (args[2].ToLowerInvariant()) args[3] (sp.GetRequiredService<IData>())
 | 
				
			||||||
    | _ -> eprintfn "Usage: myWebLog set-password [web-log-url-base] [email-address] [password]"
 | 
					    | _ -> eprintfn "Usage: myWebLog set-password [web-log-url-base] [email-address] [password]"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -31,13 +31,13 @@
 | 
				
			|||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.0.0" />
 | 
					    <PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.0.0" />
 | 
				
			||||||
    <PackageReference Include="DotLiquid" Version="2.2.692" />
 | 
					    <PackageReference Include="DotLiquid" Version="2.2.692" />
 | 
				
			||||||
    <PackageReference Include="Giraffe" Version="6.3.0" />
 | 
					    <PackageReference Include="Giraffe" Version="6.4.0" />
 | 
				
			||||||
    <PackageReference Include="Giraffe.Htmx" Version="1.9.11" />
 | 
					    <PackageReference Include="Giraffe.Htmx" Version="2.0.0" />
 | 
				
			||||||
    <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="1.9.11" />
 | 
					    <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.0" />
 | 
				
			||||||
    <PackageReference Include="NeoSmart.Caching.Sqlite.AspNetCore" Version="8.0.0" />
 | 
					    <PackageReference Include="NeoSmart.Caching.Sqlite.AspNetCore" Version="8.0.0" />
 | 
				
			||||||
    <PackageReference Include="RethinkDB.DistributedCache" Version="1.0.0-rc1" />
 | 
					    <PackageReference Include="RethinkDB.DistributedCache" Version="1.0.0-rc1" />
 | 
				
			||||||
    <PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
 | 
					    <PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
 | 
				
			||||||
    <PackageReference Update="FSharp.Core" Version="8.0.200" />
 | 
					    <PackageReference Update="FSharp.Core" Version="8.0.300" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
				
			|||||||
@ -218,8 +218,9 @@ let main args =
 | 
				
			|||||||
        // Load admin and default themes, and all themes in the /themes directory
 | 
					        // Load admin and default themes, and all themes in the /themes directory
 | 
				
			||||||
        do! Maintenance.loadTheme [| ""; "./admin-theme.zip"   |] app.Services
 | 
					        do! Maintenance.loadTheme [| ""; "./admin-theme.zip"   |] app.Services
 | 
				
			||||||
        do! Maintenance.loadTheme [| ""; "./default-theme.zip" |] app.Services
 | 
					        do! Maintenance.loadTheme [| ""; "./default-theme.zip" |] app.Services
 | 
				
			||||||
        if Directory.Exists "./themes" then
 | 
					        let themePath = Path.Combine(".", "themes")
 | 
				
			||||||
            for themeFile in Directory.EnumerateFiles("./themes", "*-theme.zip") do
 | 
					        if Directory.Exists themePath then
 | 
				
			||||||
 | 
					            for themeFile in Directory.EnumerateFiles(themePath, "*-theme.zip") do
 | 
				
			||||||
                do! Maintenance.loadTheme [| ""; themeFile |] app.Services
 | 
					                do! Maintenance.loadTheme [| ""; themeFile |] app.Services
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
        let _ = app.UseForwardedHeaders()
 | 
					        let _ = app.UseForwardedHeaders()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "Generator": "myWebLog 2.1.1",
 | 
					  "Generator": "myWebLog 2.2",
 | 
				
			||||||
  "Logging": {
 | 
					  "Logging": {
 | 
				
			||||||
    "LogLevel": {
 | 
					    "LogLevel": {
 | 
				
			||||||
      "MyWebLog.Handlers": "Information"
 | 
					      "MyWebLog.Handlers": "Information"
 | 
				
			||||||
 | 
				
			|||||||
@ -1,2 +1,2 @@
 | 
				
			|||||||
myWebLog Admin
 | 
					myWebLog Admin
 | 
				
			||||||
2.1.1
 | 
					2.2
 | 
				
			||||||
@ -1,2 +1,2 @@
 | 
				
			|||||||
myWebLog Default Theme
 | 
					myWebLog Default Theme
 | 
				
			||||||
2.1.1
 | 
					2.2
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user