diff --git a/src/MyWebLog.Data/Category.cs b/src/MyWebLog.Data/Category.cs
new file mode 100644
index 0000000..c472dec
--- /dev/null
+++ b/src/MyWebLog.Data/Category.cs
@@ -0,0 +1,42 @@
+namespace MyWebLog.Data;
+
+///
+/// A category under which a post may be identfied
+///
+public class Category
+{
+ ///
+ /// The ID of the category
+ ///
+ public string Id { get; set; } = "";
+
+ ///
+ /// The displayed name
+ ///
+ public string Name { get; set; } = "";
+
+ ///
+ /// The slug (used in category URLs)
+ ///
+ public string Slug { get; set; } = "";
+
+ ///
+ /// A longer description of the category
+ ///
+ public string? Description { get; set; } = null;
+
+ ///
+ /// The parent ID of this category (if a subcategory)
+ ///
+ public string? ParentId { get; set; } = null;
+
+ ///
+ /// The parent of this category (if a subcategory)
+ ///
+ public Category? Parent { get; set; } = default;
+
+ ///
+ /// The posts assigned to this category
+ ///
+ public ICollection Posts { get; set; } = default!;
+}
diff --git a/src/MyWebLog.Data/Comment.cs b/src/MyWebLog.Data/Comment.cs
new file mode 100644
index 0000000..bdcbded
--- /dev/null
+++ b/src/MyWebLog.Data/Comment.cs
@@ -0,0 +1,62 @@
+namespace MyWebLog.Data;
+
+///
+/// A comment on a post
+///
+public class Comment
+{
+ ///
+ /// The ID of the comment
+ ///
+ public string Id { get; set; } = "";
+
+ ///
+ /// The ID of the post to which this comment applies
+ ///
+ public string PostId { get; set; } = "";
+
+ ///
+ /// The post to which this comment applies
+ ///
+ public Post Post { get; set; } = default!;
+
+ ///
+ /// The ID of the comment to which this comment is a reply
+ ///
+ public string? InReplyToId { get; set; } = null;
+
+ ///
+ /// The comment to which this comment is a reply
+ ///
+ public Comment? InReplyTo { get; set; } = default;
+
+ ///
+ /// The name of the commentor
+ ///
+ public string Name { get; set; } = "";
+
+ ///
+ /// The e-mail address of the commentor
+ ///
+ public string Email { get; set; } = "";
+
+ ///
+ /// The URL of the commentor's personal website
+ ///
+ public string? Url { get; set; } = null;
+
+ ///
+ /// The status of the comment
+ ///
+ public CommentStatus Status { get; set; } = CommentStatus.Pending;
+
+ ///
+ /// When the comment was posted
+ ///
+ public DateTime PostedOn { get; set; } = DateTime.UtcNow;
+
+ ///
+ /// The text of the comment
+ ///
+ public string Text { get; set; } = "";
+}
diff --git a/src/MyWebLog.Data/Enums.cs b/src/MyWebLog.Data/Enums.cs
new file mode 100644
index 0000000..3eecc51
--- /dev/null
+++ b/src/MyWebLog.Data/Enums.cs
@@ -0,0 +1,47 @@
+namespace MyWebLog.Data;
+
+///
+/// The source format for a revision
+///
+public enum RevisionSource
+{
+ /// Markdown text
+ Markdown,
+ /// HTML
+ Html
+}
+
+///
+/// A level of authorization for a given web log
+///
+public enum AuthorizationLevel
+{
+ /// The user may administer all aspects of a web log
+ Administrator,
+ /// The user is a known user of a web log
+ User
+}
+
+///
+/// Statuses for posts
+///
+public enum PostStatus
+{
+ /// The post should not be publicly available
+ Draft,
+ /// The post is publicly viewable
+ Published
+}
+
+///
+/// Statuses for post comments
+///
+public enum CommentStatus
+{
+ /// The comment is approved
+ Approved,
+ /// The comment has yet to be approved
+ Pending,
+ /// The comment was unsolicited and unwelcome
+ Spam
+}
diff --git a/src/MyWebLog.Data/MyWebLog.Data.csproj b/src/MyWebLog.Data/MyWebLog.Data.csproj
new file mode 100644
index 0000000..132c02c
--- /dev/null
+++ b/src/MyWebLog.Data/MyWebLog.Data.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/src/MyWebLog.Data/Page.cs b/src/MyWebLog.Data/Page.cs
new file mode 100644
index 0000000..e1b3dd7
--- /dev/null
+++ b/src/MyWebLog.Data/Page.cs
@@ -0,0 +1,62 @@
+namespace MyWebLog.Data;
+
+///
+/// A page (text not associated with a date/time)
+///
+public class Page
+{
+ ///
+ /// The ID of this page
+ ///
+ public string Id { get; set; } = "";
+
+ ///
+ /// The ID of the author of this page
+ ///
+ public string AuthorId { get; set; } = "";
+
+ ///
+ /// The author of this page
+ ///
+ public WebLogUser Author { get; set; } = default!;
+
+ ///
+ /// The title of the page
+ ///
+ public string Title { get; set; } = "";
+
+ ///
+ /// The link at which this page is displayed
+ ///
+ public string Permalink { get; set; } = "";
+
+ ///
+ /// The instant this page was published
+ ///
+ public DateTime PublishedOn { get; set; } = DateTime.MinValue;
+
+ ///
+ /// The instant this page was last updated
+ ///
+ public DateTime UpdatedOn { get; set; } = DateTime.MinValue;
+
+ ///
+ /// Whether this page shows as part of the web log's navigation
+ ///
+ public bool ShowInPageList { get; set; } = false;
+
+ ///
+ /// The current text of the page
+ ///
+ public string Text { get; set; } = "";
+
+ ///
+ /// Permalinks at which this page may have been previously served (useful for migrated content)
+ ///
+ public ICollection PriorPermalinks { get; set; } = default!;
+
+ ///
+ /// Revisions of this page
+ ///
+ public ICollection Revisions { get; set; } = default!;
+}
diff --git a/src/MyWebLog.Data/Permalink.cs b/src/MyWebLog.Data/Permalink.cs
new file mode 100644
index 0000000..b36ceca
--- /dev/null
+++ b/src/MyWebLog.Data/Permalink.cs
@@ -0,0 +1,49 @@
+namespace MyWebLog.Data;
+
+///
+/// A permalink which a post or page used to have
+///
+public abstract class Permalink
+{
+ ///
+ /// The ID of this permalink
+ ///
+ public string Id { get; set; } = "";
+
+ ///
+ /// The link
+ ///
+ public string Url { get; set; } = "";
+}
+
+///
+/// A prior permalink for a page
+///
+public class PagePermalink : Permalink
+{
+ ///
+ /// The ID of the page to which this permalink belongs
+ ///
+ public string PageId { get; set; } = "";
+
+ ///
+ /// The page to which this permalink belongs
+ ///
+ public Page Page { get; set; } = default!;
+}
+
+///
+/// A prior permalink for a post
+///
+public class PostPermalink : Permalink
+{
+ ///
+ /// The ID of the post to which this permalink belongs
+ ///
+ public string PostId { get; set; } = "";
+
+ ///
+ /// The post to which this permalink belongs
+ ///
+ public Post Post { get; set; } = default!;
+}
diff --git a/src/MyWebLog.Data/Post.cs b/src/MyWebLog.Data/Post.cs
new file mode 100644
index 0000000..f2afe39
--- /dev/null
+++ b/src/MyWebLog.Data/Post.cs
@@ -0,0 +1,72 @@
+namespace MyWebLog.Data;
+
+///
+/// A web log post
+///
+public class Post
+{
+ ///
+ /// The ID of this post
+ ///
+ public string Id { get; set; } = "";
+
+ ///
+ /// The ID of the author of this post
+ ///
+ public string AuthorId { get; set; } = "";
+
+ ///
+ /// The author of the post
+ ///
+ public WebLogUser Author { get; set; } = default!;
+
+ ///
+ /// The status
+ ///
+ public PostStatus Status { get; set; } = PostStatus.Draft;
+
+ ///
+ /// The title
+ ///
+ public string Title { get; set; } = "";
+
+ ///
+ /// The link at which the post resides
+ ///
+ public string Permalink { get; set; } = "";
+
+ ///
+ /// The instant on which the post was originally published
+ ///
+ public DateTime? PublishedOn { get; set; } = null;
+
+ ///
+ /// The instant on which the post was last updated
+ ///
+ public DateTime UpdatedOn { get; set; } = DateTime.MinValue;
+
+ ///
+ /// The text of the post in HTML (ready to display) format
+ ///
+ public string Text { get; set; } = "";
+
+ ///
+ /// The Ids of the categories to which this is assigned
+ ///
+ public ICollection Categories { get; set; } = default!;
+
+ ///
+ /// The tags for the post
+ ///
+ public ICollection Tags { get; set; } = default!;
+
+ ///
+ /// Permalinks at which this post may have been previously served (useful for migrated content)
+ ///
+ public ICollection PriorPermalinks { get; set; } = default!;
+
+ ///
+ /// The revisions for this post
+ ///
+ public ICollection Revisions { get; set; } = default!;
+}
diff --git a/src/MyWebLog.Data/Revision.cs b/src/MyWebLog.Data/Revision.cs
new file mode 100644
index 0000000..cd50875
--- /dev/null
+++ b/src/MyWebLog.Data/Revision.cs
@@ -0,0 +1,59 @@
+namespace MyWebLog.Data;
+
+///
+/// A revision of a page or post
+///
+public abstract class Revision
+{
+ ///
+ /// The ID of this revision
+ ///
+ public string Id { get; set; } = "";
+
+ ///
+ /// When this revision was saved
+ ///
+ public DateTime AsOf { get; set; } = DateTime.UtcNow;
+
+ ///
+ /// The source language (Markdown or HTML)
+ ///
+ public RevisionSource SourceType { get; set; } = RevisionSource.Html;
+
+ ///
+ /// The text of the revision
+ ///
+ public string Text { get; set; } = "";
+}
+
+///
+/// A revision of a page
+///
+public class PageRevision : Revision
+{
+ ///
+ /// The ID of the page to which this revision belongs
+ ///
+ public string PageId { get; set; } = "";
+
+ ///
+ /// The page to which this revision belongs
+ ///
+ public Page Page { get; set; } = default!;
+}
+
+///
+/// A revision of a post
+///
+public class PostRevision : Revision
+{
+ ///
+ /// The ID of the post to which this revision applies
+ ///
+ public string PostId { get; set; } = "";
+
+ ///
+ /// The post to which this revision applies
+ ///
+ public Post Post { get; set; } = default!;
+}
diff --git a/src/MyWebLog.Data/Tag.cs b/src/MyWebLog.Data/Tag.cs
new file mode 100644
index 0000000..28f07ef
--- /dev/null
+++ b/src/MyWebLog.Data/Tag.cs
@@ -0,0 +1,17 @@
+namespace MyWebLog.Data;
+
+///
+/// A tag
+///
+public class Tag
+{
+ ///
+ /// The name of the tag
+ ///
+ public string Name { get; set; } = "";
+
+ ///
+ /// The posts with this tag assigned
+ ///
+ public ICollection Posts { get; set; } = default!;
+}
diff --git a/src/MyWebLog.Data/WebLogDbContext.cs b/src/MyWebLog.Data/WebLogDbContext.cs
new file mode 100644
index 0000000..afbcd8f
--- /dev/null
+++ b/src/MyWebLog.Data/WebLogDbContext.cs
@@ -0,0 +1,69 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace MyWebLog.Data;
+
+///
+/// Data context for web log data
+///
+public sealed class WebLogDbContext : DbContext
+{
+ ///
+ /// The categories for the web log
+ ///
+ public DbSet Categories { get; set; } = default!;
+
+ ///
+ /// Comments on posts
+ ///
+ public DbSet Comments { get; set; } = default!;
+
+ ///
+ /// Pages
+ ///
+ public DbSet Pages { get; set; } = default!;
+
+ ///
+ /// Web log posts
+ ///
+ public DbSet Posts { get; set; } = default!;
+
+ ///
+ /// Post tags
+ ///
+ public DbSet Tags { get; set; } = default!;
+
+ ///
+ /// The users of the web log
+ ///
+ public DbSet Users { get; set; } = default!;
+
+ ///
+ /// The details for the web log
+ ///
+ public DbSet WebLogDetails { get; set; } = default!;
+
+ ///
+ /// Constructor
+ ///
+ /// Configuration options
+ public WebLogDbContext(DbContextOptions options) : base(options) { }
+
+ ///
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ // Tag and WebLogDetails use Name as its ID
+ modelBuilder.Entity().HasKey(t => t.Name);
+ modelBuilder.Entity().HasKey(wld => wld.Name);
+
+ // Index slugs and links
+ modelBuilder.Entity().HasIndex(c => c.Slug);
+ modelBuilder.Entity().HasIndex(p => p.Permalink);
+ modelBuilder.Entity().HasIndex(p => p.Permalink);
+
+ // Link "author" to "user"
+ modelBuilder.Entity().HasOne(p => p.Author).WithMany(wbu => wbu.Pages).HasForeignKey(p => p.AuthorId);
+ modelBuilder.Entity().HasOne(p => p.Author).WithMany(wbu => wbu.Posts).HasForeignKey(p => p.AuthorId);
+ }
+}
diff --git a/src/MyWebLog.Data/WebLogDetails.cs b/src/MyWebLog.Data/WebLogDetails.cs
new file mode 100644
index 0000000..b891669
--- /dev/null
+++ b/src/MyWebLog.Data/WebLogDetails.cs
@@ -0,0 +1,37 @@
+namespace MyWebLog.Data;
+
+///
+/// The details about a web log
+///
+public class WebLogDetails
+{
+ ///
+ /// The name of the web log
+ ///
+ public string Name { get; set; } = "";
+
+ ///
+ /// A subtitle for the web log
+ ///
+ public string? Subtitle { get; set; } = null;
+
+ ///
+ /// The default page ("posts" or a page Id)
+ ///
+ public string DefaultPage { get; set; } = "";
+
+ ///
+ /// The path of the theme (within /views/themes)
+ ///
+ public string ThemePath { get; set; } = "";
+
+ ///
+ /// The URL base
+ ///
+ public string UrlBase { get; set; } = "";
+
+ ///
+ /// The time zone in which dates/times should be displayed
+ ///
+ public string TimeZone { get; set; } = "";
+}
diff --git a/src/MyWebLog.Data/WebLogUser.cs b/src/MyWebLog.Data/WebLogUser.cs
new file mode 100644
index 0000000..075e83f
--- /dev/null
+++ b/src/MyWebLog.Data/WebLogUser.cs
@@ -0,0 +1,57 @@
+namespace MyWebLog.Data;
+
+///
+/// A user of the web log
+///
+public class WebLogUser
+{
+ ///
+ /// The ID of the user
+ ///
+ public string Id { get; set; } = "";
+
+ ///
+ /// The user name (e-mail address)
+ ///
+ public string UserName { get; set; } = "";
+
+ ///
+ /// The user's first name
+ ///
+ public string FirstName { get; set; } = "";
+
+ ///
+ /// The user's last name
+ ///
+ public string LastName { get; set; } = "";
+
+ ///
+ /// The user's preferred name
+ ///
+ public string PreferredName { get; set; } = "";
+
+ ///
+ /// The hash of the user's password
+ ///
+ public string PasswordHash { get; set; } = "";
+
+ ///
+ /// Salt used to calculate the user's password hash
+ ///
+ public Guid Salt { get; set; } = Guid.Empty;
+
+ ///
+ /// The URL of the user's personal site
+ ///
+ public string? Url { get; set; } = null;
+
+ ///
+ /// Pages written by this author
+ ///
+ public ICollection Pages { get; set; } = default!;
+
+ ///
+ /// Posts written by this author
+ ///
+ public ICollection Posts { get; set; } = default!;
+}
diff --git a/src/MyWebLog.sln b/src/MyWebLog.sln
new file mode 100644
index 0000000..5251070
--- /dev/null
+++ b/src/MyWebLog.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32210.238
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "MyWebLog", "MyWebLog\MyWebLog.fsproj", "{2E5E2346-25FE-4CBD-89AA-6148A33DE09C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyWebLog.Data", "MyWebLog.Data\MyWebLog.Data.csproj", "{0177C744-F913-4352-A0EC-478B4B0388C3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2E5E2346-25FE-4CBD-89AA-6148A33DE09C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E5E2346-25FE-4CBD-89AA-6148A33DE09C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E5E2346-25FE-4CBD-89AA-6148A33DE09C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E5E2346-25FE-4CBD-89AA-6148A33DE09C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0177C744-F913-4352-A0EC-478B4B0388C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0177C744-F913-4352-A0EC-478B4B0388C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0177C744-F913-4352-A0EC-478B4B0388C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0177C744-F913-4352-A0EC-478B4B0388C3}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {70EEF0F4-8709-44A8-AD9B-24D1EB84CFB4}
+ EndGlobalSection
+EndGlobal