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