From 9e0410f1000b07cc2f8974c9fdc8e8d4e3a3e18e Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Tue, 20 Feb 2024 20:36:23 +0000 Subject: [PATCH] feat: add tutorials page --- .../TutorialArticleConfiguration.cs | 27 ++++ .../TutorialFolderConfiguration.cs | 23 +++ OliverBooth/Data/Web/ITutorialArticle.cs | 67 +++++++++ OliverBooth/Data/Web/ITutorialFolder.cs | 37 +++++ OliverBooth/Data/Web/TutorialArticle.cs | 98 ++++++++++++ OliverBooth/Data/Web/TutorialFolder.cs | 83 +++++++++++ OliverBooth/Data/Web/WebContext.cs | 14 ++ OliverBooth/Pages/Tutorials/Article.cshtml | 88 +++++++++++ OliverBooth/Pages/Tutorials/Article.cshtml.cs | 41 ++++++ OliverBooth/Pages/Tutorials/Index.cshtml | 109 +++++++++++--- OliverBooth/Pages/Tutorials/Index.cshtml.cs | 37 +++++ OliverBooth/Program.cs | 1 + OliverBooth/Services/ITutorialService.cs | 87 +++++++++++ OliverBooth/Services/TutorialService.cs | 139 ++++++++++++++++++ 14 files changed, 834 insertions(+), 17 deletions(-) create mode 100644 OliverBooth/Data/Web/Configuration/TutorialArticleConfiguration.cs create mode 100644 OliverBooth/Data/Web/Configuration/TutorialFolderConfiguration.cs create mode 100644 OliverBooth/Data/Web/ITutorialArticle.cs create mode 100644 OliverBooth/Data/Web/ITutorialFolder.cs create mode 100644 OliverBooth/Data/Web/TutorialArticle.cs create mode 100644 OliverBooth/Data/Web/TutorialFolder.cs create mode 100644 OliverBooth/Pages/Tutorials/Article.cshtml create mode 100644 OliverBooth/Pages/Tutorials/Article.cshtml.cs create mode 100644 OliverBooth/Pages/Tutorials/Index.cshtml.cs create mode 100644 OliverBooth/Services/ITutorialService.cs create mode 100644 OliverBooth/Services/TutorialService.cs diff --git a/OliverBooth/Data/Web/Configuration/TutorialArticleConfiguration.cs b/OliverBooth/Data/Web/Configuration/TutorialArticleConfiguration.cs new file mode 100644 index 0000000..17f578c --- /dev/null +++ b/OliverBooth/Data/Web/Configuration/TutorialArticleConfiguration.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace OliverBooth.Data.Web.Configuration; + +/// +/// Represents the configuration for the entity. +/// +internal sealed class TutorialArticleConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("TutorialArticle"); + builder.HasKey(e => e.Id); + + builder.Property(e => e.Id).IsRequired(); + builder.Property(e => e.Folder).IsRequired(); + builder.Property(e => e.Published).IsRequired(); + builder.Property(e => e.Updated); + builder.Property(e => e.Slug).IsRequired(); + builder.Property(e => e.Title).IsRequired(); + builder.Property(e => e.PreviewImageUrl).HasConversion(); + builder.Property(e => e.NextPart); + builder.Property(e => e.PreviousPart); + } +} diff --git a/OliverBooth/Data/Web/Configuration/TutorialFolderConfiguration.cs b/OliverBooth/Data/Web/Configuration/TutorialFolderConfiguration.cs new file mode 100644 index 0000000..a925f63 --- /dev/null +++ b/OliverBooth/Data/Web/Configuration/TutorialFolderConfiguration.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace OliverBooth.Data.Web.Configuration; + +/// +/// Represents the configuration for the entity. +/// +internal sealed class TutorialFolderConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("TutorialFolder"); + builder.HasKey(e => e.Id); + + builder.Property(e => e.Id).IsRequired(); + builder.Property(e => e.Parent); + builder.Property(e => e.Slug).IsRequired(); + builder.Property(e => e.Title).IsRequired(); + builder.Property(e => e.PreviewImageUrl).HasConversion(); + } +} diff --git a/OliverBooth/Data/Web/ITutorialArticle.cs b/OliverBooth/Data/Web/ITutorialArticle.cs new file mode 100644 index 0000000..a52ac3d --- /dev/null +++ b/OliverBooth/Data/Web/ITutorialArticle.cs @@ -0,0 +1,67 @@ +namespace OliverBooth.Data.Web; + +/// +/// Represents a tutorial article. +/// +public interface ITutorialArticle +{ + /// + /// Gets the body of this article. + /// + /// The body. + string Body { get; } + + /// + /// Gets the ID of the folder this article is contained within. + /// + /// The ID of the folder. + int Folder { get; } + + /// + /// Gets the ID of this article. + /// + /// The ID. + int Id { get; } + + /// + /// Gets the ID of the next article to this one. + /// + /// The next part ID. + int? NextPart { get; } + + /// + /// Gets the URL of the article's preview image. + /// + /// The preview image URL. + Uri? PreviewImageUrl { get; } + + /// + /// Gets the ID of the previous article to this one. + /// + /// The previous part ID. + int? PreviousPart { get; } + + /// + /// Gets the date and time at which this article was published. + /// + /// The publish timestamp. + DateTimeOffset Published { get; } + + /// + /// Gets the slug of this article. + /// + /// The slug. + string Slug { get; } + + /// + /// Gets the title of this article. + /// + /// The title. + string Title { get; } + + /// + /// Gets the date and time at which this article was updated. + /// + /// The update timestamp, or if this article has not been updated. + DateTimeOffset? Updated { get; } +} diff --git a/OliverBooth/Data/Web/ITutorialFolder.cs b/OliverBooth/Data/Web/ITutorialFolder.cs new file mode 100644 index 0000000..7195c68 --- /dev/null +++ b/OliverBooth/Data/Web/ITutorialFolder.cs @@ -0,0 +1,37 @@ +namespace OliverBooth.Data.Web; + +/// +/// Represents a folder for tutorial articles. +/// +public interface ITutorialFolder +{ + /// + /// Gets the ID of this folder. + /// + /// The ID of the folder. + int Id { get; } + + /// + /// Gets the ID of this folder's parent. + /// + /// The ID of the parent, or if this folder is at the root. + int? Parent { get; } + + /// + /// Gets the URL of the folder's preview image. + /// + /// The preview image URL. + Uri? PreviewImageUrl { get; } + + /// + /// Gets the slug of this folder. + /// + /// The slug. + string Slug { get; } + + /// + /// Gets the title of this folder. + /// + /// The title. + string Title { get; } +} \ No newline at end of file diff --git a/OliverBooth/Data/Web/TutorialArticle.cs b/OliverBooth/Data/Web/TutorialArticle.cs new file mode 100644 index 0000000..0389473 --- /dev/null +++ b/OliverBooth/Data/Web/TutorialArticle.cs @@ -0,0 +1,98 @@ +namespace OliverBooth.Data.Web; + +/// +/// Represents a tutorial article. +/// +internal sealed class TutorialArticle : IEquatable, ITutorialArticle +{ + /// + public string Body { get; private set; } = string.Empty; + + /// + public int Folder { get; private set; } + + /// + public int Id { get; private set; } + + /// + public int? NextPart { get; } + + /// + public Uri? PreviewImageUrl { get; private set; } + + /// + public int? PreviousPart { get; private set; } + + /// + public DateTimeOffset Published { get; private set; } + + /// + public string Slug { get; private set; } = string.Empty; + + /// + public string Title { get; private set; } = string.Empty; + + /// + public DateTimeOffset? Updated { get; private set; } + + /// + /// Returns a value indicating whether two instances of are equal. + /// + /// The first instance of to compare. + /// The second instance of to compare. + /// + /// if and are equal; otherwise, + /// . + /// + public static bool operator ==(TutorialArticle? left, TutorialArticle? right) => Equals(left, right); + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance of to compare. + /// The second instance of to compare. + /// + /// if and are not equal; otherwise, + /// . + /// + public static bool operator !=(TutorialArticle? left, TutorialArticle? right) => !(left == right); + + /// + /// Returns a value indicating whether this instance of is equal to another + /// instance. + /// + /// An instance to compare with this instance. + /// + /// if is equal to this instance; otherwise, + /// . + /// + public bool Equals(TutorialArticle? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id); + } + + /// + /// Returns a value indicating whether this instance is equal to a specified object. + /// + /// An object to compare with this instance. + /// + /// if is an instance of and + /// equals the value of this instance; otherwise, . + /// + public override bool Equals(object? obj) + { + return ReferenceEquals(this, obj) || obj is TutorialArticle other && Equals(other); + } + + /// + /// Gets the hash code for this instance. + /// + /// The hash code. + public override int GetHashCode() + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + return Id; + } +} diff --git a/OliverBooth/Data/Web/TutorialFolder.cs b/OliverBooth/Data/Web/TutorialFolder.cs new file mode 100644 index 0000000..372b960 --- /dev/null +++ b/OliverBooth/Data/Web/TutorialFolder.cs @@ -0,0 +1,83 @@ +namespace OliverBooth.Data.Web; + +/// +/// Represents a folder for tutorial articles. +/// +internal sealed class TutorialFolder : IEquatable, ITutorialFolder +{ + /// + public int Id { get; private set; } + + /// + public int? Parent { get; private set; } + + /// + public Uri? PreviewImageUrl { get; private set; } + + /// + public string Slug { get; private set; } = string.Empty; + + /// + public string Title { get; private set; } = string.Empty; + + /// + /// Returns a value indicating whether two instances of are equal. + /// + /// The first instance of to compare. + /// The second instance of to compare. + /// + /// if and are equal; otherwise, + /// . + /// + public static bool operator ==(TutorialFolder? left, TutorialFolder? right) => Equals(left, right); + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance of to compare. + /// The second instance of to compare. + /// + /// if and are not equal; otherwise, + /// . + /// + public static bool operator !=(TutorialFolder? left, TutorialFolder? right) => !(left == right); + + /// + /// Returns a value indicating whether this instance of is equal to another + /// instance. + /// + /// An instance to compare with this instance. + /// + /// if is equal to this instance; otherwise, + /// . + /// + public bool Equals(TutorialFolder? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id); + } + + /// + /// Returns a value indicating whether this instance is equal to a specified object. + /// + /// An object to compare with this instance. + /// + /// if is an instance of and + /// equals the value of this instance; otherwise, . + /// + public override bool Equals(object? obj) + { + return ReferenceEquals(this, obj) || obj is TutorialFolder other && Equals(other); + } + + /// + /// Gets the hash code for this instance. + /// + /// The hash code. + public override int GetHashCode() + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + return Id; + } +} \ No newline at end of file diff --git a/OliverBooth/Data/Web/WebContext.cs b/OliverBooth/Data/Web/WebContext.cs index d5b74e4..8d43e43 100644 --- a/OliverBooth/Data/Web/WebContext.cs +++ b/OliverBooth/Data/Web/WebContext.cs @@ -55,6 +55,18 @@ internal sealed class WebContext : DbContext /// The collection of templates. public DbSet