Implement default SQLite connection
- Add logging to SQLite table creation - Create common admin layout template
This commit is contained in:
parent
cfc9472a2e
commit
409b1def32
@ -866,13 +866,13 @@ type RethinkDbData (conn : Net.IConnection, config : DataConfig, log : ILogger<R
|
||||
member _.startUp () = backgroundTask {
|
||||
let! dbs = rethink<string list> { dbList; result; withRetryOnce conn }
|
||||
if not (dbs |> List.contains config.Database) then
|
||||
log.LogInformation($"Creating database {config.Database}...")
|
||||
log.LogInformation $"Creating database {config.Database}..."
|
||||
do! rethink { dbCreate config.Database; write; withRetryOnce; ignoreResult conn }
|
||||
|
||||
let! tables = rethink<string list> { tableList; result; withRetryOnce conn }
|
||||
for tbl in Table.all do
|
||||
if not (tables |> List.contains tbl) then
|
||||
log.LogInformation($"Creating table {tbl}...")
|
||||
log.LogInformation $"Creating table {tbl}..."
|
||||
do! rethink { tableCreate tbl; write; withRetryOnce; ignoreResult conn }
|
||||
|
||||
do! ensureIndexes Table.Category [ "webLogId" ]
|
||||
|
@ -4,6 +4,7 @@ open System
|
||||
open System.IO
|
||||
open System.Threading.Tasks
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
open MyWebLog.ViewModels
|
||||
|
||||
@ -235,7 +236,7 @@ module private SqliteHelpers =
|
||||
|
||||
|
||||
/// SQLite myWebLog data implementation
|
||||
type SQLiteData (conn : SqliteConnection) =
|
||||
type SQLiteData (conn : SqliteConnection, log : ILogger<SQLiteData>) =
|
||||
|
||||
/// Add parameters for category INSERT or UPDATE statements
|
||||
let addCategoryParameters (cmd : SqliteCommand) (cat : Category) =
|
||||
@ -1800,6 +1801,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||
|
||||
let! exists = tableExists "theme"
|
||||
if not exists then
|
||||
log.LogInformation "Creating theme tables..."
|
||||
use cmd = conn.CreateCommand ()
|
||||
cmd.CommandText <-
|
||||
"""CREATE TABLE theme (
|
||||
@ -1825,6 +1827,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||
|
||||
let! exists = tableExists "web_log"
|
||||
if not exists then
|
||||
log.LogInformation "Creating web log tables..."
|
||||
use cmd = conn.CreateCommand ()
|
||||
cmd.CommandText <-
|
||||
"""CREATE TABLE web_log (
|
||||
@ -1870,6 +1873,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||
|
||||
let! exists = tableExists "category"
|
||||
if not exists then
|
||||
log.LogInformation "Creating category table..."
|
||||
use cmd = conn.CreateCommand ()
|
||||
cmd.CommandText <-
|
||||
"""CREATE TABLE category (
|
||||
@ -1883,6 +1887,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||
|
||||
let! exists = tableExists "web_log_user"
|
||||
if not exists then
|
||||
log.LogInformation "Creating user table..."
|
||||
use cmd = conn.CreateCommand ()
|
||||
cmd.CommandText <-
|
||||
"""CREATE TABLE web_log_user (
|
||||
@ -1900,6 +1905,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||
|
||||
let! exists = tableExists "page"
|
||||
if not exists then
|
||||
log.LogInformation "Creating page tables..."
|
||||
use cmd = conn.CreateCommand ()
|
||||
cmd.CommandText <-
|
||||
"""CREATE TABLE page (
|
||||
@ -1937,6 +1943,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||
|
||||
let! exists = tableExists "post"
|
||||
if not exists then
|
||||
log.LogInformation "Creating post tables..."
|
||||
use cmd = conn.CreateCommand ()
|
||||
cmd.CommandText <-
|
||||
"""CREATE TABLE post (
|
||||
@ -1998,6 +2005,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||
|
||||
let! exists = tableExists "tag_map"
|
||||
if not exists then
|
||||
log.LogInformation "Creating tag map tables..."
|
||||
use cmd = conn.CreateCommand ()
|
||||
cmd.CommandText <-
|
||||
"""CREATE TABLE tag_map (
|
||||
|
@ -38,19 +38,23 @@ module DataImplementation =
|
||||
open RethinkDb.Driver.Net
|
||||
|
||||
/// Get the configured data implementation
|
||||
let get (sp : IServiceProvider) : IData option =
|
||||
let get (sp : IServiceProvider) : IData =
|
||||
let config = sp.GetRequiredService<IConfiguration> ()
|
||||
if (config.GetSection "RethinkDB").Exists () then
|
||||
if (config.GetConnectionString >> isNull >> not) "SQLite" then
|
||||
let conn = new SqliteConnection (config.GetConnectionString "SQLite")
|
||||
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||
upcast SQLiteData (conn, sp.GetRequiredService<ILogger<SQLiteData>> ())
|
||||
elif (config.GetSection "RethinkDB").Exists () then
|
||||
Json.all () |> Seq.iter Converter.Serializer.Converters.Add
|
||||
let rethinkCfg = DataConfig.FromConfiguration (config.GetSection "RethinkDB")
|
||||
let conn = rethinkCfg.CreateConnectionAsync () |> Async.AwaitTask |> Async.RunSynchronously
|
||||
Some (upcast RethinkDbData (conn, rethinkCfg, sp.GetRequiredService<ILogger<RethinkDbData>> ()))
|
||||
elif (config.GetConnectionString >> isNull >> not) "SQLite" then
|
||||
let conn = new SqliteConnection (config.GetConnectionString "SQLite")
|
||||
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||
Some (upcast SQLiteData conn)
|
||||
upcast RethinkDbData (conn, rethinkCfg, sp.GetRequiredService<ILogger<RethinkDbData>> ())
|
||||
else
|
||||
None
|
||||
let log = sp.GetRequiredService<ILogger<SQLiteData>> ()
|
||||
log.LogInformation "Using default SQLite database myweblog.db"
|
||||
let conn = new SqliteConnection ("Data Source=./myweblog.db;Cache=Shared")
|
||||
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||
upcast SQLiteData (conn, log)
|
||||
|
||||
|
||||
open Giraffe
|
||||
@ -78,39 +82,37 @@ let rec main args =
|
||||
let _ = builder.Services.AddAuthorization ()
|
||||
let _ = builder.Services.AddAntiforgery ()
|
||||
|
||||
let sp = builder.Services.BuildServiceProvider ()
|
||||
match DataImplementation.get sp with
|
||||
| Some data ->
|
||||
task {
|
||||
do! data.startUp ()
|
||||
do! WebLogCache.fill data
|
||||
do! ThemeAssetCache.fill data
|
||||
} |> Async.AwaitTask |> Async.RunSynchronously
|
||||
|
||||
// Define distributed cache implementation based on data implementation
|
||||
match data with
|
||||
| :? RethinkDbData as rethink ->
|
||||
// A RethinkDB connection is designed to work as a singleton
|
||||
builder.Services.AddSingleton<IData> data |> ignore
|
||||
builder.Services.AddDistributedRethinkDBCache (fun opts ->
|
||||
opts.TableName <- "Session"
|
||||
opts.Connection <- rethink.Conn)
|
||||
|> ignore
|
||||
| :? SQLiteData ->
|
||||
// ADO.NET connections are designed to work as per-request instantiation
|
||||
let cfg = sp.GetRequiredService<IConfiguration> ()
|
||||
builder.Services.AddScoped<SqliteConnection> (fun sp ->
|
||||
let conn = new SqliteConnection (cfg.GetConnectionString "SQLite")
|
||||
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||
conn)
|
||||
|> ignore
|
||||
builder.Services.AddScoped<IData, SQLiteData> () |> ignore
|
||||
// Use SQLite for caching as well
|
||||
let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SQLiteCachePath")) "./session.db"
|
||||
builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath) |> ignore
|
||||
| _ -> ()
|
||||
| None ->
|
||||
invalidOp "There is no data configuration present; please add a RethinkDB section or LiteDB connection string"
|
||||
let sp = builder.Services.BuildServiceProvider ()
|
||||
let data = DataImplementation.get sp
|
||||
|
||||
task {
|
||||
do! data.startUp ()
|
||||
do! WebLogCache.fill data
|
||||
do! ThemeAssetCache.fill data
|
||||
} |> Async.AwaitTask |> Async.RunSynchronously
|
||||
|
||||
// Define distributed cache implementation based on data implementation
|
||||
match data with
|
||||
| :? RethinkDbData as rethink ->
|
||||
// A RethinkDB connection is designed to work as a singleton
|
||||
builder.Services.AddSingleton<IData> data |> ignore
|
||||
builder.Services.AddDistributedRethinkDBCache (fun opts ->
|
||||
opts.TableName <- "Session"
|
||||
opts.Connection <- rethink.Conn)
|
||||
|> ignore
|
||||
| :? SQLiteData as sql ->
|
||||
// ADO.NET connections are designed to work as per-request instantiation
|
||||
let cfg = sp.GetRequiredService<IConfiguration> ()
|
||||
builder.Services.AddScoped<SqliteConnection> (fun sp ->
|
||||
let conn = new SqliteConnection (sql.Conn.ConnectionString)
|
||||
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||
conn)
|
||||
|> ignore
|
||||
builder.Services.AddScoped<IData, SQLiteData> () |> ignore
|
||||
// Use SQLite for caching as well
|
||||
let cachePath = Option.ofObj (cfg.GetConnectionString "SQLiteCachePath") |> Option.defaultValue "./session.db"
|
||||
builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath) |> ignore
|
||||
| _ -> ()
|
||||
|
||||
let _ = builder.Services.AddSession(fun opts ->
|
||||
opts.IdleTimeout <- TimeSpan.FromMinutes 60
|
||||
|
54
src/admin-theme/_layout.liquid
Normal file
54
src/admin-theme/_layout.liquid
Normal file
@ -0,0 +1,54 @@
|
||||
<header>
|
||||
<nav class="navbar navbar-dark bg-dark navbar-expand-md justify-content-start px-2 position-fixed top-0 w-100">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ "" | relative_link }}" hx-boost="false">{{ web_log.name }}</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText"
|
||||
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
{% if logged_on -%}
|
||||
<ul class="navbar-nav">
|
||||
{{ "admin/dashboard" | nav_link: "Dashboard" }}
|
||||
{{ "admin/pages" | nav_link: "Pages" }}
|
||||
{{ "admin/posts" | nav_link: "Posts" }}
|
||||
{{ "admin/categories" | nav_link: "Categories" }}
|
||||
{{ "admin/settings" | nav_link: "Settings" }}
|
||||
</ul>
|
||||
{%- endif %}
|
||||
<ul class="navbar-nav flex-grow-1 justify-content-end">
|
||||
{% if logged_on -%}
|
||||
{{ "admin/user/edit" | nav_link: "Edit User" }}
|
||||
{{ "user/log-off" | nav_link: "Log Off" }}
|
||||
{%- else -%}
|
||||
{{ "user/log-on" | nav_link: "Log On" }}
|
||||
{%- endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="mx-3 mt-3">
|
||||
<div class="messages mt-2" id="msgContainer">
|
||||
{% for msg in messages %}
|
||||
<div role="alert" class="alert alert-{{ msg.level }} alert-dismissible fade show">
|
||||
{{ msg.message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
{% if msg.detail %}
|
||||
<hr>
|
||||
{{ msg.detail.value }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ content }}
|
||||
</main>
|
||||
<footer class="position-fixed bottom-0 w-100">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-end">
|
||||
<img src="{{ "themes/admin/logo-light.png" | relative_link }}" alt="myWebLog" width="120" height="34">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
@ -1,63 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ page_title | escape }} « Admin « {{ web_log.name | escape }}</title>
|
||||
<title>{{ page_title | strip_html }} « Admin « {{ web_log.name | strip_html }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-dark bg-dark navbar-expand-md justify-content-start px-2 position-fixed top-0 w-100">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ "" | relative_link }}" hx-boost="false">{{ web_log.name }}</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText"
|
||||
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
{% if logged_on -%}
|
||||
<ul class="navbar-nav">
|
||||
{{ "admin/dashboard" | nav_link: "Dashboard" }}
|
||||
{{ "admin/pages" | nav_link: "Pages" }}
|
||||
{{ "admin/posts" | nav_link: "Posts" }}
|
||||
{{ "admin/categories" | nav_link: "Categories" }}
|
||||
{{ "admin/settings" | nav_link: "Settings" }}
|
||||
</ul>
|
||||
{%- endif %}
|
||||
<ul class="navbar-nav flex-grow-1 justify-content-end">
|
||||
{% if logged_on -%}
|
||||
{{ "admin/user/edit" | nav_link: "Edit User" }}
|
||||
{{ "user/log-off" | nav_link: "Log Off" }}
|
||||
{%- else -%}
|
||||
{{ "user/log-on" | nav_link: "Log On" }}
|
||||
{%- endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="mx-3 mt-3">
|
||||
<div class="messages mt-2" id="msgContainer">
|
||||
{% for msg in messages %}
|
||||
<div role="alert" class="alert alert-{{ msg.level }} alert-dismissible fade show">
|
||||
{{ msg.message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
{% if msg.detail %}
|
||||
<hr>
|
||||
{{ msg.detail.value }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ content }}
|
||||
</main>
|
||||
<footer class="position-fixed bottom-0 w-100">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-end">
|
||||
<img src="{{ "themes/admin/logo-light.png" | relative_link }}" alt="myWebLog" width="120" height="34">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{% include_template "_layout" %}
|
||||
<script>Admin.dismissSuccesses()</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,66 +3,13 @@
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="generator" content="{{ generator }}">
|
||||
<title>{{ page_title | escape }} « Admin « {{ web_log.name | escape }}</title>
|
||||
<title>{{ page_title | strip_html }} « Admin « {{ web_log.name | strip_html }}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{{ "themes/admin/admin.css" | relative_link }}">
|
||||
</head>
|
||||
<body hx-boost="true">
|
||||
<header>
|
||||
<nav class="navbar navbar-dark bg-dark navbar-expand-md justify-content-start px-2 position-fixed top-0 w-100">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ "" | relative_link }}" hx-boost="false">{{ web_log.name }}</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText"
|
||||
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
{% if logged_on -%}
|
||||
<ul class="navbar-nav">
|
||||
{{ "admin/dashboard" | nav_link: "Dashboard" }}
|
||||
{{ "admin/pages" | nav_link: "Pages" }}
|
||||
{{ "admin/posts" | nav_link: "Posts" }}
|
||||
{{ "admin/categories" | nav_link: "Categories" }}
|
||||
{{ "admin/settings" | nav_link: "Settings" }}
|
||||
</ul>
|
||||
{%- endif %}
|
||||
<ul class="navbar-nav flex-grow-1 justify-content-end">
|
||||
{% if logged_on -%}
|
||||
{{ "admin/user/edit" | nav_link: "Edit User" }}
|
||||
{{ "user/log-off" | nav_link: "Log Off" }}
|
||||
{%- else -%}
|
||||
{{ "user/log-on" | nav_link: "Log On" }}
|
||||
{%- endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="mx-3 mt-3">
|
||||
<div class="messages mt-2" id="msgContainer">
|
||||
{% for msg in messages %}
|
||||
<div role="alert" class="alert alert-{{ msg.level }} alert-dismissible fade show">
|
||||
{{ msg.message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
{% if msg.detail %}
|
||||
<hr>
|
||||
{{ msg.detail.value }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ content }}
|
||||
</main>
|
||||
<footer class="position-fixed bottom-0 w-100">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-end">
|
||||
<img src="{{ "themes/admin/logo-light.png" | relative_link }}" alt="myWebLog" width="120" height="34">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{% include_template "_layout" %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
|
||||
crossorigin="anonymous"></script>
|
||||
|
@ -1,7 +1,11 @@
|
||||
{% if model.subtitle %}
|
||||
<h2>{{ model.subtitle.value }}</h2>
|
||||
{% endif %}
|
||||
<section class="container" aria-label="The posts for the page">
|
||||
{%- if is_category or is_tag %}
|
||||
<h1 class="index-title">{{ page_title }}</h1>
|
||||
{%- if is_category %}
|
||||
{%- assign cat = categories | where: "slug", slug | first -%}
|
||||
{%- if cat.description %}<h4 class="text-muted">{{ cat.description.value }}</h4>{% endif -%}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
<section class="container mt-3" aria-label="The posts for the page">
|
||||
{% for post in model.posts %}
|
||||
<article>
|
||||
<h1>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<title>{{ page_title | escape }}{% if page_title %} « {% endif %}{{ web_log.name | escape }}</title>
|
||||
<title>{{ page_title | strip_html }}{% if page_title %} « {% endif %}{{ web_log.name | strip_html }}</title>
|
||||
{% page_head -%}
|
||||
</head>
|
||||
<body>
|
||||
@ -19,7 +19,7 @@
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
{% if web_log.subtitle -%}
|
||||
<span class="navbar-text">{{ web_log.subtitle.value | escape }}</span>
|
||||
<span class="navbar-text">{{ web_log.subtitle.value }}</span>
|
||||
{%- endif %}
|
||||
<ul class="navbar-nav">
|
||||
{% unless web_log.default_page == "posts" %}{{ "page/1" | nav_link: "Posts" }}{% endunless %}
|
||||
|
Loading…
Reference in New Issue
Block a user