From 32b381de4c474c18685bcdc8eaa1389f2aa91be6 Mon Sep 17 00:00:00 2001 From: Jeremiah Cooper Date: Fri, 10 Nov 2023 12:04:42 -0500 Subject: [PATCH] Add Bluesky Url to Members Profile (#1177) * Add Bluesky Url to Members Profile * Update tests * Add Bluesky Url to UserDetails Page --------- Co-authored-by: Steve Smith --- src/DevBetterWeb.Core/Entities/Member.cs | 7 +- .../Data/Config/MemberConfig.cs | 1 + .../20231006005334_AddBlueskyUrl.Designer.cs | 949 ++++++++++++++++++ .../20231006005334_AddBlueskyUrl.cs | 85 ++ .../Migrations/AppDbContextModelSnapshot.cs | 12 +- src/DevBetterWeb.Web/Models/MemberLinksDTO.cs | 2 + .../Pages/Admin/User.cshtml.cs | 2 +- .../Pages/User/Details.cshtml | 7 + src/DevBetterWeb.Web/Pages/User/Index.cshtml | 3 +- .../Pages/User/MyProfile/Index.cshtml | 4 +- .../Pages/User/MyProfile/Links.cshtml | 4 + .../Pages/User/MyProfile/Links.cshtml.cs | 2 +- .../Pages/User/UserDetailsViewModel.cs | 2 + .../Pages/User/UserLinksUpdateModel.cs | 2 + .../Pages/User/UserProfileViewModel.cs | 3 + .../Web/MemberLinksDtoFromMemberEntity.cs | 4 +- .../Entities/MemberTests/MemberUpdateLinks.cs | 6 + 17 files changed, 1083 insertions(+), 12 deletions(-) create mode 100644 src/DevBetterWeb.Infrastructure/Data/Migrations/20231006005334_AddBlueskyUrl.Designer.cs create mode 100644 src/DevBetterWeb.Infrastructure/Data/Migrations/20231006005334_AddBlueskyUrl.cs diff --git a/src/DevBetterWeb.Core/Entities/Member.cs b/src/DevBetterWeb.Core/Entities/Member.cs index 53ea73975..24e6274f9 100644 --- a/src/DevBetterWeb.Core/Entities/Member.cs +++ b/src/DevBetterWeb.Core/Entities/Member.cs @@ -59,6 +59,7 @@ internal Member(string userId, string firstName, string lastName) public string? TwitchUrl { get; private set; } public string? YouTubeUrl { get; private set; } public string? TwitterUrl { get; private set; } + public string? BlueskyUrl { get; private set; } public string? CodinGameUrl { get; private set; } public string? DiscordUsername { get; private set; } public string? MastodonUrl { get; private set; } // added and deployed 5 Jan 2022 but site broke @@ -225,19 +226,21 @@ public void UpdatePEInfo(string? peFriendCode, string? peUsername, bool isEvent } public void UpdateLinks(string? blogUrl, - string? codinGameUrl, + string? codinGameUrl, string? gitHubUrl, string? linkedInUrl, string? otherUrl, string? twitchUrl, string? youtubeUrl, string? twitterUrl, + string? blueskyUrl, string? mastodonUrl, bool isEvent = true) { HashSet<(PropertyInfo prop, string? paramValue)> propInfosAndParamValues = new() { (GetProperty(nameof(BlogUrl)), blogUrl), + (GetProperty(nameof(BlueskyUrl)), blueskyUrl), (GetProperty(nameof(CodinGameUrl)), codinGameUrl), (GetProperty(nameof(GitHubUrl)), gitHubUrl), (GetProperty(nameof(LinkedInUrl)), linkedInUrl), @@ -245,7 +248,7 @@ public void UpdateLinks(string? blogUrl, (GetProperty(nameof(OtherUrl)), otherUrl), (GetProperty(nameof(TwitchUrl)), twitchUrl), (GetProperty(nameof(TwitterUrl)), twitterUrl), - (GetProperty(nameof(YouTubeUrl)), youtubeUrl), + (GetProperty(nameof(YouTubeUrl)), youtubeUrl) }; var valuesChanged = propInfosAndParamValues diff --git a/src/DevBetterWeb.Infrastructure/Data/Config/MemberConfig.cs b/src/DevBetterWeb.Infrastructure/Data/Config/MemberConfig.cs index 63e306de7..5f0a1469d 100644 --- a/src/DevBetterWeb.Infrastructure/Data/Config/MemberConfig.cs +++ b/src/DevBetterWeb.Infrastructure/Data/Config/MemberConfig.cs @@ -19,6 +19,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.TwitchUrl).HasMaxLength(DataConfigConstants.URL_COLUMN_WIDTH); builder.Property(x => x.MastodonUrl).HasMaxLength(DataConfigConstants.URL_COLUMN_WIDTH); builder.Property(x => x.TwitterUrl).HasMaxLength(DataConfigConstants.URL_COLUMN_WIDTH); + builder.Property(x => x.BlueskyUrl).HasMaxLength(DataConfigConstants.URL_COLUMN_WIDTH); builder.Property(x => x.YouTubeUrl).HasMaxLength(DataConfigConstants.URL_COLUMN_WIDTH); builder.Property(x => x.FirstName).HasMaxLength(DataConfigConstants.NAME_COLUMN_WIDTH); builder.Property(x => x.LastName).HasMaxLength(DataConfigConstants.NAME_COLUMN_WIDTH); diff --git a/src/DevBetterWeb.Infrastructure/Data/Migrations/20231006005334_AddBlueskyUrl.Designer.cs b/src/DevBetterWeb.Infrastructure/Data/Migrations/20231006005334_AddBlueskyUrl.Designer.cs new file mode 100644 index 000000000..6f8d939ee --- /dev/null +++ b/src/DevBetterWeb.Infrastructure/Data/Migrations/20231006005334_AddBlueskyUrl.Designer.cs @@ -0,0 +1,949 @@ +// +using System; +using DevBetterWeb.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DevBetterWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231006005334_AddBlueskyUrl")] + partial class AddBlueskyUrl + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("BookMember", b => + { + b.Property("BooksReadId") + .HasColumnType("int"); + + b.Property("MembersWhoHaveReadId") + .HasColumnType("int"); + + b.HasKey("BooksReadId", "MembersWhoHaveReadId"); + + b.HasIndex("MembersWhoHaveReadId"); + + b.ToTable("BookMember"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.ArchiveVideo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AnimatedThumbnailUri") + .HasColumnType("nvarchar(max)"); + + b.Property("DateCreated") + .HasColumnType("datetimeoffset"); + + b.Property("DateUploaded") + .HasColumnType("datetimeoffset"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("VideoId") + .HasColumnType("nvarchar(max)"); + + b.Property("VideoUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Views") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("ArchiveVideos"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.BillingActivity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("MemberId") + .HasMaxLength(500) + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MemberId"); + + b.ToTable("BillingActivities"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("BookCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("Details") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("MemberWhoUploadId") + .HasColumnType("int"); + + b.Property("PurchaseUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Title") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("BookCategoryId"); + + b.HasIndex("MemberWhoUploadId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.BookCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Title") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("BookCategories"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.CoachingSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("StartAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("CoachingSessions", (string)null); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.DailyCheck", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("TasksCompleted") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("DailyChecks"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Invitation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateOfLastAdminPing") + .HasColumnType("datetime2"); + + b.Property("DateOfUserPing") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("InviteCode") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("PaymentHandlerSubscriptionId") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("Invitations"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Member", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AboutInfo") + .HasColumnType("nvarchar(max)"); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("BlogUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("BlueskyUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CityLatitude") + .HasColumnType("decimal(18,2)"); + + b.Property("CityLongitude") + .HasColumnType("decimal(18,2)"); + + b.Property("CodinGameUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DiscordUsername") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("GitHubUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LinkedInUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("MastodonUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OtherUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PEFriendCode") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PEUsername") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TwitchUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TwitterUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("YouTubeUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Members"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.MemberSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("MemberId") + .HasColumnType("int"); + + b.Property("MemberSubscriptionPlanId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MemberId"); + + b.ToTable("MemberSubscriptions"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.MemberSubscriptionPlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.HasKey("Id"); + + b.ToTable("MemberSubscriptionPlan"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.MemberVideoProgress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ArchiveVideoId") + .HasColumnType("int"); + + b.Property("CurrentDuration") + .HasColumnType("int"); + + b.Property("MemberId") + .HasColumnType("int"); + + b.Property("VideoWatchedStatus") + .IsRequired() + .HasMaxLength(1) + .HasColumnType("nvarchar(1)") + .HasColumnName("VideoWatchedStatus"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveVideoId"); + + b.HasIndex("MemberId"); + + b.ToTable("MembersVideosProgress", (string)null); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ArchiveVideoId") + .HasColumnType("int"); + + b.Property("CoachingSessionId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MemberId") + .HasColumnType("int"); + + b.Property("QuestionText") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Votes") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CoachingSessionId"); + + b.HasIndex("MemberId"); + + b.ToTable("Questions", (string)null); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.QuestionVote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("MemberId") + .HasColumnType("int"); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MemberId"); + + b.HasIndex("QuestionId"); + + b.ToTable("QuestionVote"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.VideoComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Body") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("MemberId") + .HasColumnType("int"); + + b.Property("ParentCommentId") + .HasColumnType("int"); + + b.Property("VideoId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MemberId"); + + b.HasIndex("ParentCommentId"); + + b.HasIndex("VideoId"); + + b.ToTable("VideoComments"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.ValueObjects.MemberFavoriteArchiveVideo", b => + { + b.Property("MemberId") + .HasColumnType("int"); + + b.Property("ArchiveVideoId") + .HasColumnType("int"); + + b.HasKey("MemberId", "ArchiveVideoId"); + + b.HasIndex("ArchiveVideoId"); + + b.ToTable("MemberFavoriteArchiveVideos", (string)null); + }); + + modelBuilder.Entity("DevBetterWeb.Infrastructure.Identity.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ApplicationUser"); + }); + + modelBuilder.Entity("BookMember", b => + { + b.HasOne("DevBetterWeb.Core.Entities.Book", null) + .WithMany() + .HasForeignKey("BooksReadId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DevBetterWeb.Core.Entities.Member", null) + .WithMany() + .HasForeignKey("MembersWhoHaveReadId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.BillingActivity", b => + { + b.HasOne("DevBetterWeb.Core.Entities.Member", null) + .WithMany("BillingActivities") + .HasForeignKey("MemberId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("DevBetterWeb.Core.ValueObjects.BillingDetails", "Details", b1 => + { + b1.Property("BillingActivityId") + .HasColumnType("int"); + + b1.Property("ActionVerbPastTense") + .HasMaxLength(100) + .HasColumnType("int"); + + b1.Property("Amount") + .ValueGeneratedOnAdd() + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + + b1.Property("BillingPeriod") + .HasMaxLength(100) + .HasColumnType("int"); + + b1.Property("Date") + .HasColumnType("datetime2"); + + b1.Property("MemberName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("SubscriptionPlanName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.HasKey("BillingActivityId"); + + b1.ToTable("BillingActivities"); + + b1.WithOwner() + .HasForeignKey("BillingActivityId"); + }); + + b.Navigation("Details") + .IsRequired(); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Book", b => + { + b.HasOne("DevBetterWeb.Core.Entities.BookCategory", "BookCategory") + .WithMany("Books") + .HasForeignKey("BookCategoryId"); + + b.HasOne("DevBetterWeb.Core.Entities.Member", "MemberWhoUpload") + .WithMany("UploadedBooks") + .HasForeignKey("MemberWhoUploadId"); + + b.Navigation("BookCategory"); + + b.Navigation("MemberWhoUpload"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Member", b => + { + b.OwnsOne("DevBetterWeb.Core.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("MemberId") + .HasColumnType("int"); + + b1.Property("City") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasDefaultValue(""); + + b1.Property("Country") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasDefaultValue(""); + + b1.Property("PostalCode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(12) + .HasColumnType("nvarchar(12)") + .HasDefaultValue(""); + + b1.Property("State") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasDefaultValue(""); + + b1.Property("Street") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b1.HasKey("MemberId"); + + b1.ToTable("Members"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.OwnsOne("DevBetterWeb.Core.ValueObjects.Birthday", "Birthday", b1 => + { + b1.Property("MemberId") + .HasColumnType("int"); + + b1.Property("Day") + .HasColumnType("int"); + + b1.Property("Month") + .HasColumnType("int"); + + b1.HasKey("MemberId"); + + b1.ToTable("Members"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.OwnsOne("DevBetterWeb.Core.ValueObjects.Geolocation", "CityLocation", b1 => + { + b1.Property("MemberId") + .HasColumnType("int"); + + b1.Property("Latitude") + .HasColumnType("decimal(18,4)"); + + b1.Property("Longitude") + .HasColumnType("decimal(18,4)"); + + b1.HasKey("MemberId"); + + b1.ToTable("Members"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.Navigation("Birthday"); + + b.Navigation("CityLocation"); + + b.Navigation("ShippingAddress"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.MemberSubscription", b => + { + b.HasOne("DevBetterWeb.Core.Entities.Member", null) + .WithMany("MemberSubscriptions") + .HasForeignKey("MemberId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("DevBetterWeb.Core.ValueObjects.DateTimeRange", "Dates", b1 => + { + b1.Property("MemberSubscriptionId") + .HasColumnType("int"); + + b1.Property("EndDate") + .HasColumnType("datetime2"); + + b1.Property("StartDate") + .HasColumnType("datetime2"); + + b1.HasKey("MemberSubscriptionId"); + + b1.ToTable("MemberSubscriptionDates", (string)null); + + b1.WithOwner() + .HasForeignKey("MemberSubscriptionId"); + }); + + b.Navigation("Dates") + .IsRequired(); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.MemberSubscriptionPlan", b => + { + b.OwnsOne("DevBetterWeb.Core.ValueObjects.MemberSubscriptionPlanDetails", "Details", b1 => + { + b1.Property("MemberSubscriptionPlanId") + .HasColumnType("int"); + + b1.Property("BillingPeriod") + .HasMaxLength(100) + .HasColumnType("int"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PricePerBillingPeriod") + .HasMaxLength(100) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("MemberSubscriptionPlanId"); + + b1.ToTable("MemberSubscriptionPlan"); + + b1.WithOwner() + .HasForeignKey("MemberSubscriptionPlanId"); + }); + + b.Navigation("Details") + .IsRequired(); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.MemberVideoProgress", b => + { + b.HasOne("DevBetterWeb.Core.Entities.ArchiveVideo", "Video") + .WithMany("MembersVideoProgress") + .HasForeignKey("ArchiveVideoId") + .IsRequired(); + + b.HasOne("DevBetterWeb.Core.Entities.Member", "Member") + .WithMany("MemberVideosProgress") + .HasForeignKey("MemberId") + .IsRequired(); + + b.Navigation("Member"); + + b.Navigation("Video"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Question", b => + { + b.HasOne("DevBetterWeb.Core.Entities.CoachingSession", "CoachingSession") + .WithMany("Questions") + .HasForeignKey("CoachingSessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DevBetterWeb.Core.Entities.Member", "MemberWhoCreate") + .WithMany("Questions") + .HasForeignKey("MemberId") + .IsRequired(); + + b.Navigation("CoachingSession"); + + b.Navigation("MemberWhoCreate"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.QuestionVote", b => + { + b.HasOne("DevBetterWeb.Core.Entities.Member", "Member") + .WithMany("QuestionVotes") + .HasForeignKey("MemberId") + .IsRequired(); + + b.HasOne("DevBetterWeb.Core.Entities.Question", "Question") + .WithMany("QuestionVotes") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Member"); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.VideoComment", b => + { + b.HasOne("DevBetterWeb.Core.Entities.Member", "MemberWhoCreate") + .WithMany("VideosComments") + .HasForeignKey("MemberId") + .IsRequired(); + + b.HasOne("DevBetterWeb.Core.Entities.VideoComment", "ParentComment") + .WithMany("Replies") + .HasForeignKey("ParentCommentId"); + + b.HasOne("DevBetterWeb.Core.Entities.ArchiveVideo", "Video") + .WithMany("Comments") + .HasForeignKey("VideoId") + .IsRequired(); + + b.Navigation("MemberWhoCreate"); + + b.Navigation("ParentComment"); + + b.Navigation("Video"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.ValueObjects.MemberFavoriteArchiveVideo", b => + { + b.HasOne("DevBetterWeb.Core.Entities.ArchiveVideo", null) + .WithMany("MemberFavorites") + .HasForeignKey("ArchiveVideoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DevBetterWeb.Core.Entities.Member", null) + .WithMany("FavoriteArchiveVideos") + .HasForeignKey("MemberId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.ArchiveVideo", b => + { + b.Navigation("Comments"); + + b.Navigation("MemberFavorites"); + + b.Navigation("MembersVideoProgress"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.BookCategory", b => + { + b.Navigation("Books"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.CoachingSession", b => + { + b.Navigation("Questions"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Member", b => + { + b.Navigation("BillingActivities"); + + b.Navigation("FavoriteArchiveVideos"); + + b.Navigation("MemberSubscriptions"); + + b.Navigation("MemberVideosProgress"); + + b.Navigation("QuestionVotes"); + + b.Navigation("Questions"); + + b.Navigation("UploadedBooks"); + + b.Navigation("VideosComments"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.Question", b => + { + b.Navigation("QuestionVotes"); + }); + + modelBuilder.Entity("DevBetterWeb.Core.Entities.VideoComment", b => + { + b.Navigation("Replies"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/DevBetterWeb.Infrastructure/Data/Migrations/20231006005334_AddBlueskyUrl.cs b/src/DevBetterWeb.Infrastructure/Data/Migrations/20231006005334_AddBlueskyUrl.cs new file mode 100644 index 000000000..d517e5c3c --- /dev/null +++ b/src/DevBetterWeb.Infrastructure/Data/Migrations/20231006005334_AddBlueskyUrl.cs @@ -0,0 +1,85 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DevBetterWeb.Infrastructure.Data.Migrations +{ + /// + public partial class AddBlueskyUrl : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "CityLocation_Longitude", + table: "Members", + type: "decimal(18,4)", + nullable: true, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CityLocation_Latitude", + table: "Members", + type: "decimal(18,4)", + nullable: true, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "BlueskyUrl", + table: "Members", + type: "nvarchar(200)", + maxLength: 200, + nullable: true); + + migrationBuilder.AlterColumn( + name: "Details_Amount", + table: "BillingActivities", + type: "decimal(18,4)", + nullable: false, + defaultValue: 0m, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)", + oldDefaultValue: 0m); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BlueskyUrl", + table: "Members"); + + migrationBuilder.AlterColumn( + name: "CityLocation_Longitude", + table: "Members", + type: "decimal(18,2)", + nullable: true, + oldClrType: typeof(decimal), + oldType: "decimal(18,4)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CityLocation_Latitude", + table: "Members", + type: "decimal(18,2)", + nullable: true, + oldClrType: typeof(decimal), + oldType: "decimal(18,4)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Details_Amount", + table: "BillingActivities", + type: "decimal(18,2)", + nullable: false, + defaultValue: 0m, + oldClrType: typeof(decimal), + oldType: "decimal(18,4)", + oldDefaultValue: 0m); + } + } +} diff --git a/src/DevBetterWeb.Infrastructure/Data/Migrations/AppDbContextModelSnapshot.cs b/src/DevBetterWeb.Infrastructure/Data/Migrations/AppDbContextModelSnapshot.cs index 1cd618a2f..7598c978e 100644 --- a/src/DevBetterWeb.Infrastructure/Data/Migrations/AppDbContextModelSnapshot.cs +++ b/src/DevBetterWeb.Infrastructure/Data/Migrations/AppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("ProductVersion", "7.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -254,6 +254,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("nvarchar(200)"); + b.Property("BlueskyUrl") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + b.Property("CityLatitude") .HasColumnType("decimal(18,2)"); @@ -593,7 +597,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Property("Amount") .ValueGeneratedOnAdd() - .HasColumnType("decimal(18,2)") + .HasColumnType("decimal(18,4)") .HasDefaultValue(0m); b1.Property("BillingPeriod") @@ -715,10 +719,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("int"); b1.Property("Latitude") - .HasColumnType("decimal(18,2)"); + .HasColumnType("decimal(18,4)"); b1.Property("Longitude") - .HasColumnType("decimal(18,2)"); + .HasColumnType("decimal(18,4)"); b1.HasKey("MemberId"); diff --git a/src/DevBetterWeb.Web/Models/MemberLinksDTO.cs b/src/DevBetterWeb.Web/Models/MemberLinksDTO.cs index df0ed9c0b..2e4b1763b 100644 --- a/src/DevBetterWeb.Web/Models/MemberLinksDTO.cs +++ b/src/DevBetterWeb.Web/Models/MemberLinksDTO.cs @@ -14,6 +14,7 @@ public class MemberLinksDTO public string? TwitchUrl { get; private set; } public string? YouTubeUrl { get; private set; } public string? TwitterUrl { get; private set; } + public string? BlueskyUrl { get; private set; } public string? PEUsername { get; private set; } public string? PEBadgeURL { get; private set; } public string? Address { get; private set; } @@ -33,6 +34,7 @@ public static MemberLinksDTO FromMemberEntity(Member member) TwitchUrl = member.TwitchUrl, YouTubeUrl = member.YouTubeUrl, TwitterUrl = member.TwitterUrl, + BlueskyUrl = member.BlueskyUrl, UserId = member.UserId, PEUsername = member.PEUsername, PEBadgeURL = $"https://projecteuler.net/profile/{member.PEUsername}.png", diff --git a/src/DevBetterWeb.Web/Pages/Admin/User.cshtml.cs b/src/DevBetterWeb.Web/Pages/Admin/User.cshtml.cs index c459ca159..5fc254169 100644 --- a/src/DevBetterWeb.Web/Pages/Admin/User.cshtml.cs +++ b/src/DevBetterWeb.Web/Pages/Admin/User.cshtml.cs @@ -293,7 +293,7 @@ public async Task OnPostUpdateLinksAsync(string userId) if (member is null) throw new MemberNotFoundException(userId); member.UpdateLinks(UserLinksUpdateModel.BlogUrl, UserLinksUpdateModel.CodinGameUrl, UserLinksUpdateModel.GithubUrl, UserLinksUpdateModel.LinkedInUrl, - UserLinksUpdateModel.OtherUrl, UserLinksUpdateModel.TwitchUrl, UserLinksUpdateModel.YouTubeUrl, UserLinksUpdateModel.TwitterUrl, UserLinksUpdateModel.MastodonUrl); + UserLinksUpdateModel.OtherUrl, UserLinksUpdateModel.TwitchUrl, UserLinksUpdateModel.YouTubeUrl, UserLinksUpdateModel.TwitterUrl, UserLinksUpdateModel.BlueskyUrl, UserLinksUpdateModel.MastodonUrl); await _memberRepository.UpdateAsync(member); diff --git a/src/DevBetterWeb.Web/Pages/User/Details.cshtml b/src/DevBetterWeb.Web/Pages/User/Details.cshtml index e0e7b3c1a..e66b3fe96 100644 --- a/src/DevBetterWeb.Web/Pages/User/Details.cshtml +++ b/src/DevBetterWeb.Web/Pages/User/Details.cshtml @@ -126,6 +126,13 @@ } + @if (!string.IsNullOrEmpty(Model.UserDetailsViewModel.BlueskyUrl)) + { +
+ Bluesky +
+ } + @if (!string.IsNullOrEmpty(Model.UserDetailsViewModel.YouTubeUrl)) {
diff --git a/src/DevBetterWeb.Web/Pages/User/Index.cshtml b/src/DevBetterWeb.Web/Pages/User/Index.cshtml index 0756fc73c..c6d29aeae 100644 --- a/src/DevBetterWeb.Web/Pages/User/Index.cshtml +++ b/src/DevBetterWeb.Web/Pages/User/Index.cshtml @@ -61,7 +61,8 @@ - + + diff --git a/src/DevBetterWeb.Web/Pages/User/MyProfile/Index.cshtml b/src/DevBetterWeb.Web/Pages/User/MyProfile/Index.cshtml index 0b5d6c046..690ffbba5 100644 --- a/src/DevBetterWeb.Web/Pages/User/MyProfile/Index.cshtml +++ b/src/DevBetterWeb.Web/Pages/User/MyProfile/Index.cshtml @@ -141,7 +141,9 @@

@Model.UserProfileViewModel.MastodonUrl

-

@Model.UserProfileViewModel.TwitterUrl

+

@Model.UserProfileViewModel.TwitterUrl

+ +

@Model.UserProfileViewModel.BlueskyUrl

@Model.UserProfileViewModel.TwitchUrl

diff --git a/src/DevBetterWeb.Web/Pages/User/MyProfile/Links.cshtml b/src/DevBetterWeb.Web/Pages/User/MyProfile/Links.cshtml index b3fd7537b..43bfcc4bb 100644 --- a/src/DevBetterWeb.Web/Pages/User/MyProfile/Links.cshtml +++ b/src/DevBetterWeb.Web/Pages/User/MyProfile/Links.cshtml @@ -39,6 +39,10 @@
+ + + +
diff --git a/src/DevBetterWeb.Web/Pages/User/MyProfile/Links.cshtml.cs b/src/DevBetterWeb.Web/Pages/User/MyProfile/Links.cshtml.cs index a5d1fb037..fd9573892 100644 --- a/src/DevBetterWeb.Web/Pages/User/MyProfile/Links.cshtml.cs +++ b/src/DevBetterWeb.Web/Pages/User/MyProfile/Links.cshtml.cs @@ -64,7 +64,7 @@ public async Task OnPost() if (member is null) throw new MemberNotFoundException(applicationUser.Id); member.UpdateLinks(UserLinksUpdateModel.BlogUrl, UserLinksUpdateModel.CodinGameUrl, UserLinksUpdateModel.GithubUrl, UserLinksUpdateModel.LinkedInUrl, - UserLinksUpdateModel.OtherUrl, UserLinksUpdateModel.TwitchUrl, UserLinksUpdateModel.YouTubeUrl, UserLinksUpdateModel.TwitterUrl, UserLinksUpdateModel.MastodonUrl); + UserLinksUpdateModel.OtherUrl, UserLinksUpdateModel.TwitchUrl, UserLinksUpdateModel.YouTubeUrl, UserLinksUpdateModel.TwitterUrl, UserLinksUpdateModel.BlueskyUrl,UserLinksUpdateModel.MastodonUrl); await _memberRepository.UpdateAsync(member); } diff --git a/src/DevBetterWeb.Web/Pages/User/UserDetailsViewModel.cs b/src/DevBetterWeb.Web/Pages/User/UserDetailsViewModel.cs index ae116ad88..bcad79527 100644 --- a/src/DevBetterWeb.Web/Pages/User/UserDetailsViewModel.cs +++ b/src/DevBetterWeb.Web/Pages/User/UserDetailsViewModel.cs @@ -17,6 +17,7 @@ public class UserDetailsViewModel public string? OtherUrl { get; set; } public string? TwitchUrl { get; set; } public string? TwitterUrl { get; set; } + public string? BlueskyUrl { get; set; } public string? YouTubeUrl { get; set; } public string? Name { get; set; } @@ -43,6 +44,7 @@ public UserDetailsViewModel(Member member) MastodonUrl = member.MastodonUrl; TwitchUrl = member.TwitchUrl; TwitterUrl = member.TwitterUrl; + BlueskyUrl = member.BlueskyUrl; YouTubeUrl = member.YouTubeUrl; if (!(string.IsNullOrEmpty(YouTubeUrl)) && !(YouTubeUrl.Contains("?"))) diff --git a/src/DevBetterWeb.Web/Pages/User/UserLinksUpdateModel.cs b/src/DevBetterWeb.Web/Pages/User/UserLinksUpdateModel.cs index 7c8f15401..109669b91 100644 --- a/src/DevBetterWeb.Web/Pages/User/UserLinksUpdateModel.cs +++ b/src/DevBetterWeb.Web/Pages/User/UserLinksUpdateModel.cs @@ -9,6 +9,8 @@ public class UserLinksUpdateModel public string? LinkedInUrl { get; set; } [ValidUrlContainingString("Twitter")] public string? TwitterUrl { get; set; } + [ValidUrlContainingString("bsky")] + public string? BlueskyUrl { get; set; } [ValidUrlContainingString("GitHub")] public string? GithubUrl { get; set; } [ValidUrl] diff --git a/src/DevBetterWeb.Web/Pages/User/UserProfileViewModel.cs b/src/DevBetterWeb.Web/Pages/User/UserProfileViewModel.cs index c642595a5..8133df2d8 100644 --- a/src/DevBetterWeb.Web/Pages/User/UserProfileViewModel.cs +++ b/src/DevBetterWeb.Web/Pages/User/UserProfileViewModel.cs @@ -29,6 +29,8 @@ public class UserProfileViewModel [ValidUrl] public string? TwitterUrl { get; set; } [ValidUrl] + public string? BlueskyUrl { get; set; } + [ValidUrl] public string? TwitchUrl { get; set; } [ValidUrl] public string? YouTubeUrl { get; set; } @@ -69,6 +71,7 @@ public UserProfileViewModel(Member member) PEUsername = member.PEUsername ?? valueToUseIfNull; TwitchUrl = member.TwitchUrl ?? valueToUseIfNull; TwitterUrl = member.TwitterUrl ?? valueToUseIfNull; + BlueskyUrl = member.BlueskyUrl ?? valueToUseIfNull; YouTubeUrl = member.YouTubeUrl ?? valueToUseIfNull; } } diff --git a/tests/DevBetterWeb.Tests/Integration/Web/MemberLinksDtoFromMemberEntity.cs b/tests/DevBetterWeb.Tests/Integration/Web/MemberLinksDtoFromMemberEntity.cs index 00fb761aa..7e93fe91f 100644 --- a/tests/DevBetterWeb.Tests/Integration/Web/MemberLinksDtoFromMemberEntity.cs +++ b/tests/DevBetterWeb.Tests/Integration/Web/MemberLinksDtoFromMemberEntity.cs @@ -12,7 +12,7 @@ public void ReturnsInputYouTubeUrlIfContainsQuestionMark() string youtubeInput = "https://www.youtube.com/ardalis?"; - member.UpdateLinks(null, null, null, null, null, null, youtubeInput, null, null); + member.UpdateLinks(null, null, null, null, null, null, youtubeInput, null, null, null); MemberLinksDTO dto = MemberLinksDTO.FromMemberEntity(member); @@ -28,7 +28,7 @@ public void ReturnsAlteredYouTubeUrlIfContainsNoQuestionMark() string youtubeInput = "https://www.youtube.com/ardalis"; - member.UpdateLinks(null, null, null, null, null, null, youtubeInput, null, null); + member.UpdateLinks(null, null, null, null, null, null, youtubeInput, null, null, null); MemberLinksDTO dto = MemberLinksDTO.FromMemberEntity(member); diff --git a/tests/DevBetterWeb.UnitTests/Core/Entities/MemberTests/MemberUpdateLinks.cs b/tests/DevBetterWeb.UnitTests/Core/Entities/MemberTests/MemberUpdateLinks.cs index 98482430e..3039aca41 100644 --- a/tests/DevBetterWeb.UnitTests/Core/Entities/MemberTests/MemberUpdateLinks.cs +++ b/tests/DevBetterWeb.UnitTests/Core/Entities/MemberTests/MemberUpdateLinks.cs @@ -17,6 +17,7 @@ public class MemberUpdateLinks private string _initialTwitchUrl = ""; private string _initialYouTubeUrl = ""; private string _initialTwitterUrl = ""; + private string _initialBlueskyUrl = ""; private Member GetMemberWithDefaultLinks() { @@ -31,6 +32,7 @@ private Member GetMemberWithDefaultLinks() _initialTwitchUrl, _initialYouTubeUrl, _initialTwitterUrl, + _initialBlueskyUrl, _initialMastodonUrl); member.Events.Clear(); @@ -51,6 +53,7 @@ public void SetsBlogLink() _initialTwitchUrl, _initialYouTubeUrl, _initialTwitterUrl, + _initialBlueskyUrl, _initialMastodonUrl); Assert.Equal(newLink, member.BlogUrl); @@ -70,6 +73,7 @@ public void RecordsEventIfLinksChanges() _initialTwitchUrl, _initialYouTubeUrl, _initialTwitterUrl, + _initialBlueskyUrl, _initialMastodonUrl); var eventCreated = (MemberUpdatedEvent)member.Events.First(); @@ -89,6 +93,7 @@ public void RecordsNoEventIfLinksDoesNotChange() _initialTwitchUrl, _initialYouTubeUrl, _initialTwitterUrl, + _initialBlueskyUrl, _initialMastodonUrl); Assert.Empty(member.Events); @@ -110,6 +115,7 @@ public void RecordsEventWithAppendedDetailsIfOtherThingsChanged() _initialTwitchUrl, _initialYouTubeUrl, _initialTwitterUrl, + _initialBlueskyUrl, _initialMastodonUrl); var eventCreated = (MemberUpdatedEvent)member.Events.First();