From 71d6513bd62000cd738f54ed26eeddf95660c930 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 16 Jan 2024 13:10:05 +0800 Subject: [PATCH] move to file scoped namespaces --- .../Admin/AdminServices.cs | 137 ++- .../Admin/DummyTypesService.cs | 17 +- .../Admin/EmailServices.cs | 109 +- .../Admin/ImportServices.cs | 425 ++++--- .../Admin/NotificationServices.Utils.cs | 227 ++-- .../Admin/NotificationServices.cs | 386 +++--- .../AppFeatureFlags.cs | 11 +- .../Auth/DiscourseAuthProvider.cs | 97 +- .../ClientRoutesService.cs | 83 +- .../CustomAuthUserSession.cs | 100 +- .../DataModel/EmailTemplate.cs | 35 +- .../DataModel/Notification.cs | 45 +- .../DataModel/SubscribePost.cs | 29 +- .../DataModel/SubscriptionPost.cs | 39 +- .../Html/AppScriptMethods.cs | 311 +++-- .../IMarkdownProvider.cs | 83 +- .../ITwitterUpdates.cs | 11 +- .../ImgurExtensions.cs | 147 ++- .../Import/DiscourseSyncServices.cs | 777 ++++++------ .../Messaging/BackgroundAdminServices.cs | 57 +- .../Notifications/EmailProvider.cs | 143 ++- .../Notifications/TwitterUpdates.cs | 159 ++- .../OrganizationServices.cs | 1065 ++++++++--------- .../PostPublicServices.cs | 108 +- TechStacks.ServiceInterface/PostServices.cs | 777 ++++++------ .../PostServicesBase.cs | 397 +++--- .../PostUserServices.cs | 387 +++--- .../PreRenderService.cs | 65 +- .../SessionInfoServices.cs | 45 +- .../SubscriptionServices.cs | 131 +- TechStacks.ServiceInterface/TechExtensions.cs | 125 +- .../TechStackQueries.cs | 51 +- .../TechnologyServices.cs | 129 +- .../TechnologyServicesAdmin.cs | 317 +++-- .../TechnologyStackServices.cs | 533 ++++----- .../TechnologyStackServicesAdmin.cs | 401 ++++--- .../UserFavoriteServices.cs | 203 ++-- .../UserStackServices.cs | 267 ++--- .../Validations/OrganizationValidators.cs | 61 +- .../Validations/PostValidators.cs | 91 +- .../Validations/TechStackValidators.cs | 51 +- .../Validations/TechValidators.cs | 47 +- .../Validations/ValidatorUtils.cs | 171 ++- TechStacks.ServiceModel/Admin.cs | 102 +- TechStacks.ServiceModel/App.cs | 190 ++- TechStacks.ServiceModel/Import.cs | 191 ++- TechStacks.ServiceModel/Organizations.cs | 703 ++++++----- TechStacks.ServiceModel/Posts.cs | 615 +++++----- TechStacks.ServiceModel/Tags.cs | 21 +- TechStacks.ServiceModel/Technologies.cs | 283 +++-- TechStacks.ServiceModel/TechnologyStacks.cs | 365 +++--- .../Types/Organizations.cs | 309 +++-- TechStacks.ServiceModel/Types/PageStats.cs | 31 +- TechStacks.ServiceModel/Types/Posts.cs | 454 ++++--- .../Types/TechnologyStack.cs | 319 +++-- TechStacks.ServiceModel/Types/UserActivity.cs | 103 +- TechStacks.ServiceModel/Users.cs | 427 ++++--- 57 files changed, 6444 insertions(+), 6519 deletions(-) diff --git a/TechStacks.ServiceInterface/Admin/AdminServices.cs b/TechStacks.ServiceInterface/Admin/AdminServices.cs index 21d4b26..2a1403e 100644 --- a/TechStacks.ServiceInterface/Admin/AdminServices.cs +++ b/TechStacks.ServiceInterface/Admin/AdminServices.cs @@ -6,119 +6,118 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface.Admin +namespace TechStacks.ServiceInterface.Admin; + +[ExcludeMetadata] +[Route("/tasks/daily")] +public class DailyTasks : IReturn {} + +public class DailyTasksResponse { - [ExcludeMetadata] - [Route("/tasks/daily")] - public class DailyTasks : IReturn {} + public int TechStackFavCountRowsUpdated { get; set; } + public int TechnologyFavCountRowsUpdated { get; set; } + public int TechStackViewCountRowsUpdated { get; set; } + public int TechnologyViewCountRowsUpdated { get; set; } + + public int UserTechStackCountRowsUpdated { get; set; } + public int UserTechnologyCountRowsUpdated { get; set; } + public int UserCommentCountRowsUpdated { get; set; } + public int UserUpVotesRowsUpdated { get; set; } + public int UserDownVotesRowsUpdated { get; set; } + public int UserReportCountRowsUpdated { get; set; } +} - public class DailyTasksResponse +[Authenticate] +[RequiredRole("Admin")] +public class AdminServices : Service +{ + public object Put(LogoUrlApproval request) { - public int TechStackFavCountRowsUpdated { get; set; } - public int TechnologyFavCountRowsUpdated { get; set; } - public int TechStackViewCountRowsUpdated { get; set; } - public int TechnologyViewCountRowsUpdated { get; set; } - - public int UserTechStackCountRowsUpdated { get; set; } - public int UserTechnologyCountRowsUpdated { get; set; } - public int UserCommentCountRowsUpdated { get; set; } - public int UserUpVotesRowsUpdated { get; set; } - public int UserDownVotesRowsUpdated { get; set; } - public int UserReportCountRowsUpdated { get; set; } + var tech = Db.SingleById(request.TechnologyId); + if (tech == null) + { + throw HttpError.NotFound("Technology not found"); + } + tech.LogoApproved = request.Approved; + Db.Save(tech); + return new LogoUrlApprovalResponse + { + Result = tech + }; } - [Authenticate] - [RequiredRole("Admin")] - public class AdminServices : Service + public object Put(LockTechStack request) { - public object Put(LogoUrlApproval request) + var techStack = Db.SingleById(request.TechnologyStackId); + if (techStack == null) { - var tech = Db.SingleById(request.TechnologyId); - if (tech == null) - { - throw HttpError.NotFound("Technology not found"); - } - tech.LogoApproved = request.Approved; - Db.Save(tech); - return new LogoUrlApprovalResponse - { - Result = tech - }; + throw HttpError.NotFound("TechnologyStack not found"); } - public object Put(LockTechStack request) - { - var techStack = Db.SingleById(request.TechnologyStackId); - if (techStack == null) - { - throw HttpError.NotFound("TechnologyStack not found"); - } - - techStack.IsLocked = request.IsLocked; - Db.Save(techStack); - return new LockStackResponse(); - } + techStack.IsLocked = request.IsLocked; + Db.Save(techStack); + return new LockStackResponse(); + } - public object Put(LockTech request) + public object Put(LockTech request) + { + var tech = Db.SingleById(request.TechnologyId); + if (tech == null) { - var tech = Db.SingleById(request.TechnologyId); - if (tech == null) - { - throw HttpError.NotFound("Technology not found"); - } - - tech.IsLocked = request.IsLocked; - Db.Save(tech); - return new LockTechResponse(); + throw HttpError.NotFound("Technology not found"); } - public object Any(DailyTasks request) + tech.IsLocked = request.IsLocked; + Db.Save(tech); + return new LockTechResponse(); + } + + public object Any(DailyTasks request) + { + return new DailyTasksResponse { - return new DailyTasksResponse - { - TechStackFavCountRowsUpdated = Db.ExecuteSql(@"UPDATE technology_stack SET fav_count=fav.count + TechStackFavCountRowsUpdated = Db.ExecuteSql(@"UPDATE technology_stack SET fav_count=fav.count FROM (SELECT technology_stack_id, Count(*) AS Count FROM user_favorite_technology_stack GROUP BY technology_stack_id) as fav WHERE id = fav.technology_stack_id"), - TechnologyFavCountRowsUpdated = Db.ExecuteSql(@"UPDATE technology SET fav_count=fav.count + TechnologyFavCountRowsUpdated = Db.ExecuteSql(@"UPDATE technology SET fav_count=fav.count FROM (SELECT technology_id, Count(*) AS Count FROM user_favorite_technology GROUP BY technology_id) as fav WHERE id = fav.technology_id"), - TechStackViewCountRowsUpdated = Db.ExecuteSql(@"UPDATE technology_stack SET view_count=v.view_count + TechStackViewCountRowsUpdated = Db.ExecuteSql(@"UPDATE technology_stack SET view_count=v.view_count FROM (SELECT ref_slug, view_count FROM page_stats WHERE ref_type='stack') AS v WHERE v.ref_slug = slug"), - TechnologyViewCountRowsUpdated = Db.ExecuteSql(@"UPDATE technology SET view_count=v.view_count + TechnologyViewCountRowsUpdated = Db.ExecuteSql(@"UPDATE technology SET view_count=v.view_count FROM (SELECT ref_slug, view_count FROM page_stats WHERE ref_type='tech') AS v WHERE v.ref_slug = slug"), - UserTechStackCountRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET tech_stacks_count=t.total + UserTechStackCountRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET tech_stacks_count=t.total FROM (SELECT owner_id, COUNT(*) as total FROM technology_stack GROUP BY owner_id) AS t WHERE t.owner_id::int = id"), - UserTechnologyCountRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET technology_count=t.total + UserTechnologyCountRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET technology_count=t.total FROM (SELECT owner_id, COUNT(*) as total FROM technology GROUP BY owner_id) AS t WHERE t.owner_id::int = id"), - UserCommentCountRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET comments_count=t.total + UserCommentCountRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET comments_count=t.total FROM (SELECT user_id, COUNT(*) as total FROM post_comment GROUP BY user_id) AS t WHERE t.user_id = id"), - UserUpVotesRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET up_votes=t.total + UserUpVotesRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET up_votes=t.total FROM (SELECT user_id, SUM(up_votes) as total FROM post GROUP BY user_id) AS t WHERE t.user_id = id"), - UserDownVotesRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET down_votes=t.total + UserDownVotesRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET down_votes=t.total FROM (SELECT user_id, SUM(down_votes) as total FROM post GROUP BY user_id) AS t WHERE t.user_id = id"), - UserReportCountRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET report_count=t.report_count + UserReportCountRowsUpdated = Db.ExecuteSql(@"UPDATE user_activity SET report_count=t.report_count FROM (SELECT id as user_id, (select count(*) from post_report where user_id = c.id) + (select count(*) from post_comment where user_id = c.id and report_user_ids != null) as report_count FROM custom_user_auth c) t WHERE t.user_id = id") - }; - } + }; } -} +} \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Admin/DummyTypesService.cs b/TechStacks.ServiceInterface/Admin/DummyTypesService.cs index 09f35fd..2e2c972 100644 --- a/TechStacks.ServiceInterface/Admin/DummyTypesService.cs +++ b/TechStacks.ServiceInterface/Admin/DummyTypesService.cs @@ -2,15 +2,14 @@ using ServiceStack; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface.Admin +namespace TechStacks.ServiceInterface.Admin; + +public class DummyTypes { - public class DummyTypes - { - public List Post { get; set; } - } + public List Post { get; set; } +} - public class DummyTypesService : Service - { - public object Any(DummyTypes request) => request; - } +public class DummyTypesService : Service +{ + public object Any(DummyTypes request) => request; } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Admin/EmailServices.cs b/TechStacks.ServiceInterface/Admin/EmailServices.cs index 791aebc..c0fe289 100644 --- a/TechStacks.ServiceInterface/Admin/EmailServices.cs +++ b/TechStacks.ServiceInterface/Admin/EmailServices.cs @@ -7,70 +7,69 @@ using TechStacks.ServiceInterface.Notifications; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface.Admin +namespace TechStacks.ServiceInterface.Admin; + +[Route("/email/post/{PostId}")] +public class EmailTest : IReturn { - [Route("/email/post/{PostId}")] - public class EmailTest : IReturn - { - public int? PostId { get; set; } - } + public int? PostId { get; set; } +} - public class EmailTestRespoonse - { - public ResponseStatus ResponseStatus { get; set; } - } +public class EmailTestRespoonse +{ + public ResponseStatus ResponseStatus { get; set; } +} - [Authenticate] - [RequiredRole("Admin")] - public class EmailServices : Service - { - public EmailProvider Email { get; set; } +[Authenticate] +[RequiredRole("Admin")] +public class EmailServices : Service +{ + public EmailProvider Email { get; set; } - public IAppSettings AppSettings { get; set; } + public IAppSettings AppSettings { get; set; } - public async Task Any(EmailTest request) - { - if (request.PostId == null) - throw new ArgumentNullException(nameof(request.PostId)); + public async Task Any(EmailTest request) + { + if (request.PostId == null) + throw new ArgumentNullException(nameof(request.PostId)); - var post = Db.SingleById(request.PostId); - var org = Db.SingleById(post.OrganizationId); + var post = Db.SingleById(request.PostId); + var org = Db.SingleById(post.OrganizationId); - var context = new ScriptContext { - VirtualFiles = base.VirtualFiles, - }.Init(); + var context = new ScriptContext { + VirtualFiles = base.VirtualFiles, + }.Init(); - var page = context.GetPage("emails/post-new"); - var result = new PageResult(page) { - Args = { - ["baseUrl"] = AppSettings.GetString("PublicBaseUrl"), - ["post"] = post, - ["organization"] = org, - } - }; - var html = await result.RenderToStringAsync(); + var page = context.GetPage("emails/post-new"); + var result = new PageResult(page) { + Args = { + ["baseUrl"] = AppSettings.GetString("PublicBaseUrl"), + ["post"] = post, + ["organization"] = org, + } + }; + var html = await result.RenderToStringAsync(); - var user = Db.SingleById(post.UserId); + var user = Db.SingleById(post.UserId); - Email.Send(new EmailMessage { - To = new MailTo { - Email = AppSettings.GetString("NotificationsFromEmail"), - Name = "Demis" - }, - From = new MailTo { - Email = AppSettings.GetString("NotificationsFromEmail"), - Name = user.DisplayName ?? user.UserName - }, - Cc = new MailTo { - Email = AppSettings.GetString("NotificationsCcEmail"), - Name = org.Name + " Subscribed" - }, - Subject = $"[{post.Type}] {post.Title}", - BodyHtml = html, - }); + Email.Send(new EmailMessage { + To = new MailTo { + Email = AppSettings.GetString("NotificationsFromEmail"), + Name = "Demis" + }, + From = new MailTo { + Email = AppSettings.GetString("NotificationsFromEmail"), + Name = user.DisplayName ?? user.UserName + }, + Cc = new MailTo { + Email = AppSettings.GetString("NotificationsCcEmail"), + Name = org.Name + " Subscribed" + }, + Subject = $"[{post.Type}] {post.Title}", + BodyHtml = html, + }); - return new EmailTestRespoonse(); - } - + return new EmailTestRespoonse(); } -} \ No newline at end of file + +} \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Admin/ImportServices.cs b/TechStacks.ServiceInterface/Admin/ImportServices.cs index 412d4bc..05a86fc 100644 --- a/TechStacks.ServiceInterface/Admin/ImportServices.cs +++ b/TechStacks.ServiceInterface/Admin/ImportServices.cs @@ -9,260 +9,259 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface.Admin +namespace TechStacks.ServiceInterface.Admin; + +public class ImportUserVoiceSuggestionValidator : AbstractValidator { - public class ImportUserVoiceSuggestionValidator : AbstractValidator + public ImportUserVoiceSuggestionValidator() { - public ImportUserVoiceSuggestionValidator() - { - RuleFor(x => x.TopicId).GreaterThan(0); - RuleFor(x => x.Url).RequiredUrl(); - RuleFor(x => x.Id).GreaterThan(0); - RuleFor(x => x.Title).NotEmpty(); - RuleFor(x => x.Creator) - .Must(x => x != null - && !string.IsNullOrEmpty(x.Name) - && !string.IsNullOrEmpty(x.Email) - && x.Id > 0); - RuleFor(x => x.Response) - .Must(x => { - if (x == null) - return true; - var creator = x.Creator; - var ret = ( - !string.IsNullOrEmpty(creator.Name) - && !string.IsNullOrEmpty(creator.Email) - && creator.Id > 0 - ); - return ret; - }); - RuleFor(x => x.CreatedAt).NotEmpty(); - RuleFor(x => x.UpdatedAt).NotEmpty(); - } + RuleFor(x => x.TopicId).GreaterThan(0); + RuleFor(x => x.Url).RequiredUrl(); + RuleFor(x => x.Id).GreaterThan(0); + RuleFor(x => x.Title).NotEmpty(); + RuleFor(x => x.Creator) + .Must(x => x != null + && !string.IsNullOrEmpty(x.Name) + && !string.IsNullOrEmpty(x.Email) + && x.Id > 0); + RuleFor(x => x.Response) + .Must(x => { + if (x == null) + return true; + var creator = x.Creator; + var ret = ( + !string.IsNullOrEmpty(creator.Name) + && !string.IsNullOrEmpty(creator.Email) + && creator.Id > 0 + ); + return ret; + }); + RuleFor(x => x.CreatedAt).NotEmpty(); + RuleFor(x => x.UpdatedAt).NotEmpty(); } +} - [RequiredRole("Import")] //or Admin - public class ImportServices : PostServicesBase +[RequiredRole("Import")] //or Admin +public class ImportServices : PostServicesBase +{ + public async Task Any(ImportUser request) { - public async Task Any(ImportUser request) - { - if (string.IsNullOrEmpty(request.Email) && string.IsNullOrEmpty(request.UserName)) - throw new ArgumentNullException(nameof(request.Email)); + if (string.IsNullOrEmpty(request.Email) && string.IsNullOrEmpty(request.UserName)) + throw new ArgumentNullException(nameof(request.Email)); - if (string.IsNullOrEmpty(request.RefSource)) - throw new ArgumentNullException(nameof(request.RefSource)); + if (string.IsNullOrEmpty(request.RefSource)) + throw new ArgumentNullException(nameof(request.RefSource)); - if (string.IsNullOrEmpty(request.RefIdStr) && request.RefId == null) - throw new ArgumentNullException(nameof(request.RefId)); + if (string.IsNullOrEmpty(request.RefIdStr) && request.RefId == null) + throw new ArgumentNullException(nameof(request.RefId)); - var existingUser = - (string.IsNullOrEmpty(request.Email) ? null : AuthRepository.GetUserAuthByUserName(request.Email)) - ?? (string.IsNullOrEmpty(request.UserName) ? null : AuthRepository.GetUserAuthByUserName(request.UserName)); + var existingUser = + (string.IsNullOrEmpty(request.Email) ? null : AuthRepository.GetUserAuthByUserName(request.Email)) + ?? (string.IsNullOrEmpty(request.UserName) ? null : AuthRepository.GetUserAuthByUserName(request.UserName)); - if (existingUser != null) - throw HttpError.Conflict("Email and UserName must be unique"); + if (existingUser != null) + throw HttpError.Conflict("Email and UserName must be unique"); - var session = SessionAs(); + var session = SessionAs(); - var user = request.ConvertTo(); - user.CreatedDate = user.ModifiedDate = DateTime.Now; - user.CreatedBy = session.UserName; + var user = request.ConvertTo(); + user.CreatedDate = user.ModifiedDate = DateTime.Now; + user.CreatedBy = session.UserName; - var userId = (int)await Db.InsertAsync(user, selectIdentity: true); + var userId = (int)await Db.InsertAsync(user, selectIdentity: true); - return new ImportUserResponse { - Id = userId - }; - } + return new ImportUserResponse { + Id = userId + }; + } - public const string UserVoiceSource = "uservoice"; + public const string UserVoiceSource = "uservoice"; - public async Task GetOrCreateUser(UserVoiceUser user) - { - if (user == null) - return null; + public async Task GetOrCreateUser(UserVoiceUser user) + { + if (user == null) + return null; - var dbUser = await Db.SingleAsync(x => x.Email == user.Email - || (x.RefSource == UserVoiceSource && x.RefId == user.Id)); - if (dbUser != null) - return dbUser; - - var userNameFromEmail = user.Email.LeftPart('@'); - var potentialUserNames = new[] { - userNameFromEmail, - user.Name.Replace(" ",""), - user.Name.Replace(" ","-"), - userNameFromEmail + ".uv", - }; - - var takenUserNames = await Db.ColumnAsync(Db.From() - .Where(x => potentialUserNames.Contains(x.UserName)) - .Select(x => x.UserName)); - - var bestUserName = potentialUserNames.FirstOrDefault(x => !takenUserNames.Contains(x)); - - if (bestUserName == null) - throw HttpError.Conflict("Could not generate unique username, please specify one for UserVoice User: " + user.Id); - - var session = GetUser(); - - var newUser = new CustomUserAuth { - UserName = bestUserName, - Email = user.Email, - DisplayName = user.Name, - DefaultProfileUrl = user.AvatarUrl, - RefSource = "uservoice", - RefId = user.Id, - RefUrn = $"urn:uservoice:user:{user.Id}", - CreatedBy = session.UserName, - CreatedDate = user.CreatedAt, - ModifiedDate = user.UpdatedAt, - }; - - newUser.Id = (int) await Db.InsertAsync(newUser, selectIdentity: true); - return newUser; - } + var dbUser = await Db.SingleAsync(x => x.Email == user.Email + || (x.RefSource == UserVoiceSource && x.RefId == user.Id)); + if (dbUser != null) + return dbUser; + + var userNameFromEmail = user.Email.LeftPart('@'); + var potentialUserNames = new[] { + userNameFromEmail, + user.Name.Replace(" ",""), + user.Name.Replace(" ","-"), + userNameFromEmail + ".uv", + }; + + var takenUserNames = await Db.ColumnAsync(Db.From() + .Where(x => potentialUserNames.Contains(x.UserName)) + .Select(x => x.UserName)); + + var bestUserName = potentialUserNames.FirstOrDefault(x => !takenUserNames.Contains(x)); + + if (bestUserName == null) + throw HttpError.Conflict("Could not generate unique username, please specify one for UserVoice User: " + user.Id); + + var session = GetUser(); + + var newUser = new CustomUserAuth { + UserName = bestUserName, + Email = user.Email, + DisplayName = user.Name, + DefaultProfileUrl = user.AvatarUrl, + RefSource = "uservoice", + RefId = user.Id, + RefUrn = $"urn:uservoice:user:{user.Id}", + CreatedBy = session.UserName, + CreatedDate = user.CreatedAt, + ModifiedDate = user.UpdatedAt, + }; + + newUser.Id = (int) await Db.InsertAsync(newUser, selectIdentity: true); + return newUser; + } - public async Task GetOrCreateCategory(int organizationId, string category) - { - if (string.IsNullOrEmpty(category)) - return default(int); + public async Task GetOrCreateCategory(int organizationId, string category) + { + if (string.IsNullOrEmpty(category)) + return default(int); - var orgCategory = Db.SingleAsync(x => x.OrganizationId == organizationId && x.Name == category); - if (orgCategory != null) - return orgCategory.Id; + var orgCategory = Db.SingleAsync(x => x.OrganizationId == organizationId && x.Name == category); + if (orgCategory != null) + return orgCategory.Id; - var user = GetUser(); - - var now = DateTime.Now; - var newCategoryId = (int)await Db.InsertAsync(new Category { - OrganizationId = organizationId, - Name = category, - Created = now, - CreatedBy = user.UserName, - Modified = now, - ModifiedBy = user.UserName, - RefSource = "uservoice", - RefUrn = $"urn:uservoice:category:{category}" - }, selectIdentity:true); - - return newCategoryId; - } + var user = GetUser(); + + var now = DateTime.Now; + var newCategoryId = (int)await Db.InsertAsync(new Category { + OrganizationId = organizationId, + Name = category, + Created = now, + CreatedBy = user.UserName, + Modified = now, + ModifiedBy = user.UserName, + RefSource = "uservoice", + RefUrn = $"urn:uservoice:category:{category}" + }, selectIdentity:true); + + return newCategoryId; + } - public async Task Any(ImportUserVoiceSuggestion request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + public async Task Any(ImportUserVoiceSuggestion request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - var creator = await GetOrCreateUser(request.Creator); + var creator = await GetOrCreateUser(request.Creator); - var existingPost = await Db.SingleAsync(x => - (request.Url != null && x.Url == request.Url) || - (x.RefSource == UserVoiceSource && x.RefId == request.Id)); + var existingPost = await Db.SingleAsync(x => + (request.Url != null && x.Url == request.Url) || + (x.RefSource == UserVoiceSource && x.RefId == request.Id)); - if (existingPost != null) - throw HttpError.Conflict($"Post already imported: /posts/{existingPost.Id}/{existingPost.Slug}"); + if (existingPost != null) + throw HttpError.Conflict($"Post already imported: /posts/{existingPost.Id}/{existingPost.Slug}"); - var categoryId = await GetOrCreateCategory(request.OrganizationId, request.Category); + var categoryId = await GetOrCreateCategory(request.OrganizationId, request.Category); - var now = DateTime.Now; - var hidden = request.StatusKey == "declined" || request.StatusKey == "completed" - ? now - : (DateTime?) null; + var now = DateTime.Now; + var hidden = request.StatusKey == "declined" || request.StatusKey == "completed" + ? now + : (DateTime?) null; - var statusBy = (await GetOrCreateUser(request.StatusChangedBy))?.UserName - ?? user.UserName; + var statusBy = (await GetOrCreateUser(request.StatusChangedBy))?.UserName + ?? user.UserName; + + var labels = new List(); + if (request.State != null && request.State != "closed") + { + labels.Add(request.State.GenerateSlug()); + } + + if (request.StatusKey != null) + { + labels.Add(request.StatusKey); - var labels = new List(); - if (request.State != null && request.State != "closed") + var orgLabelExists = await Db.ExistsAsync(x => + x.OrganizationId == request.OrganizationId && x.Slug == request.StatusKey); + + if (!orgLabelExists) { - labels.Add(request.State.GenerateSlug()); + await Db.InsertAsync(new OrganizationLabel { + OrganizationId = request.OrganizationId, + Slug = request.StatusKey, + Description = $"UserVoice Suggestion", + Color = request.StatusHexColor, + }); } - - if (request.StatusKey != null) + } + + Post newPost = null; + + using (var trans = Db.OpenTransaction()) + { + newPost = new Post { - labels.Add(request.StatusKey); + OrganizationId = request.OrganizationId, + UserId = creator.Id, + Type = PostType.Request, + Url = request.Url, + Title = request.Title, + Slug = request.Slug ?? request.Title.GenerateSlug(), + RefSource = "uservoice", + RefId = request.Id, + RefUrn = $"urn:uservoice:suggestion:{request.TopicId}:{request.Id}", + Created = request.CreatedAt, + CreatedBy = creator.UserName, + Modified = request.UpdatedAt, + ModifiedBy = creator.UserName, + CategoryId = categoryId, + PointsModifier = request.VoteCount, + Hidden = hidden, + HiddenBy = statusBy, + Content = request.Text, + ContentHtml = request.FormattedText, + Labels = labels.Count > 0 ? labels.ToArray() : null, + }; - var orgLabelExists = await Db.ExistsAsync(x => - x.OrganizationId == request.OrganizationId && x.Slug == request.StatusKey); - - if (!orgLabelExists) - { - await Db.InsertAsync(new OrganizationLabel { - OrganizationId = request.OrganizationId, - Slug = request.StatusKey, - Description = $"UserVoice Suggestion", - Color = request.StatusHexColor, - }); - } + if (request.State == "closed") + { + newPost.Status = request.State; + newPost.StatusBy = statusBy; + newPost.StatusDate = request.StatusChangedBy?.UpdatedAt; } - Post newPost = null; + newPost.Id = await Db.InsertAsync(newPost, selectIdentity:true); - using (var trans = Db.OpenTransaction()) + if (request.Response != null) { - newPost = new Post - { - OrganizationId = request.OrganizationId, - UserId = creator.Id, - Type = PostType.Request, - Url = request.Url, - Title = request.Title, - Slug = request.Slug ?? request.Title.GenerateSlug(), + var commentUser = await GetOrCreateUser(request.Response.Creator); + + var newComment = new PostComment { + PostId = newPost.Id, + Content = request.Response.Text, + ContentHtml = request.Response.FormattedText, + Created = request.Response.CreatedAt, + CreatedBy = commentUser.UserName, + Modified = request.Response.CreatedAt, RefSource = "uservoice", RefId = request.Id, - RefUrn = $"urn:uservoice:suggestion:{request.TopicId}:{request.Id}", - Created = request.CreatedAt, - CreatedBy = creator.UserName, - Modified = request.UpdatedAt, - ModifiedBy = creator.UserName, - CategoryId = categoryId, - PointsModifier = request.VoteCount, - Hidden = hidden, - HiddenBy = statusBy, - Content = request.Text, - ContentHtml = request.FormattedText, - Labels = labels.Count > 0 ? labels.ToArray() : null, + RefUrn = $"urn:uservoice:response:{request.TopicId}:{request.Id}", }; - if (request.State == "closed") - { - newPost.Status = request.State; - newPost.StatusBy = statusBy; - newPost.StatusDate = request.StatusChangedBy?.UpdatedAt; - } + newComment.Id = await Db.InsertAsync(newComment, selectIdentity: true); - newPost.Id = await Db.InsertAsync(newPost, selectIdentity:true); - - if (request.Response != null) - { - var commentUser = await GetOrCreateUser(request.Response.Creator); - - var newComment = new PostComment { - PostId = newPost.Id, - Content = request.Response.Text, - ContentHtml = request.Response.FormattedText, - Created = request.Response.CreatedAt, - CreatedBy = commentUser.UserName, - Modified = request.Response.CreatedAt, - RefSource = "uservoice", - RefId = request.Id, - RefUrn = $"urn:uservoice:response:{request.TopicId}:{request.Id}", - }; - - newComment.Id = await Db.InsertAsync(newComment, selectIdentity: true); - - await Db.UpdateOnlyAsync(() => new Post {PinCommentId = newComment.Id}, - where: x => x.Id == newPost.Id && x.OrganizationId == request.OrganizationId); - } - - trans.Commit(); + await Db.UpdateOnlyAsync(() => new Post {PinCommentId = newComment.Id}, + where: x => x.Id == newPost.Id && x.OrganizationId == request.OrganizationId); } - - return new ImportUserVoiceSuggestionResponse { - PostId = newPost.Id, - PostSlug = newPost.Slug - }; + + trans.Commit(); } + + return new ImportUserVoiceSuggestionResponse { + PostId = newPost.Id, + PostSlug = newPost.Slug + }; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Admin/NotificationServices.Utils.cs b/TechStacks.ServiceInterface/Admin/NotificationServices.Utils.cs index fdf2a43..6732892 100644 --- a/TechStacks.ServiceInterface/Admin/NotificationServices.Utils.cs +++ b/TechStacks.ServiceInterface/Admin/NotificationServices.Utils.cs @@ -9,148 +9,147 @@ using TechStacks.ServiceInterface.Notifications; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface.Admin +namespace TechStacks.ServiceInterface.Admin; + +public class UserEmailInfo { - public class UserEmailInfo - { - public int Id { get; set; } - public string UserName { get; set; } - public string Email { get; set; } - public string DisplayName { get; set; } - } + public int Id { get; set; } + public string UserName { get; set; } + public string Email { get; set; } + public string DisplayName { get; set; } +} - public partial class NotificationServices : Service +public partial class NotificationServices : Service +{ + private Notification AssertNotification(long Id) { - private Notification AssertNotification(long Id) - { - if (Id <= 0) - throw new ArgumentNullException(nameof(Id)); + if (Id <= 0) + throw new ArgumentNullException(nameof(Id)); - var notification = Db.SingleById(Id); - if (notification == null) - throw HttpError.NotFound($"Notification not found {Id}"); + var notification = Db.SingleById(Id); + if (notification == null) + throw HttpError.NotFound($"Notification not found {Id}"); - return notification; - } + return notification; + } - private async Task AssertPost(long postId) - { - var post = await Db.SingleByIdAsync(postId); - if (post == null) - throw HttpError.NotFound("Post not found: " + postId); + private async Task AssertPost(long postId) + { + var post = await Db.SingleByIdAsync(postId); + if (post == null) + throw HttpError.NotFound("Post not found: " + postId); - return post; - } + return post; + } - private ScriptContext CreateEmailContext() => new ScriptContext { - VirtualFiles = base.VirtualFiles, - }.Init(); + private ScriptContext CreateEmailContext() => new ScriptContext { + VirtualFiles = base.VirtualFiles, + }.Init(); - private Task> GetOrganizationModeratorIds(int organizationId) - { - return Db.ColumnAsync(Db.From() - .Where(x => x.OrganizationId == organizationId && (x.IsOwner || x.IsModerator)) - .Select(x => x.UserId)); - } + private Task> GetOrganizationModeratorIds(int organizationId) + { + return Db.ColumnAsync(Db.From() + .Where(x => x.OrganizationId == organizationId && (x.IsOwner || x.IsModerator)) + .Select(x => x.UserId)); + } - private async Task CreateAndSaveEmailTemplate( - Notification notification, string operation, string templatePath, - List toUserIds, string fromName, string ccName, string subject, string html) + private async Task CreateAndSaveEmailTemplate( + Notification notification, string operation, string templatePath, + List toUserIds, string fromName, string ccName, string subject, string html) + { + var template = new EmailTemplate { + TemplatePath = templatePath, + ToUserIds = toUserIds.ToArray(), + FromEmail = AppSettings.GetString("NotificationsFromEmail"), + FromName = fromName, + CcEmail = AppSettings.GetString("NotificationsCcEmail"), + CcName = ccName, + Subject = subject, + BodyHtml = html, + Created = DateTime.Now, + }; + + using (var trans = Db.OpenTransaction()) { - var template = new EmailTemplate { - TemplatePath = templatePath, - ToUserIds = toUserIds.ToArray(), - FromEmail = AppSettings.GetString("NotificationsFromEmail"), - FromName = fromName, - CcEmail = AppSettings.GetString("NotificationsCcEmail"), - CcName = ccName, - Subject = subject, - BodyHtml = html, - Created = DateTime.Now, - }; - - using (var trans = Db.OpenTransaction()) - { - template.Id = await Db.InsertAsync(template, selectIdentity: true); + template.Id = await Db.InsertAsync(template, selectIdentity: true); - var operations = new List(notification.Operations ?? TypeConstants.EmptyStringArray); - operations.AddIfNotExists(operation); - notification.Started = DateTime.Now; - notification.Operations = operations.ToArray(); - notification.EmailTemplateId = template.Id; - notification.UserIds = toUserIds.ToArray(); - notification.EmailedUserIds = new int[0]; + var operations = new List(notification.Operations ?? TypeConstants.EmptyStringArray); + operations.AddIfNotExists(operation); + notification.Started = DateTime.Now; + notification.Operations = operations.ToArray(); + notification.EmailTemplateId = template.Id; + notification.UserIds = toUserIds.ToArray(); + notification.EmailedUserIds = new int[0]; - await Db.UpdateAsync(notification); + await Db.UpdateAsync(notification); - trans.Commit(); - } - - return template; + trans.Commit(); } - private async Task RecordEmailSentToUser(long notificationId, int userId) - { - await Db.ExecuteSqlAsync(@"UPDATE notification SET emailed_user_ids = emailed_user_ids || @userId WHERE id = @id", - new { userId, id = notificationId }); - } + return template; + } + + private async Task RecordEmailSentToUser(long notificationId, int userId) + { + await Db.ExecuteSqlAsync(@"UPDATE notification SET emailed_user_ids = emailed_user_ids || @userId WHERE id = @id", + new { userId, id = notificationId }); + } - private async Task SendEmailsToRemainingUsers(Notification notification, EmailTemplate template) + private async Task SendEmailsToRemainingUsers(Notification notification, EmailTemplate template) + { + var remainingUserIds = notification.UserIds.Where(x => !notification.EmailedUserIds.Contains(x)).ToList(); + if (remainingUserIds.Count > 0) { - var remainingUserIds = notification.UserIds.Where(x => !notification.EmailedUserIds.Contains(x)).ToList(); - if (remainingUserIds.Count > 0) - { - var users = await Db.SelectAsync(Db.From() - .Where(x => remainingUserIds.Contains(x.Id))); + var users = await Db.SelectAsync(Db.From() + .Where(x => remainingUserIds.Contains(x.Id))); - var userMap = users.ToDictionary(x => x.Id); + var userMap = users.ToDictionary(x => x.Id); - foreach (var userId in remainingUserIds) + foreach (var userId in remainingUserIds) + { + var user = userMap[userId]; + if (!string.IsNullOrEmpty(user.Email)) { - var user = userMap[userId]; - if (!string.IsNullOrEmpty(user.Email)) - { - Email.Send(template.ToEmailMessage(user.Email, user.DisplayName ?? user.UserName)); - } - - await RecordEmailSentToUser(notification.Id, userId); + Email.Send(template.ToEmailMessage(user.Email, user.DisplayName ?? user.UserName)); } - } - else - { - SendNotificationEmail(template, $"{notification.UserIds.Length} subscribers"); + + await RecordEmailSentToUser(notification.Id, userId); } } - - private void SendNotificationEmail(EmailTemplate template, string toName) + else { - var notificationsEmail = AppSettings.GetString("NotificationsFromEmail"); - var email = template.ToEmailMessage(notificationsEmail, toName); - Email.Send(email); + SendNotificationEmail(template, $"{notification.UserIds.Length} subscribers"); } } - public static class NotificationUtils + private void SendNotificationEmail(EmailTemplate template, string toName) { - public static EmailMessage ToEmailMessage(this EmailTemplate from, string toEmail, string toName) - { - return new EmailMessage { - To = new MailTo { - Email = toEmail, - Name = toName, - }, - From = new MailTo { - Email = from.FromEmail, - Name = from.FromName, - }, - Cc = from.CcEmail == null ? null : new MailTo { - Email = from.CcEmail, - Name = from.CcName, - }, - Subject = from.Subject, - Body = from.Body, - BodyHtml = from.BodyHtml - }; - } + var notificationsEmail = AppSettings.GetString("NotificationsFromEmail"); + var email = template.ToEmailMessage(notificationsEmail, toName); + Email.Send(email); + } +} + +public static class NotificationUtils +{ + public static EmailMessage ToEmailMessage(this EmailTemplate from, string toEmail, string toName) + { + return new EmailMessage { + To = new MailTo { + Email = toEmail, + Name = toName, + }, + From = new MailTo { + Email = from.FromEmail, + Name = from.FromName, + }, + Cc = from.CcEmail == null ? null : new MailTo { + Email = from.CcEmail, + Name = from.CcName, + }, + Subject = from.Subject, + Body = from.Body, + BodyHtml = from.BodyHtml + }; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Admin/NotificationServices.cs b/TechStacks.ServiceInterface/Admin/NotificationServices.cs index 7fe8a3e..ea14a16 100644 --- a/TechStacks.ServiceInterface/Admin/NotificationServices.cs +++ b/TechStacks.ServiceInterface/Admin/NotificationServices.cs @@ -11,234 +11,232 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface.Admin +namespace TechStacks.ServiceInterface.Admin; + +[RequiredRole("Admin")] +public partial class NotificationServices : Service { - [RequiredRole("Admin")] - public partial class NotificationServices : Service - { - private static ILog log = LogManager.GetLogger(typeof(NotificationServices)); + private static ILog log = LogManager.GetLogger(typeof(NotificationServices)); - public EmailProvider Email { get; set; } + public EmailProvider Email { get; set; } - public IAppSettings AppSettings { get; set; } + public IAppSettings AppSettings { get; set; } - public object Any(RetryPendingNotifications request) + public object Any(RetryPendingNotifications request) + { + var pendingNotificationIds = Db.Column(Db.From() + .Where(x => x.Completed == null && x.Failed == null) + .Select(x => x.Id)) + .ToArray(); + + if (pendingNotificationIds.Length > 0) { - var pendingNotificationIds = Db.Column(Db.From() - .Where(x => x.Completed == null && x.Failed == null) - .Select(x => x.Id)) - .ToArray(); + log.Info($"Resending {pendingNotificationIds.Length} pending notifications: {pendingNotificationIds}"); - if (pendingNotificationIds.Length > 0) + foreach (var notificationId in pendingNotificationIds) { - log.Info($"Resending {pendingNotificationIds.Length} pending notifications: {pendingNotificationIds}"); - - foreach (var notificationId in pendingNotificationIds) - { - PublishMessage(new SendNotification { Id = notificationId }); - } + PublishMessage(new SendNotification { Id = notificationId }); } - - return new RetryPendingNotificationsResponse { - ResentIds = pendingNotificationIds - }; } + + return new RetryPendingNotificationsResponse { + ResentIds = pendingNotificationIds + }; + } - Func GetEventHandler(string eventName) + Func GetEventHandler(string eventName) + { + switch (eventName) { - switch (eventName) - { - case nameof(CreatePost): - return SendNewPostEmail; - case nameof(UserPostReport): - return SendReportPostEmail; - case nameof(UserPostCommentReport): - return SendReportCommentEmail; - } - return null; + case nameof(CreatePost): + return SendNewPostEmail; + case nameof(UserPostReport): + return SendReportPostEmail; + case nameof(UserPostCommentReport): + return SendReportCommentEmail; } + return null; + } - public void Any(SendSystemEmail request) - { - Email.Send(new EmailMessage { - To = new MailTo { - Email = AppSettings.GetString("SystemToEmail"), - }, - From = new MailTo { - Email = AppSettings.GetString("NotificationsFromEmail"), - }, - Subject = $"[SYSTEM] {request.Subject}", - Body = request.Body, - }); - } + public void Any(SendSystemEmail request) + { + Email.Send(new EmailMessage { + To = new MailTo { + Email = AppSettings.GetString("SystemToEmail"), + }, + From = new MailTo { + Email = AppSettings.GetString("NotificationsFromEmail"), + }, + Subject = $"[SYSTEM] {request.Subject}", + Body = request.Body, + }); + } - public void Any(SendEmail request) - { - Email.Send(new EmailMessage { - To = new MailTo { - Email = request.To, - }, - From = new MailTo { - Email = AppSettings.GetString("NotificationsFromEmail"), - }, - Subject = request.Subject, - Body = request.Body, - }); - } + public void Any(SendEmail request) + { + Email.Send(new EmailMessage { + To = new MailTo { + Email = request.To, + }, + From = new MailTo { + Email = AppSettings.GetString("NotificationsFromEmail"), + }, + Subject = request.Subject, + Body = request.Body, + }); + } - public async Task Any(SendNotification request) - { - var notification = AssertNotification(request.Id); + public async Task Any(SendNotification request) + { + var notification = AssertNotification(request.Id); - var eventHandler = GetEventHandler(notification.Event); - if (eventHandler != null) + var eventHandler = GetEventHandler(notification.Event); + if (eventHandler != null) + { + try { - try - { - await eventHandler(notification); - - await Db.UpdateOnlyAsync(() => new Notification { - Completed = DateTime.Now - }, - where: x => x.Id == notification.Id); - } - catch (Exception ex) - { - await Db.UpdateOnlyAsync(() => new Notification { - Failed = DateTime.Now, - Error = ex.Message + Environment.NewLine + ex - }, - where:x => x.Id == notification.Id); - throw; - } + await eventHandler(notification); + + await Db.UpdateOnlyAsync(() => new Notification { + Completed = DateTime.Now + }, + where: x => x.Id == notification.Id); } - else + catch (Exception ex) { - log.Warn($"Received notification of unknown Event Type: {notification.Event}"); + await Db.UpdateOnlyAsync(() => new Notification { + Failed = DateTime.Now, + Error = ex.Message + Environment.NewLine + ex + }, + where:x => x.Id == notification.Id); + throw; } } - - private async Task SendNewPostEmail(Notification notification) + else { - EmailTemplate template = null; + log.Warn($"Received notification of unknown Event Type: {notification.Event}"); + } + } - if (notification.EmailTemplateId == null) - { - var post = await AssertPost(notification.RefId); - var org = await Db.SingleByIdAsync(post.OrganizationId); - var user = await Db.SingleByIdAsync(post.UserId); - - var q = Db.From() - .Where(x => x.OrganizationId == post.OrganizationId) - .And("ARRAY[{0}] && post_types", post.Type) - .Select(x => x.UserId); - var postTypeSubscriberUserIds = await Db.ColumnAsync(q); - - var context = CreateEmailContext(); - var templatePath = "emails/post-new"; - var page = context.GetPage(templatePath); - var result = new PageResult(page) { - Args = { - ["baseUrl"] = AppSettings.GetString("PublicBaseUrl"), - ["post"] = post, - ["organization"] = org, - } - }; - - template = await CreateAndSaveEmailTemplate(notification, nameof(SendNewPostEmail), templatePath, - toUserIds: postTypeSubscriberUserIds, - fromName: user.DisplayName ?? user.UserName, - ccName: org.Name + " Subscribed", - subject: $"[{post.Type}] {post.Title}", - html: await result.RenderToStringAsync()); - } - else - { - template = await Db.SingleByIdAsync(notification.EmailTemplateId); - } + private async Task SendNewPostEmail(Notification notification) + { + EmailTemplate template = null; - await SendEmailsToRemainingUsers(notification, template); - } + if (notification.EmailTemplateId == null) + { + var post = await AssertPost(notification.RefId); + var org = await Db.SingleByIdAsync(post.OrganizationId); + var user = await Db.SingleByIdAsync(post.UserId); + + var q = Db.From() + .Where(x => x.OrganizationId == post.OrganizationId) + .And("ARRAY[{0}] && post_types", post.Type) + .Select(x => x.UserId); + var postTypeSubscriberUserIds = await Db.ColumnAsync(q); + + var context = CreateEmailContext(); + var templatePath = "emails/post-new"; + var page = context.GetPage(templatePath); + var result = new PageResult(page) { + Args = { + ["baseUrl"] = AppSettings.GetString("PublicBaseUrl"), + ["post"] = post, + ["organization"] = org, + } + }; - async Task SendReportPostEmail(Notification notification) + template = await CreateAndSaveEmailTemplate(notification, nameof(SendNewPostEmail), templatePath, + toUserIds: postTypeSubscriberUserIds, + fromName: user.DisplayName ?? user.UserName, + ccName: org.Name + " Subscribed", + subject: $"[{post.Type}] {post.Title}", + html: await result.RenderToStringAsync()); + } + else { - EmailTemplate template = null; + template = await Db.SingleByIdAsync(notification.EmailTemplateId); + } - if (notification.EmailTemplateId == null) - { - var report = await Db.SingleByIdAsync(notification.RefId); - var post = await AssertPost(report.PostId); - var org = await Db.SingleByIdAsync(post.OrganizationId); - var moderatorUserIds = await GetOrganizationModeratorIds(org.Id); + await SendEmailsToRemainingUsers(notification, template); + } + + async Task SendReportPostEmail(Notification notification) + { + EmailTemplate template = null; + + if (notification.EmailTemplateId == null) + { + var report = await Db.SingleByIdAsync(notification.RefId); + var post = await AssertPost(report.PostId); + var org = await Db.SingleByIdAsync(post.OrganizationId); + var moderatorUserIds = await GetOrganizationModeratorIds(org.Id); - var context = CreateEmailContext(); - var templatePath = "emails/post-report"; - var page = context.GetPage(templatePath); - var result = new PageResult(page) { - Args = { - ["baseUrl"] = AppSettings.GetString("PublicBaseUrl"), - ["report"] = report, - ["post"] = post, - ["organization"] = org, - } - }; - - var reportType = report.FlagType == FlagType.Other ? "Report" : report.FlagType.ToString(); - - template = await CreateAndSaveEmailTemplate(notification, nameof(SendReportPostEmail), templatePath, - toUserIds: moderatorUserIds, - fromName: report.UserName, - ccName: org.Name + " Moderators", - subject: $"[{reportType}] {post.Title}", - html: await result.RenderToStringAsync()); - } - else - { - template = await Db.SingleByIdAsync(notification.EmailTemplateId); - } + var context = CreateEmailContext(); + var templatePath = "emails/post-report"; + var page = context.GetPage(templatePath); + var result = new PageResult(page) { + Args = { + ["baseUrl"] = AppSettings.GetString("PublicBaseUrl"), + ["report"] = report, + ["post"] = post, + ["organization"] = org, + } + }; - await SendEmailsToRemainingUsers(notification, template); - } + var reportType = report.FlagType == FlagType.Other ? "Report" : report.FlagType.ToString(); - async Task SendReportCommentEmail(Notification notification) + template = await CreateAndSaveEmailTemplate(notification, nameof(SendReportPostEmail), templatePath, + toUserIds: moderatorUserIds, + fromName: report.UserName, + ccName: org.Name + " Moderators", + subject: $"[{reportType}] {post.Title}", + html: await result.RenderToStringAsync()); + } + else { - EmailTemplate template = null; + template = await Db.SingleByIdAsync(notification.EmailTemplateId); + } - if (notification.EmailTemplateId == null) - { - var report = await Db.SingleByIdAsync(notification.RefId); - var comment = await Db.SingleByIdAsync(report.PostCommentId); - var org = await Db.SingleByIdAsync(report.OrganizationId); - var moderatorUserIds = await GetOrganizationModeratorIds(org.Id); + await SendEmailsToRemainingUsers(notification, template); + } + + async Task SendReportCommentEmail(Notification notification) + { + EmailTemplate template = null; + + if (notification.EmailTemplateId == null) + { + var report = await Db.SingleByIdAsync(notification.RefId); + var comment = await Db.SingleByIdAsync(report.PostCommentId); + var org = await Db.SingleByIdAsync(report.OrganizationId); + var moderatorUserIds = await GetOrganizationModeratorIds(org.Id); - var context = CreateEmailContext(); - var templatePath = "emails/comment-report"; - var page = context.GetPage(templatePath); - var result = new PageResult(page) { - Args = { - ["baseUrl"] = AppSettings.GetString("PublicBaseUrl"), - ["report"] = report, - ["comment"] = comment, - ["organization"] = org, - } - }; - - var reportType = report.FlagType == FlagType.Other ? "Report" : report.FlagType.ToString(); - - template = await CreateAndSaveEmailTemplate(notification, nameof(SendReportPostEmail), templatePath, - toUserIds: moderatorUserIds, - fromName: report.UserName, - ccName: org.Name + " Moderators", - subject: $"[{reportType}] Comment", - html: await result.RenderToStringAsync()); - } - else - { - template = await Db.SingleByIdAsync(notification.EmailTemplateId); - } + var context = CreateEmailContext(); + var templatePath = "emails/comment-report"; + var page = context.GetPage(templatePath); + var result = new PageResult(page) { + Args = { + ["baseUrl"] = AppSettings.GetString("PublicBaseUrl"), + ["report"] = report, + ["comment"] = comment, + ["organization"] = org, + } + }; - await SendEmailsToRemainingUsers(notification, template); + var reportType = report.FlagType == FlagType.Other ? "Report" : report.FlagType.ToString(); + + template = await CreateAndSaveEmailTemplate(notification, nameof(SendReportPostEmail), templatePath, + toUserIds: moderatorUserIds, + fromName: report.UserName, + ccName: org.Name + " Moderators", + subject: $"[{reportType}] Comment", + html: await result.RenderToStringAsync()); + } + else + { + template = await Db.SingleByIdAsync(notification.EmailTemplateId); } - } + await SendEmailsToRemainingUsers(notification, template); + } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/AppFeatureFlags.cs b/TechStacks.ServiceInterface/AppFeatureFlags.cs index 79fcd93..e1ed576 100644 --- a/TechStacks.ServiceInterface/AppFeatureFlags.cs +++ b/TechStacks.ServiceInterface/AppFeatureFlags.cs @@ -1,12 +1,11 @@ using ServiceStack.Configuration; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public static class AppFeatureFlags { - public static class AppFeatureFlags + public static bool EnableTwitterUpdates(this IAppSettings appSettings) { - public static bool EnableTwitterUpdates(this IAppSettings appSettings) - { - return appSettings.Get("EnableTwitterUpdates", true); - } + return appSettings.Get("EnableTwitterUpdates", true); } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Auth/DiscourseAuthProvider.cs b/TechStacks.ServiceInterface/Auth/DiscourseAuthProvider.cs index 823537f..c50ebbd 100644 --- a/TechStacks.ServiceInterface/Auth/DiscourseAuthProvider.cs +++ b/TechStacks.ServiceInterface/Auth/DiscourseAuthProvider.cs @@ -5,65 +5,64 @@ using ServiceStack; using ServiceStack.Auth; -namespace TechStacks.ServiceInterface.Auth -{ - public class DiscourseAuthProvider : CredentialsAuthProvider - { - public string DiscourseUrl { get; set; } +namespace TechStacks.ServiceInterface.Auth; - public override async Task TryAuthenticateAsync(IServiceBase authService, string userName, string password, - CancellationToken token = new CancellationToken()) - { - var valid = await base.TryAuthenticateAsync(authService, userName, password, token); - if (valid) - return true; +public class DiscourseAuthProvider : CredentialsAuthProvider +{ + public string DiscourseUrl { get; set; } + + public override async Task TryAuthenticateAsync(IServiceBase authService, string userName, string password, + CancellationToken token = new()) + { + var valid = await base.TryAuthenticateAsync(authService, userName, password, token); + if (valid) + return true; - var csrfUrl = DiscourseUrl.CombineWith("/session/csrf"); + var csrfUrl = DiscourseUrl.CombineWith("/session/csrf"); - var cookies = new CookieContainer(); - var csrfJson = await csrfUrl.GetJsonFromUrlAsync( - requestFilter: req => - { - req.Headers.Add("Cookie",cookies.GetAllCookies().Join(";")); - }); - var csrfObj = (Dictionary)JSON.parse(csrfJson); + var cookies = new CookieContainer(); + var csrfJson = await csrfUrl.GetJsonFromUrlAsync( + requestFilter: req => + { + req.Headers.Add("Cookie",cookies.GetAllCookies().Join(";")); + }, token: token); + var csrfObj = (Dictionary)JSON.parse(csrfJson); - var csrf = csrfObj["csrf"] as string; + var csrf = csrfObj["csrf"] as string; - var loginUrl = DiscourseUrl.CombineWith("/session"); + var loginUrl = DiscourseUrl.CombineWith("/session"); - var sessionJson = await loginUrl.PostToUrlAsync(new { - login = userName, - password = password, - }, requestFilter: req => { - req.Headers.Add("Cookie",cookies.GetAllCookies().Join(";")); - req.Headers.Add("X-CSRF-Token", csrf); - req.AddHeader("Accept",MimeTypes.Json); - }); + var sessionJson = await loginUrl.PostToUrlAsync(new { + login = userName, + password = password, + }, requestFilter: req => { + req.Headers.Add("Cookie",cookies.GetAllCookies().Join(";")); + req.Headers.Add("X-CSRF-Token", csrf); + req.AddHeader("Accept",MimeTypes.Json); + }, token: token); - var jsonObj = (Dictionary) JSON.parse(sessionJson); - if (jsonObj.TryGetValue("user", out var oUser) && - oUser is Dictionary user && - user.TryGetValue("username", out var oUserName)) - { - var authRepo = authService.TryResolve(); - var userAuth = authRepo.GetUserAuthByUserName((string) oUserName); + var jsonObj = (Dictionary) JSON.parse(sessionJson); + if (jsonObj.TryGetValue("user", out var oUser) && + oUser is Dictionary user && + user.TryGetValue("username", out var oUserName)) + { + var authRepo = authService.TryResolve(); + var userAuth = authRepo.GetUserAuthByUserName((string) oUserName); - var isMatch = userAuth.UserName == (string) oUserName; - if (isMatch) - { - authRepo.UpdateUserAuth(userAuth, userAuth, password); - return true; - } + var isMatch = userAuth.UserName == (string) oUserName; + if (isMatch) + { + authRepo.UpdateUserAuth(userAuth, userAuth, password); + return true; } - - return false; } - public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null) - { - var ret = base.IsAuthorized(session, tokens, request); - return ret; - } + return false; + } + + public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null) + { + var ret = base.IsAuthorized(session, tokens, request); + return ret; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/ClientRoutesService.cs b/TechStacks.ServiceInterface/ClientRoutesService.cs index 373c9be..182ce97 100644 --- a/TechStacks.ServiceInterface/ClientRoutesService.cs +++ b/TechStacks.ServiceInterface/ClientRoutesService.cs @@ -2,56 +2,55 @@ using ServiceStack.DataAnnotations; using ServiceStack.Script; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +[Exclude(Feature.Metadata)] +[FallbackRoute("/{PathInfo*}", Matches = "AcceptsHtml")] +public class FallbackForClientRoutes { - [Exclude(Feature.Metadata)] - [FallbackRoute("/{PathInfo*}", Matches = "AcceptsHtml")] - public class FallbackForClientRoutes - { - public string PathInfo { get; set; } - public ResponseStatus ResponseStatus { get; set; } - } + public string PathInfo { get; set; } + public ResponseStatus ResponseStatus { get; set; } +} - [Route("/ping")] - public class Ping {} +[Route("/ping")] +public class Ping {} - public class ClientRoutesService : Service +public class ClientRoutesService : Service +{ + //Return index.html for unmatched requests so routing is handled on client + public object Any(FallbackForClientRoutes request) { - //Return index.html for unmatched requests so routing is handled on client - public object Any(FallbackForClientRoutes request) - { - return Request.PathInfo == "/" - ? Request.GetPageResult("/static") - : Request.PathInfo.StartsWith("/p/") - ? Request.GetPageResult("/static-post") - : new HttpResult(VirtualFileSources.GetFile("index.html")); - } - - public object Any(Ping request) => "OK"; + return Request.PathInfo == "/" + ? Request.GetPageResult("/static") + : Request.PathInfo.StartsWith("/p/") + ? Request.GetPageResult("/static-post") + : new HttpResult(VirtualFileSources.GetFile("index.html")); } - //Client Routes to generate urls in sitemap.xml - [Route("/tech")] - public class ClientAllTechnologies {} + public object Any(Ping request) => "OK"; +} - [Route("/tech/{Slug}")] - public class ClientTechnology - { - public string Slug { get; set; } - } +//Client Routes to generate urls in sitemap.xml +[Route("/tech")] +public class ClientAllTechnologies {} + +[Route("/tech/{Slug}")] +public class ClientTechnology +{ + public string Slug { get; set; } +} - [Route("/stacks")] - public class ClientAllTechnologyStacks { } +[Route("/stacks")] +public class ClientAllTechnologyStacks { } - [Route("/stacks/{Slug}")] - public class ClientTechnologyStack - { - public string Slug { get; set; } - } +[Route("/stacks/{Slug}")] +public class ClientTechnologyStack +{ + public string Slug { get; set; } +} - [Route("/users/{UserName}")] - public class ClientUser - { - public string UserName { get; set; } - } +[Route("/users/{UserName}")] +public class ClientUser +{ + public string UserName { get; set; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/CustomAuthUserSession.cs b/TechStacks.ServiceInterface/CustomAuthUserSession.cs index 19ac290..f07c509 100644 --- a/TechStacks.ServiceInterface/CustomAuthUserSession.cs +++ b/TechStacks.ServiceInterface/CustomAuthUserSession.cs @@ -9,79 +9,77 @@ using ServiceStack.OrmLite; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public class CustomUserSession : AuthUserSession { - public class CustomUserSession : AuthUserSession - { - public string GithubProfileUrl { get; set; } + public string GithubProfileUrl { get; set; } - public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary authInfo) + public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary authInfo) + { + base.OnAuthenticated(authService, session, tokens, authInfo); + var appSettings = authService.TryResolve(); + var userAuthRepo = authService.TryResolve(); + var userAuth = userAuthRepo.GetUserAuth(session, tokens); + var dbFactory = authService.TryResolve(); + var userId = this.UserAuthId.ToInt(); + + foreach (var authTokens in session.ProviderOAuthAccess) { - base.OnAuthenticated(authService, session, tokens, authInfo); - var appSettings = authService.TryResolve(); - var userAuthRepo = authService.TryResolve(); - var userAuth = userAuthRepo.GetUserAuth(session, tokens); - var dbFactory = authService.TryResolve(); - var userId = this.UserAuthId.ToInt(); - - foreach (var authTokens in session.ProviderOAuthAccess) + if (authTokens.Provider.ToLower() == "github") { - if (authTokens.Provider.ToLower() == "github") + if (authInfo != null && authInfo.TryGetValue("avatar_url", out var avatarUrl)) { - if (authInfo != null && authInfo.TryGetValue("avatar_url", out var avatarUrl)) - { - GithubProfileUrl = avatarUrl; - } + GithubProfileUrl = avatarUrl; } + } - ProfileUrl = GithubProfileUrl; + ProfileUrl = GithubProfileUrl; - using (var db = dbFactory.OpenDbConnection()) + using (var db = dbFactory.OpenDbConnection()) + { + var userAuthInstance = db.Single(x => x.Id == userId); + if (userAuthInstance != null) { - var userAuthInstance = db.Single(x => x.Id == userId); - if (userAuthInstance != null) - { - userAuthInstance.DefaultProfileUrl = this.ProfileUrl; - userAuthInstance.IpAddress = authService.Request.UserHostAddress; + userAuthInstance.DefaultProfileUrl = this.ProfileUrl; + userAuthInstance.IpAddress = authService.Request.UserHostAddress; - db.Update(userAuthInstance); - } + db.Update(userAuthInstance); + } - var userActivity = db.SingleById(userAuth.Id); - if (userActivity == null) + var userActivity = db.SingleById(userAuth.Id); + if (userActivity == null) + { + db.Insert(new UserActivity { - db.Insert(new UserActivity - { - Id = userAuth.Id, - UserName = session.UserName, - Created = DateTime.UtcNow, - Modified = DateTime.UtcNow, - }); - } + Id = userAuth.Id, + UserName = session.UserName, + Created = DateTime.UtcNow, + Modified = DateTime.UtcNow, + }); } } } } +} - public class CustomUserAuth : UserAuth - { - public string DefaultProfileUrl { get; set; } - - public string IpAddress { get; set; } +public class CustomUserAuth : UserAuth +{ + public string DefaultProfileUrl { get; set; } - public string RefSource { get; set; } + public string IpAddress { get; set; } - public string RefUrn { get; set; } + public string RefSource { get; set; } - public DateTime? Banned { get; set; } + public string RefUrn { get; set; } - public string BannedBy { get; set; } + public DateTime? Banned { get; set; } - public string Notes { get; set; } + public string BannedBy { get; set; } - public DateTime? DisableEmails { get; set; } + public string Notes { get; set; } - public string CreatedBy { get; set; } - } -} + public DateTime? DisableEmails { get; set; } + public string CreatedBy { get; set; } +} \ No newline at end of file diff --git a/TechStacks.ServiceInterface/DataModel/EmailTemplate.cs b/TechStacks.ServiceInterface/DataModel/EmailTemplate.cs index 184deb4..d70041f 100644 --- a/TechStacks.ServiceInterface/DataModel/EmailTemplate.cs +++ b/TechStacks.ServiceInterface/DataModel/EmailTemplate.cs @@ -1,31 +1,30 @@ using System; using ServiceStack.DataAnnotations; -namespace TechStacks.ServiceInterface.DataModel +namespace TechStacks.ServiceInterface.DataModel; + +public class EmailTemplate { - public class EmailTemplate - { - [AutoIncrement] - public long Id { get; set; } + [AutoIncrement] + public long Id { get; set; } - public string TemplatePath { get; set; } + public string TemplatePath { get; set; } - public string ToEmail { get; set; } - public string ToName { get; set; } - public int[] ToUserIds { get; set; } + public string ToEmail { get; set; } + public string ToName { get; set; } + public int[] ToUserIds { get; set; } - public string FromEmail { get; set; } - public string FromName { get; set; } + public string FromEmail { get; set; } + public string FromName { get; set; } - public string CcEmail { get; set; } - public string CcName { get; set; } + public string CcEmail { get; set; } + public string CcName { get; set; } - public string Subject { get; set; } + public string Subject { get; set; } - public string Body { get; set; } + public string Body { get; set; } - public string BodyHtml { get; set; } + public string BodyHtml { get; set; } - public DateTime Created { get; set; } - } + public DateTime Created { get; set; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/DataModel/Notification.cs b/TechStacks.ServiceInterface/DataModel/Notification.cs index 6003bc3..d7c8fc1 100644 --- a/TechStacks.ServiceInterface/DataModel/Notification.cs +++ b/TechStacks.ServiceInterface/DataModel/Notification.cs @@ -1,42 +1,41 @@ using System; using ServiceStack.DataAnnotations; -namespace TechStacks.ServiceInterface.DataModel +namespace TechStacks.ServiceInterface.DataModel; + +public class Notification { - public class Notification - { - [AutoIncrement] - public long Id { get; set; } + [AutoIncrement] + public long Id { get; set; } - public string Event { get; set; } + public string Event { get; set; } - public long RefId { get; set; } + public long RefId { get; set; } - public string RefType { get; set; } + public string RefType { get; set; } - public string RefUrn { get; set; } + public string RefUrn { get; set; } - [PgSqlTextArray] - public string[] Operations { get; set; } + [PgSqlTextArray] + public string[] Operations { get; set; } - [PgSqlIntArray] - public int[] UserIds { get; set; } + [PgSqlIntArray] + public int[] UserIds { get; set; } - [PgSqlIntArray] - public int[] EmailedUserIds { get; set; } + [PgSqlIntArray] + public int[] EmailedUserIds { get; set; } - public long? EmailTemplateId { get; set; } + public long? EmailTemplateId { get; set; } - public DateTime Created { get; set; } + public DateTime Created { get; set; } - public DateTime? Started { get; set; } + public DateTime? Started { get; set; } - public DateTime? Completed { get; set; } + public DateTime? Completed { get; set; } - public DateTime? Failed { get; set; } + public DateTime? Failed { get; set; } - public string Error { get; set; } + public string Error { get; set; } - public string Notes { get; set; } - } + public string Notes { get; set; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/DataModel/SubscribePost.cs b/TechStacks.ServiceInterface/DataModel/SubscribePost.cs index a83d359..a2cdc40 100644 --- a/TechStacks.ServiceInterface/DataModel/SubscribePost.cs +++ b/TechStacks.ServiceInterface/DataModel/SubscribePost.cs @@ -1,26 +1,25 @@ using System; using ServiceStack.DataAnnotations; -namespace TechStacks.ServiceInterface.DataModel +namespace TechStacks.ServiceInterface.DataModel; + +public class SubscribePost { - public class SubscribePost - { - [AutoIncrement] - public long Id { get; set; } + [AutoIncrement] + public long Id { get; set; } - [Index] - public long PostId { get; set; } + [Index] + public long PostId { get; set; } - [Index] - public int UserId { get; set; } + [Index] + public int UserId { get; set; } - public string UserName { get; set; } + public string UserName { get; set; } - [Index] - public long OrganizationId { get; set; } + [Index] + public long OrganizationId { get; set; } - public DateTime? LastSynced { get; set; } + public DateTime? LastSynced { get; set; } - public DateTime Created { get; set; } - } + public DateTime Created { get; set; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/DataModel/SubscriptionPost.cs b/TechStacks.ServiceInterface/DataModel/SubscriptionPost.cs index 0ab1f49..557f9ab 100644 --- a/TechStacks.ServiceInterface/DataModel/SubscriptionPost.cs +++ b/TechStacks.ServiceInterface/DataModel/SubscriptionPost.cs @@ -1,34 +1,33 @@ using System; using ServiceStack.DataAnnotations; -namespace TechStacks.ServiceInterface.DataModel +namespace TechStacks.ServiceInterface.DataModel; + +/// +/// Task to send notifications to all subscribers +/// +public class SubscriptionPost { - /// - /// Task to send notifications to all subscribers - /// - public class SubscriptionPost - { - [AutoIncrement] - public long Id { get; set; } //= PostId + [AutoIncrement] + public long Id { get; set; } //= PostId - [Index] - public int OrganizationId { get; set; } + [Index] + public int OrganizationId { get; set; } - public DateTime Created { get; set; } + public DateTime Created { get; set; } - public DateTime? Completed { get; set; } + public DateTime? Completed { get; set; } - [PgSqlIntArray] - public int[] NotifiedUserIds { get; set; } + [PgSqlIntArray] + public int[] NotifiedUserIds { get; set; } - public int NotifyCount { get; set; } + public int NotifyCount { get; set; } - public long EmailTemplateId { get; set; } + public long EmailTemplateId { get; set; } - public DateTime? Failed { get; set; } + public DateTime? Failed { get; set; } - public string Error { get; set; } + public string Error { get; set; } - public string Notes { get; set; } - } + public string Notes { get; set; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Html/AppScriptMethods.cs b/TechStacks.ServiceInterface/Html/AppScriptMethods.cs index fd8f577..c57af2d 100644 --- a/TechStacks.ServiceInterface/Html/AppScriptMethods.cs +++ b/TechStacks.ServiceInterface/Html/AppScriptMethods.cs @@ -10,180 +10,179 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface.Html +namespace TechStacks.ServiceInterface.Html; + +public class PostView : Post +{ + public List TechnologySlugs { get; set; } + public string OrganizationSlug { get; set; } + public string OrganizationName { get; set; } + public string TimeAgo { get; set; } +} + +public class AppScriptMethods : ScriptMethods { - public class PostView : Post + ICacheClient Cache { get; } + IDbConnectionFactory DbFactory { get; } + + public AppScriptMethods(ICacheClient cache, IDbConnectionFactory dbFactory) { - public List TechnologySlugs { get; set; } - public string OrganizationSlug { get; set; } - public string OrganizationName { get; set; } - public string TimeAgo { get; set; } + Cache = cache; + DbFactory = dbFactory; } - public class AppScriptMethods : ScriptMethods + /*remove copy from SS v5.12.1+*/ + public IAuthSession sessionIfAuthenticated(ScriptScopeContext scope) { - ICacheClient Cache { get; } - IDbConnectionFactory DbFactory { get; } - - public AppScriptMethods(ICacheClient cache, IDbConnectionFactory dbFactory) - { - Cache = cache; - DbFactory = dbFactory; - } + var session = scope.GetRequest().GetSession(); + return session.IsAuthenticated + ? session + : null; + } - /*remove copy from SS v5.12.1+*/ - public IAuthSession sessionIfAuthenticated(ScriptScopeContext scope) - { - var session = scope.GetRequest().GetSession(); - return session.IsAuthenticated - ? session - : null; - } + public Dictionary techSlugs() + { + var to = Cache.GetOrCreate("tech:id:slugs", () => { + using var db = DbFactory.OpenDbConnection(); + var q = db.From() + .Select(x => new {x.Id, x.Slug}); + var map = db.Dictionary(q); + return map; + }); + return to; + } - public Dictionary techSlugs() - { - var to = Cache.GetOrCreate("tech:id:slugs", () => { - using var db = DbFactory.OpenDbConnection(); - var q = db.From() - .Select(x => new {x.Id, x.Slug}); - var map = db.Dictionary(q); - return map; - }); - return to; - } + public List techSlugs(IEnumerable techIds) => this.techSlugs().AsTechSlugs(techIds); - public List techSlugs(IEnumerable techIds) => this.techSlugs().AsTechSlugs(techIds); + public List queryPosts(int page) + { + var cacheKey = CreateCacheKey(page); + var posts = Cache.GetOrCreate(cacheKey, TimeSpan.FromMinutes(1), () => { + var techSlugs = this.techSlugs(); + using var db = DbFactory.OpenDbConnection(); + var q = db.From() + .Join() + .Where(x => x.Deleted == null && x.Hidden == null && (x.Status == null || x.Status != "closed")) + .OrderBy(x => x.Rank) + .Select((p, o) => new { + p.Id, + p.OrganizationId, + OrganizationSlug = o.Slug, + OrganizationName = o.Name, + p.UserId, + p.CategoryId, + p.Slug, + p.Title, + p.ImageUrl, + p.Labels, + p.TechnologyIds, + p.Points, + p.UpVotes, + p.DownVotes, + p.CommentsCount, + p.Created, + p.CreatedBy + }); + if (page > 0) + q.Skip(page * 30); + q.Take(30); + var results = db.Select(q); + results.ForEach(x => x.DecoratePost(techSlugs)); - public List queryPosts(int page) - { - var cacheKey = CreateCacheKey(page); - var posts = Cache.GetOrCreate(cacheKey, TimeSpan.FromMinutes(1), () => { - var techSlugs = this.techSlugs(); - using var db = DbFactory.OpenDbConnection(); - var q = db.From() - .Join() - .Where(x => x.Deleted == null && x.Hidden == null && (x.Status == null || x.Status != "closed")) - .OrderBy(x => x.Rank) - .Select((p, o) => new { - p.Id, - p.OrganizationId, - OrganizationSlug = o.Slug, - OrganizationName = o.Name, - p.UserId, - p.CategoryId, - p.Slug, - p.Title, - p.ImageUrl, - p.Labels, - p.TechnologyIds, - p.Points, - p.UpVotes, - p.DownVotes, - p.CommentsCount, - p.Created, - p.CreatedBy - }); - if (page > 0) - q.Skip(page * 30); - q.Take(30); - var results = db.Select(q); - results.ForEach(x => x.DecoratePost(techSlugs)); + return results; + }); + return posts; + } - return results; - }); - return posts; - } + public PostView queryPost(int postId) + { + var cacheKey = UrnId.Create($"{postId}"); + var post = Cache.GetOrCreate(cacheKey, () => { + var techSlugs = this.techSlugs(); + using var db = DbFactory.OpenDbConnection(); + var q = db.From() + .Join() + .Where(x => x.Id == postId) + .And(x => x.Deleted == null && x.Hidden == null && (x.Status == null || x.Status != "closed")) + .Select((p, o) => new { + p.Id, + p.OrganizationId, + OrganizationSlug = o.Slug, + OrganizationName = o.Name, + p.UserId, + p.CategoryId, + p.Slug, + p.Title, + p.ImageUrl, + p.Labels, + p.TechnologyIds, + p.Points, + p.UpVotes, + p.DownVotes, + p.CommentsCount, + p.Created, + p.CreatedBy, + p.Url, + p.Content, + p.ContentHtml, + }); + var result = db.Single(q); + result.DecoratePost(techSlugs); + return result; + }); + return post; + } - public PostView queryPost(int postId) - { - var cacheKey = UrnId.Create($"{postId}"); - var post = Cache.GetOrCreate(cacheKey, () => { - var techSlugs = this.techSlugs(); - using var db = DbFactory.OpenDbConnection(); - var q = db.From() - .Join() - .Where(x => x.Id == postId) - .And(x => x.Deleted == null && x.Hidden == null && (x.Status == null || x.Status != "closed")) - .Select((p, o) => new { - p.Id, - p.OrganizationId, - OrganizationSlug = o.Slug, - OrganizationName = o.Name, - p.UserId, - p.CategoryId, - p.Slug, - p.Title, - p.ImageUrl, - p.Labels, - p.TechnologyIds, - p.Points, - p.UpVotes, - p.DownVotes, - p.CommentsCount, - p.Created, - p.CreatedBy, - p.Url, - p.Content, - p.ContentHtml, - }); - var result = db.Single(q); - result.DecoratePost(techSlugs); - return result; - }); - return post; - } + // Currently Cache.FlushAll() is called on post updates + public static string CreateCacheKey(int page = 0) => UrnId.Create("news", $"{page}"); +} - // Currently Cache.FlushAll() is called on post updates - public static string CreateCacheKey(int page = 0) => UrnId.Create("news", $"{page}"); +public static class AppUtils +{ + public static void DecoratePost(this PostView post, Dictionary techSlugs) + { + post.TechnologySlugs = post.TechnologyIds?.Length > 0 + ? techSlugs.AsTechSlugs(post.TechnologyIds) + : TypeConstants.EmptyList; + post.TimeAgo = DateTime.UtcNow.Subtract(post.Created).TimeAgo(); } - public static class AppUtils + public static List AsTechSlugs(this Dictionary techSlugs, IEnumerable techIds) { - public static void DecoratePost(this PostView post, Dictionary techSlugs) + var to = new List(); + foreach (var techId in techIds) { - post.TechnologySlugs = post.TechnologyIds?.Length > 0 - ? techSlugs.AsTechSlugs(post.TechnologyIds) - : TypeConstants.EmptyList; - post.TimeAgo = DateTime.UtcNow.Subtract(post.Created).TimeAgo(); + if (techSlugs.TryGetValue(techId, out var slug)) + to.Add(slug); } - public static List AsTechSlugs(this Dictionary techSlugs, IEnumerable techIds) - { - var to = new List(); - foreach (var techId in techIds) - { - if (techSlugs.TryGetValue(techId, out var slug)) - to.Add(slug); - } - - return to; - } + return to; + } - public static string TimeAgo(this TimeSpan timeSpan) - { - if (timeSpan <= TimeSpan.Zero) - return "just now"; - if (timeSpan <= TimeSpan.FromSeconds(60)) - return $"{timeSpan.Seconds} seconds ago"; - if (timeSpan <= TimeSpan.FromMinutes(60)) - return timeSpan.Minutes > 1 - ? $"about {timeSpan.Minutes} minutes ago" - : "about a minute ago"; - if (timeSpan <= TimeSpan.FromHours(24)) - return timeSpan.Hours > 1 - ? $"about {timeSpan.Hours} hours ago" - : "about an hour ago"; - if (timeSpan <= TimeSpan.FromDays(30)) - return timeSpan.Days > 1 - ? $"about {timeSpan.Days} days ago" - : "yesterday"; - if (timeSpan <= TimeSpan.FromDays(365)) - return timeSpan.Days / 30 > 1 - ? $"about {timeSpan.Days / 30} months ago" - : "about a month ago"; - return timeSpan.Days / 365 > 1 - ? $"about {timeSpan.Days / 365} years ago" - : "about a year ago"; - } + public static string TimeAgo(this TimeSpan timeSpan) + { + if (timeSpan <= TimeSpan.Zero) + return "just now"; + if (timeSpan <= TimeSpan.FromSeconds(60)) + return $"{timeSpan.Seconds} seconds ago"; + if (timeSpan <= TimeSpan.FromMinutes(60)) + return timeSpan.Minutes > 1 + ? $"about {timeSpan.Minutes} minutes ago" + : "about a minute ago"; + if (timeSpan <= TimeSpan.FromHours(24)) + return timeSpan.Hours > 1 + ? $"about {timeSpan.Hours} hours ago" + : "about an hour ago"; + if (timeSpan <= TimeSpan.FromDays(30)) + return timeSpan.Days > 1 + ? $"about {timeSpan.Days} days ago" + : "yesterday"; + if (timeSpan <= TimeSpan.FromDays(365)) + return timeSpan.Days / 30 > 1 + ? $"about {timeSpan.Days / 30} months ago" + : "about a month ago"; + return timeSpan.Days / 365 > 1 + ? $"about {timeSpan.Days / 365} years ago" + : "about a year ago"; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/IMarkdownProvider.cs b/TechStacks.ServiceInterface/IMarkdownProvider.cs index 0c619be..0917d9f 100644 --- a/TechStacks.ServiceInterface/IMarkdownProvider.cs +++ b/TechStacks.ServiceInterface/IMarkdownProvider.cs @@ -5,61 +5,60 @@ using ServiceStack; using ServiceStack.Logging; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public interface IMarkdownProvider +{ + Task TransformAsync(string markdown, string authToken = null); +} + +public class GitHubApiMarkdownProvider : IMarkdownProvider { - public interface IMarkdownProvider + public static ILog Log = LogManager.GetLogger(typeof(GitHubApiMarkdownProvider)); + + public const string GithubApiBaseUrl = "https://api.github.com"; + public const string UserAgent = "TechStacks"; + + public string Base64UserPass { get; set; } + + public GitHubApiMarkdownProvider(string base64UserPass = null) { - Task TransformAsync(string markdown, string authToken = null); + Base64UserPass = base64UserPass; } - public class GitHubApiMarkdownProvider : IMarkdownProvider + public async Task TransformAsync(string markdown, string authToken = null) { - public static ILog Log = LogManager.GetLogger(typeof(GitHubApiMarkdownProvider)); + if (string.IsNullOrEmpty(markdown)) + return null; - public const string GithubApiBaseUrl = "https://api.github.com"; - public const string UserAgent = "TechStacks"; - - public string Base64UserPass { get; set; } + try + { + var html = await $"{GithubApiBaseUrl}/markdown/raw" + .PostStringToUrlAsync(markdown, + contentType: MimeTypes.PlainText, + requestFilter: req => ConfigureRequest(req, authToken)); - public GitHubApiMarkdownProvider(string base64UserPass = null) + return "
" + html + "
"; + } + catch (Exception ex) { - Base64UserPass = base64UserPass; + Log.Error("GitHub failed to convert markdown\n" + markdown.SafeSubstring(0, 500), ex); + var html = MarkdownConfig.Transform(markdown); + return "
" + html + "
"; } + } - public async Task TransformAsync(string markdown, string authToken = null) + private void ConfigureRequest(HttpRequestMessage req, string githubToken) + { + if (!string.IsNullOrEmpty(githubToken)) { - if (string.IsNullOrEmpty(markdown)) - return null; - - try - { - var html = await $"{GithubApiBaseUrl}/markdown/raw" - .PostStringToUrlAsync(markdown, - contentType: MimeTypes.PlainText, - requestFilter: req => ConfigureRequest(req, authToken)); - - return "
" + html + "
"; - } - catch (Exception ex) - { - Log.Error("GitHub failed to convert markdown\n" + markdown.SafeSubstring(0, 500), ex); - var html = MarkdownConfig.Transform(markdown); - return "
" + html + "
"; - } + req.AddHeader(HttpHeaders.Authorization, "Token " + githubToken); } - - private void ConfigureRequest(HttpRequestMessage req, string githubToken) + else if (!string.IsNullOrEmpty(Base64UserPass)) { - if (!string.IsNullOrEmpty(githubToken)) - { - req.AddHeader(HttpHeaders.Authorization, "Token " + githubToken); - } - else if (!string.IsNullOrEmpty(Base64UserPass)) - { - req.AddHeader(HttpHeaders.Authorization,"Basic " + Base64UserPass); - } - - req.AddHeader(HttpHeaders.UserAgent,UserAgent); + req.AddHeader(HttpHeaders.Authorization,"Basic " + Base64UserPass); } + + req.AddHeader(HttpHeaders.UserAgent,UserAgent); } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/ITwitterUpdates.cs b/TechStacks.ServiceInterface/ITwitterUpdates.cs index 08f94e9..8892e60 100644 --- a/TechStacks.ServiceInterface/ITwitterUpdates.cs +++ b/TechStacks.ServiceInterface/ITwitterUpdates.cs @@ -1,8 +1,7 @@ -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public interface ITwitterUpdates { - public interface ITwitterUpdates - { - string BaseUrl { get; set; } - string Tweet(string status); - } + string BaseUrl { get; set; } + string Tweet(string status); } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/ImgurExtensions.cs b/TechStacks.ServiceInterface/ImgurExtensions.cs index 857064d..43019e7 100644 --- a/TechStacks.ServiceInterface/ImgurExtensions.cs +++ b/TechStacks.ServiceInterface/ImgurExtensions.cs @@ -6,107 +6,106 @@ using ServiceStack; using ServiceStack.Web; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public static class ImgurExtensions { - public static class ImgurExtensions + public static string UploadToImgur(this IHttpFile image, string imgurClientId, string paramName, + int? minWidth = null, int? minHeight = null, + int? maxWidth = null, int? maxHeight = null) { - public static string UploadToImgur(this IHttpFile image, string imgurClientId, string paramName, - int? minWidth = null, int? minHeight = null, - int? maxWidth = null, int? maxHeight = null) + try { - try - { - using var content = new MultipartFormDataContent(); - var imgurClient = HttpUtils.Create(); + using var content = new MultipartFormDataContent(); + var imgurClient = HttpUtils.Create(); - var reqMsg = new HttpRequestMessage(HttpMethod.Post, "https://api.imgur.com/3/image"); - reqMsg.Headers.Add(HttpHeaders.Authorization, $"Client-ID {imgurClientId}"); - content.AddFile("image", image.FileName, image.InputStream, image.ContentType); - reqMsg.Content = content; - var responseMessage = imgurClient.Send(reqMsg); + var reqMsg = new HttpRequestMessage(HttpMethod.Post, "https://api.imgur.com/3/image"); + reqMsg.Headers.Add(HttpHeaders.Authorization, $"Client-ID {imgurClientId}"); + content.AddFile("image", image.FileName, image.InputStream, image.ContentType); + reqMsg.Content = content; + var responseMessage = imgurClient.Send(reqMsg); - try - { - var imgurRes = responseMessage.ReadToEnd(); + try + { + var imgurRes = responseMessage.ReadToEnd(); - var resText = imgurRes; - var jsonRes = JSON.parse(resText); - if (jsonRes is Dictionary jsonObj) + var resText = imgurRes; + var jsonRes = JSON.parse(resText); + if (jsonRes is Dictionary jsonObj) + { + if (jsonObj["data"] is Dictionary data) { - if (jsonObj["data"] is Dictionary data) + if (minWidth != null || maxWidth != null || minHeight != null || maxHeight != null) { - if (minWidth != null || maxWidth != null || minHeight != null || maxHeight != null) - { - var width = (int) data["width"]; - var height = (int) data["height"]; + var width = (int) data["width"]; + var height = (int) data["height"]; - if (width < minWidth || height < minHeight) - throw new ArgumentException($"Minimum Dimensions {minWidth} x {minHeight}", - paramName); + if (width < minWidth || height < minHeight) + throw new ArgumentException($"Minimum Dimensions {minWidth} x {minHeight}", + paramName); - if (width > maxWidth || height > maxHeight) - throw new ArgumentException($"Maximum Dimensions {maxWidth} x {maxHeight}", - paramName); - } + if (width > maxWidth || height > maxHeight) + throw new ArgumentException($"Maximum Dimensions {maxWidth} x {maxHeight}", + paramName); + } - if (data["link"] is string link && !string.IsNullOrEmpty(link)) - { - return link.Replace("\\/", "/"); - } + if (data["link"] is string link && !string.IsNullOrEmpty(link)) + { + return link.Replace("\\/", "/"); } } - } - catch (WebException e) - { - var errorMessage = GetImgurErrorMessage(e.GetResponseBody()); - if (errorMessage != null) - throw new ArgumentException(errorMessage); - - throw; - } - - throw new ArgumentException("Invalid Upload Image Response", paramName); + } - catch (Exception ex) + catch (WebException e) { - throw new ArgumentException("Could not upload image: " + ex.Message, paramName, ex); + var errorMessage = GetImgurErrorMessage(e.GetResponseBody()); + if (errorMessage != null) + throw new ArgumentException(errorMessage); + + throw; } - } - private static string GetImgurErrorMessage(string body) + throw new ArgumentException("Invalid Upload Image Response", paramName); + } + catch (Exception ex) { - if (body == null || !body.StartsWith("{")) - return null; + throw new ArgumentException("Could not upload image: " + ex.Message, paramName, ex); + } + } - try + private static string GetImgurErrorMessage(string body) + { + if (body == null || !body.StartsWith("{")) + return null; + + try + { + var obj = JSON.parse(body); + if (obj is Dictionary response) { - var obj = JSON.parse(body); - if (obj is Dictionary response) + if (response.TryGetValue("data", out var data) && data is Dictionary oData) { - if (response.TryGetValue("data", out var data) && data is Dictionary oData) + if (oData.TryGetValue("error", out var error) && error is Dictionary oError) { - if (oData.TryGetValue("error", out var error) && error is Dictionary oError) - { - var code = 0; - string type = null; - string message = null; + var code = 0; + string type = null; + string message = null; - if (oError.TryGetValue("code", out var oCode)) - code = (int) oCode; - if (oError.TryGetValue("type", out var oType)) - type = (string) oType; - if (oError.TryGetValue("message", out var oMessage)) - message = (string) oMessage; + if (oError.TryGetValue("code", out var oCode)) + code = (int) oCode; + if (oError.TryGetValue("type", out var oType)) + type = (string) oType; + if (oError.TryGetValue("message", out var oMessage)) + message = (string) oMessage; - return $"{type} ({code}): {message}"; - } + return $"{type} ({code}): {message}"; } } } - catch (Exception) { } + } + catch (Exception) { } - return null; - } - } + return null; + } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Import/DiscourseSyncServices.cs b/TechStacks.ServiceInterface/Import/DiscourseSyncServices.cs index fd31394..9d9be1a 100644 --- a/TechStacks.ServiceInterface/Import/DiscourseSyncServices.cs +++ b/TechStacks.ServiceInterface/Import/DiscourseSyncServices.cs @@ -11,342 +11,342 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface.Import +namespace TechStacks.ServiceInterface.Import; + +public class DiscourseSite { - public class DiscourseSite - { - public string RefSource { get; set; } - public string RefSlug { get; set; } - public int OwnerId { get; set; } - public int OrganizationId { get; set; } - public string UserName { get; set; } - public Dictionary CategoryImportRules { get; set; } - public string ProfileUrlFormat { get; set; } - public string ConnectionString { get; set; } - } + public string RefSource { get; set; } + public string RefSlug { get; set; } + public int OwnerId { get; set; } + public int OrganizationId { get; set; } + public string UserName { get; set; } + public Dictionary CategoryImportRules { get; set; } + public string ProfileUrlFormat { get; set; } + public string ConnectionString { get; set; } +} + +public class DiscourseImportRule +{ + public int? CategoryId { get; set; } + public string Category { get; set; } + public PostType? PostType { get; set; } + public long? TechnologyId { get; set; } +} + +[Alias("users")] +public class DiscourseUser +{ + public int Id { get; set; } + public string Username { get; set; } + public string Email { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public bool Approved { get; set; } + public int? UploadedAvatarId { get; set; } +} + +[Alias("categories")] +public class DiscourseCategory +{ + public string ParentName { get; set; } + public int ParentId { get; set; } + public string CategoryName { get; set; } + public int CategoryId { get; set; } + public string CategorySlug { get; set; } + public string CategoryColor { get; set; } +} + +[Alias("topics")] +public class DiscourseTopic +{ + public long Id { get; set; } + public int CategoryId { get; set; } + public string CategoryName { get; set; } + public string Title { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public DateTime LastPostedAt { get; set; } + public long Views { get; set; } + public int UserId { get; set; } + public bool Visible { get; set; } + public int WordCount { get; set; } +} + +[Alias("posts")] +public class DiscoursePost +{ + public long Id { get; set; } + public int UserId { get; set; } + public long TopicId { get; set; } + public int PostNumber { get; set; } + public int? ReplyToPostNumber { get; set; } + public string Raw { get; set; } + public string Cooked { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public bool Hidden { get; set; } + public DateTime HiddenAt { get; set; } + public int WordCount { get; set; } +} + +[RequiredRole(nameof(RoleNames.Admin))] +public class DiscourseSyncServices : Service +{ + public DiscourseSite Site { get; set; } - public class DiscourseImportRule + public object Any(SyncDiscourseSite request) { - public int? CategoryId { get; set; } - public string Category { get; set; } - public PostType? PostType { get; set; } - public long? TechnologyId { get; set; } - } + if (!SiteConfigs.TryGetValue(request.Site, out var site)) + throw HttpError.NotFound("Site not found"); - [Alias("users")] - public class DiscourseUser - { - public int Id { get; set; } - public string Username { get; set; } - public string Email { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - public bool Approved { get; set; } - public int? UploadedAvatarId { get; set; } - } + Site = site; - [Alias("categories")] - public class DiscourseCategory - { - public string ParentName { get; set; } - public int ParentId { get; set; } - public string CategoryName { get; set; } - public int CategoryId { get; set; } - public string CategorySlug { get; set; } - public string CategoryColor { get; set; } - } + var now = DateTime.UtcNow; + var response = new SyncDiscourseSiteResponse(); - [Alias("topics")] - public class DiscourseTopic - { - public long Id { get; set; } - public int CategoryId { get; set; } - public string CategoryName { get; set; } - public string Title { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - public DateTime LastPostedAt { get; set; } - public long Views { get; set; } - public int UserId { get; set; } - public bool Visible { get; set; } - public int WordCount { get; set; } - } + using (var dbForums = OpenSiteDbConnection()) + { + response.UserLogs = ImportForumUsers(Db, dbForums); + response.PostsLogs = ImportForumPosts(Db, dbForums); + } - [Alias("posts")] - public class DiscoursePost - { - public long Id { get; set; } - public int UserId { get; set; } - public long TopicId { get; set; } - public int PostNumber { get; set; } - public int? ReplyToPostNumber { get; set; } - public string Raw { get; set; } - public string Cooked { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - public bool Hidden { get; set; } - public DateTime HiddenAt { get; set; } - public int WordCount { get; set; } + response.TimeTaken = (DateTime.UtcNow - now).ToString(); + return response; } - [RequiredRole(nameof(RoleNames.Admin))] - public class DiscourseSyncServices : Service - { - public DiscourseSite Site { get; set; } + public IDbConnectionFactory DbFactory { get; set; } + + public IDbConnection OpenSiteDbConnection() => ((OrmLiteConnectionFactory)DbFactory) + .OpenDbConnectionString(Site.ConnectionString, "PostgreSqlDialect"); - public object Any(SyncDiscourseSite request) + public static Dictionary SiteConfigs = new Dictionary + { + { "ss-forums", new DiscourseSite + { + RefSource = "ss-forums", + RefSlug = "servicestack", + UserName = "ServiceStack", + OrganizationId = 1, + OwnerId = 8, + CategoryImportRules = new Dictionary + { + { "News", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Announcement } }, + { "Announcements", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Announcement } }, + { "Uncategorized", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Question } }, + { "Archive", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Question } }, + { "Meta", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Question } }, + { "Showcase", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Showcase } }, + { "Live Demos", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Showcase } }, + + { "Android", new DiscourseImportRule { Category = "Clients", TechnologyId = 242} }, + { "iOS", new DiscourseImportRule { Category = "Clients", TechnologyId = 241} }, + { "Silverlight", new DiscourseImportRule { Category = "Clients", TechnologyId = 243} }, + { "PCL", new DiscourseImportRule { Category = "Clients" } }, + { "C#", new DiscourseImportRule { Category = "Clients", TechnologyId = 243 } }, + { "Java", new DiscourseImportRule { Category = "Clients", TechnologyId = 19 } }, + { "JavaScript", new DiscourseImportRule { Category = "Clients", TechnologyId = 37 } }, + { "Swift", new DiscourseImportRule { Category = "Clients", TechnologyId = 24 } }, + { "TypeScript", new DiscourseImportRule { Category = "Clients", TechnologyId = 173 } }, + { "VB.NET", new DiscourseImportRule { Category = "Clients", TechnologyId = 246 } }, + { "F#", new DiscourseImportRule { Category = "Clients", TechnologyId = 17 } }, + { "Kotlin", new DiscourseImportRule { Category = "Clients", TechnologyId = 191 } }, + + { "AngularJS", new DiscourseImportRule { Category = "Single Page Apps", TechnologyId = 7 } }, + { "React.js", new DiscourseImportRule { Category = "Single Page Apps", TechnologyId = 8 } }, + { "Vue", new DiscourseImportRule { Category = "Single Page Apps", TechnologyId = 201 } }, + { "Bundler", new DiscourseImportRule { Category = "Single Page Apps" } }, + + { "Azure", new DiscourseImportRule { Category = "ServiceStack.Azure", TechnologyId = 73 } }, + { "Dynamo DB", new DiscourseImportRule { Category = "ServiceStack.Aws", TechnologyId = 63 } }, + { "AWS", new DiscourseImportRule { Category = "ServiceStack.Aws", TechnologyId = 244 } }, + + { "Mono", new DiscourseImportRule { Category = "Hosting", TechnologyId = 124 } }, + { ".NET Core", new DiscourseImportRule { Category = "Hosting", TechnologyId = 239 } }, + { "Self Hosting", new DiscourseImportRule { Category = "Hosting", TechnologyId = 238 } }, + { "WebForms", new DiscourseImportRule { Category = "Hosting", TechnologyId = 235 } }, + + { "MySql", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 38 } }, + { "PostgreSQL", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 2 } }, + { "SQL Server", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 100 } }, + { "Sqlite", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 10 } }, + { "Oracle", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 82 } }, + + { "Message Pack", new DiscourseImportRule { Category = "Serialization", TechnologyId = 78 } }, + { "Protocol Buffers", new DiscourseImportRule { Category = "Serialization", TechnologyId = 77 } }, + { "SOAP", new DiscourseImportRule { Category = "Serialization", TechnologyId = 245 } }, + + { "Rabbit MQ", new DiscourseImportRule { Category = "Messaging (MQ)", TechnologyId = 108 } }, + { "Redis MQ", new DiscourseImportRule { Category = "Messaging (MQ)", TechnologyId = 9 } }, + + { "Stripe", new DiscourseImportRule { Category = "ServiceStack.Stripe", TechnologyId = 70 } }, + + { "Messaging", new DiscourseImportRule { Category = "Messaging (MQ)" } }, + { "ServiceStack.Gap", new DiscourseImportRule { Category = "ServiceStack" } }, + { "Encryption", new DiscourseImportRule { Category = "Encrypted Messaging" } }, + { "MyGet pre-releases", new DiscourseImportRule { Category = "MyGet" } }, + { "Markdown", new DiscourseImportRule { Category = "ServiceStack" } }, + { "JSON", new DiscourseImportRule { Category = "ServiceStack.Text" } }, + { "CSV", new DiscourseImportRule { Category = "ServiceStack.Text" } }, + { "JSV", new DiscourseImportRule { Category = "ServiceStack.Text" } }, + { "HTTP Utils", new DiscourseImportRule { Category = "ServiceStack.Text" } }, + { "Templates", new DiscourseImportRule { Category = "ServiceStack Templates" } }, + }, + ConnectionString = Environment.GetEnvironmentVariable("FORUMS_DB"), + ProfileUrlFormat = "https://forums.servicestack.net/user_avatar/forums.servicestack.net/{0}/100/{1}.png", + }} + }; + + public Category ToCategory(DiscourseCategory c) => new Category + { + Name = c.CategoryName, + Slug = c.CategorySlug.GenerateSlug(), + OrganizationId = Site.OrganizationId, + Color = "#" + c.CategoryColor, + RefId = c.CategoryId, + RefSource = Site.RefSource, //overridden with Technology.Id/Technology when exists + RefUrn = $"urn:category:{c.CategoryId}", + Created = DateTime.Now, + CreatedBy = Site.UserName, + Modified = DateTime.Now, + ModifiedBy = Site.UserName, + }; + + public Post ApplyRules(Post post, string categoryName) + { + if (Site.CategoryImportRules.TryGetValue(categoryName, out var importRule)) { - if (!SiteConfigs.TryGetValue(request.Site, out var site)) - throw HttpError.NotFound("Site not found"); + if (importRule.CategoryId != null) + post.CategoryId = importRule.CategoryId.Value; - Site = site; + if (importRule.PostType != null) + post.Type = importRule.PostType.Value; - var now = DateTime.UtcNow; - var response = new SyncDiscourseSiteResponse(); + if (importRule.TechnologyId != null) + post.TechnologyIds = new [] { (int)importRule.TechnologyId.Value }; + } - using (var dbForums = OpenSiteDbConnection()) - { - response.UserLogs = ImportForumUsers(Db, dbForums); - response.PostsLogs = ImportForumPosts(Db, dbForums); - } + return post; + } - response.TimeTaken = (DateTime.UtcNow - now).ToString(); - return response; - } + public Post ToPost(DiscourseTopic topic, int categoryId, int userId, string userName) => new Post + { + Type = PostType.Question, + Title = topic.Title, + Slug = topic.Title.GenerateSlug(), + OrganizationId = Site.OrganizationId, + CategoryId = categoryId, + UserId = userId, + Created = topic.CreatedAt, + CreatedBy = userName, + Modified = topic.UpdatedAt, + ModifiedBy = userName, + LastCommentDate = topic.LastPostedAt, + Views = topic.Views, + RefId = topic.Id, + RefSource = Site.RefSource, + RefUrn = $"urn:topic:{topic.Id}", + Hidden = !topic.Visible ? topic.UpdatedAt : (DateTime?)null, + HiddenBy = !topic.Visible ? userName : null, + WordCount = topic.WordCount, + }; + + public PostComment ToPostComment(DiscoursePost post, long postId, int userId, string userName, long? replyId = null) => new PostComment + { + PostId = postId, + UserId = userId, + Created = post.CreatedAt, + CreatedBy = userName, + Modified = post.UpdatedAt, + ReplyId = replyId, + RefId = post.Id, + Content = post.Raw.StripHtml(), + ContentHtml = post.Cooked, + RefSource = Site.RefSource, + RefUrn = $"urn:post:{post.Id}", + Hidden = post.Hidden ? post.UpdatedAt : (DateTime?)null, + HiddenBy = post.Hidden ? userName : null, + WordCount = post.WordCount, + }; + + public List ImportForumUsers(IDbConnection db, IDbConnection dbForums) + { + var logs = new List(); - public IDbConnectionFactory DbFactory { get; set; } + var maxUserId = db.Scalar(db.From() + .Where(x => x.RefSource == Site.RefSource) + .Select(x => Sql.Max(x.RefId))); - public IDbConnection OpenSiteDbConnection() => ((OrmLiteConnectionFactory)DbFactory) - .OpenDbConnectionString(Site.ConnectionString, "PostgreSqlDialect"); + var forumUsers = dbForums.Select(x => x.Approved && x.Id > maxUserId); - public static Dictionary SiteConfigs = new Dictionary + foreach (var forumUser in forumUsers) { - { "ss-forums", new DiscourseSite + var lowerEmail = forumUser.Email.ToLower(); + var existingUser = db.Single(x => x.Email.ToLower() == lowerEmail); + + if (existingUser != null) { - RefSource = "ss-forums", - RefSlug = "servicestack", - UserName = "ServiceStack", - OrganizationId = 1, - OwnerId = 8, - CategoryImportRules = new Dictionary + logs.Add($"Match found: {existingUser.UserName} <=> {forumUser.Username}"); + db.UpdateOnly(() => new CustomUserAuth { - { "News", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Announcement } }, - { "Announcements", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Announcement } }, - { "Uncategorized", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Question } }, - { "Archive", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Question } }, - { "Meta", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Question } }, - { "Showcase", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Showcase } }, - { "Live Demos", new DiscourseImportRule { Category = "ServiceStack", PostType = PostType.Showcase } }, - - { "Android", new DiscourseImportRule { Category = "Clients", TechnologyId = 242} }, - { "iOS", new DiscourseImportRule { Category = "Clients", TechnologyId = 241} }, - { "Silverlight", new DiscourseImportRule { Category = "Clients", TechnologyId = 243} }, - { "PCL", new DiscourseImportRule { Category = "Clients" } }, - { "C#", new DiscourseImportRule { Category = "Clients", TechnologyId = 243 } }, - { "Java", new DiscourseImportRule { Category = "Clients", TechnologyId = 19 } }, - { "JavaScript", new DiscourseImportRule { Category = "Clients", TechnologyId = 37 } }, - { "Swift", new DiscourseImportRule { Category = "Clients", TechnologyId = 24 } }, - { "TypeScript", new DiscourseImportRule { Category = "Clients", TechnologyId = 173 } }, - { "VB.NET", new DiscourseImportRule { Category = "Clients", TechnologyId = 246 } }, - { "F#", new DiscourseImportRule { Category = "Clients", TechnologyId = 17 } }, - { "Kotlin", new DiscourseImportRule { Category = "Clients", TechnologyId = 191 } }, - - { "AngularJS", new DiscourseImportRule { Category = "Single Page Apps", TechnologyId = 7 } }, - { "React.js", new DiscourseImportRule { Category = "Single Page Apps", TechnologyId = 8 } }, - { "Vue", new DiscourseImportRule { Category = "Single Page Apps", TechnologyId = 201 } }, - { "Bundler", new DiscourseImportRule { Category = "Single Page Apps" } }, - - { "Azure", new DiscourseImportRule { Category = "ServiceStack.Azure", TechnologyId = 73 } }, - { "Dynamo DB", new DiscourseImportRule { Category = "ServiceStack.Aws", TechnologyId = 63 } }, - { "AWS", new DiscourseImportRule { Category = "ServiceStack.Aws", TechnologyId = 244 } }, - - { "Mono", new DiscourseImportRule { Category = "Hosting", TechnologyId = 124 } }, - { ".NET Core", new DiscourseImportRule { Category = "Hosting", TechnologyId = 239 } }, - { "Self Hosting", new DiscourseImportRule { Category = "Hosting", TechnologyId = 238 } }, - { "WebForms", new DiscourseImportRule { Category = "Hosting", TechnologyId = 235 } }, - - { "MySql", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 38 } }, - { "PostgreSQL", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 2 } }, - { "SQL Server", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 100 } }, - { "Sqlite", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 10 } }, - { "Oracle", new DiscourseImportRule { Category = "ServiceStack.OrmLite", TechnologyId = 82 } }, - - { "Message Pack", new DiscourseImportRule { Category = "Serialization", TechnologyId = 78 } }, - { "Protocol Buffers", new DiscourseImportRule { Category = "Serialization", TechnologyId = 77 } }, - { "SOAP", new DiscourseImportRule { Category = "Serialization", TechnologyId = 245 } }, - - { "Rabbit MQ", new DiscourseImportRule { Category = "Messaging (MQ)", TechnologyId = 108 } }, - { "Redis MQ", new DiscourseImportRule { Category = "Messaging (MQ)", TechnologyId = 9 } }, - - { "Stripe", new DiscourseImportRule { Category = "ServiceStack.Stripe", TechnologyId = 70 } }, - - { "Messaging", new DiscourseImportRule { Category = "Messaging (MQ)" } }, - { "ServiceStack.Gap", new DiscourseImportRule { Category = "ServiceStack" } }, - { "Encryption", new DiscourseImportRule { Category = "Encrypted Messaging" } }, - { "MyGet pre-releases", new DiscourseImportRule { Category = "MyGet" } }, - { "Markdown", new DiscourseImportRule { Category = "ServiceStack" } }, - { "JSON", new DiscourseImportRule { Category = "ServiceStack.Text" } }, - { "CSV", new DiscourseImportRule { Category = "ServiceStack.Text" } }, - { "JSV", new DiscourseImportRule { Category = "ServiceStack.Text" } }, - { "HTTP Utils", new DiscourseImportRule { Category = "ServiceStack.Text" } }, - { "Templates", new DiscourseImportRule { Category = "ServiceStack Templates" } }, - }, - ConnectionString = Environment.GetEnvironmentVariable("FORUMS_DB"), - ProfileUrlFormat = "https://forums.servicestack.net/user_avatar/forums.servicestack.net/{0}/100/{1}.png", - }} - }; - - public Category ToCategory(DiscourseCategory c) => new Category - { - Name = c.CategoryName, - Slug = c.CategorySlug.GenerateSlug(), - OrganizationId = Site.OrganizationId, - Color = "#" + c.CategoryColor, - RefId = c.CategoryId, - RefSource = Site.RefSource, //overridden with Technology.Id/Technology when exists - RefUrn = $"urn:category:{c.CategoryId}", - Created = DateTime.Now, - CreatedBy = Site.UserName, - Modified = DateTime.Now, - ModifiedBy = Site.UserName, - }; - - public Post ApplyRules(Post post, string categoryName) - { - if (Site.CategoryImportRules.TryGetValue(categoryName, out var importRule)) - { - if (importRule.CategoryId != null) - post.CategoryId = importRule.CategoryId.Value; - - if (importRule.PostType != null) - post.Type = importRule.PostType.Value; - - if (importRule.TechnologyId != null) - post.TechnologyIds = new [] { (int)importRule.TechnologyId.Value }; + RefSource = Site.RefSource, + RefId = forumUser.Id, + RefUrn = $"urn:user:{forumUser.Id}", + }, where: x => x.Id == existingUser.Id); } - - return post; - } - - public Post ToPost(DiscourseTopic topic, int categoryId, int userId, string userName) => new Post - { - Type = PostType.Question, - Title = topic.Title, - Slug = topic.Title.GenerateSlug(), - OrganizationId = Site.OrganizationId, - CategoryId = categoryId, - UserId = userId, - Created = topic.CreatedAt, - CreatedBy = userName, - Modified = topic.UpdatedAt, - ModifiedBy = userName, - LastCommentDate = topic.LastPostedAt, - Views = topic.Views, - RefId = topic.Id, - RefSource = Site.RefSource, - RefUrn = $"urn:topic:{topic.Id}", - Hidden = !topic.Visible ? topic.UpdatedAt : (DateTime?)null, - HiddenBy = !topic.Visible ? userName : null, - WordCount = topic.WordCount, - }; - - public PostComment ToPostComment(DiscoursePost post, long postId, int userId, string userName, long? replyId = null) => new PostComment - { - PostId = postId, - UserId = userId, - Created = post.CreatedAt, - CreatedBy = userName, - Modified = post.UpdatedAt, - ReplyId = replyId, - RefId = post.Id, - Content = post.Raw.StripHtml(), - ContentHtml = post.Cooked, - RefSource = Site.RefSource, - RefUrn = $"urn:post:{post.Id}", - Hidden = post.Hidden ? post.UpdatedAt : (DateTime?)null, - HiddenBy = post.Hidden ? userName : null, - WordCount = post.WordCount, - }; - - public List ImportForumUsers(IDbConnection db, IDbConnection dbForums) - { - var logs = new List(); - - var maxUserId = db.Scalar(db.From() - .Where(x => x.RefSource == Site.RefSource) - .Select(x => Sql.Max(x.RefId))); - - var forumUsers = dbForums.Select(x => x.Approved && x.Id > maxUserId); - - foreach (var forumUser in forumUsers) + else { - var lowerEmail = forumUser.Email.ToLower(); - var existingUser = db.Single(x => x.Email.ToLower() == lowerEmail); + logs.Add($"No match found for {forumUser.Username}"); + var profileUrl = forumUser.UploadedAvatarId != null + ? Site.ProfileUrlFormat.Fmt(forumUser.Username, forumUser.UploadedAvatarId.Value) + : null; - if (existingUser != null) + var userId = (int)db.Insert(new CustomUserAuth { - logs.Add($"Match found: {existingUser.UserName} <=> {forumUser.Username}"); - db.UpdateOnly(() => new CustomUserAuth - { - RefSource = Site.RefSource, - RefId = forumUser.Id, - RefUrn = $"urn:user:{forumUser.Id}", - }, where: x => x.Id == existingUser.Id); - } - else + UserName = forumUser.Username, + Email = forumUser.Email, + CreatedDate = forumUser.CreatedAt, + ModifiedDate = forumUser.UpdatedAt, + DefaultProfileUrl = profileUrl, + RefSource = Site.RefSource, + RefId = forumUser.Id, + RefUrn = $"urn:user:{forumUser.Id}", + }, selectIdentity:true); + + db.Insert(new UserActivity { - logs.Add($"No match found for {forumUser.Username}"); - var profileUrl = forumUser.UploadedAvatarId != null - ? Site.ProfileUrlFormat.Fmt(forumUser.Username, forumUser.UploadedAvatarId.Value) - : null; + Id = userId, + UserName = forumUser.Username, + Created = DateTime.UtcNow, + Modified = DateTime.UtcNow, + }); - var userId = (int)db.Insert(new CustomUserAuth - { - UserName = forumUser.Username, - Email = forumUser.Email, - CreatedDate = forumUser.CreatedAt, - ModifiedDate = forumUser.UpdatedAt, - DefaultProfileUrl = profileUrl, - RefSource = Site.RefSource, - RefId = forumUser.Id, - RefUrn = $"urn:user:{forumUser.Id}", - }, selectIdentity:true); - - db.Insert(new UserActivity - { - Id = userId, - UserName = forumUser.Username, - Created = DateTime.UtcNow, - Modified = DateTime.UtcNow, - }); - - } } - - return logs; } - public List ImportForumPosts(IDbConnection db, IDbConnection dbForums) - { - var logs = new List(); + return logs; + } + + public List ImportForumPosts(IDbConnection db, IDbConnection dbForums) + { + var logs = new List(); - var categoryNameMap = db.Dictionary(db.From() - .Where(x => x.OrganizationId == Site.OrganizationId) - .Select(x => new { x.Name, x.Id })); + var categoryNameMap = db.Dictionary(db.From() + .Where(x => x.OrganizationId == Site.OrganizationId) + .Select(x => new { x.Name, x.Id })); - var maxCategoryId = db.Scalar(db.From() - .Where(x => x.RefSource == Site.RefSource) - .Select(x => Sql.Max(x.RefId))); + var maxCategoryId = db.Scalar(db.From() + .Where(x => x.RefSource == Site.RefSource) + .Select(x => Sql.Max(x.RefId))); - var maxTopicId = db.Scalar(db.From() - .Where(x => x.RefSource == Site.RefSource) - .Select(x => Sql.Max(x.RefId))); + var maxTopicId = db.Scalar(db.From() + .Where(x => x.RefSource == Site.RefSource) + .Select(x => Sql.Max(x.RefId))); - var maxPostId = db.Scalar(db.From() - .Where(x => x.RefSource == Site.RefSource) - .Select(x => Sql.Max(x.RefId))); + var maxPostId = db.Scalar(db.From() + .Where(x => x.RefSource == Site.RefSource) + .Select(x => Sql.Max(x.RefId))); - var forumCategories = dbForums.SqlList(@" + var forumCategories = dbForums.SqlList(@" select distinct p.name as parent_name, p.id as parent_id, c.name as category_name, c.id as category_id, c.slug as category_slug, c.color as category_color @@ -358,41 +358,41 @@ where c.id > @maxCategoryId and p.id > @maxCategoryId and t.id > @maxTopicId and t.posts_count > 1 order by p.name desc, c.name", new { maxCategoryId, maxTopicId }); - foreach (var entry in Site.CategoryImportRules) + foreach (var entry in Site.CategoryImportRules) + { + var fromCategory = entry.Key; + var toCategory = entry.Value.Category; + if (toCategory != null && categoryNameMap.TryGetValue(toCategory, out var toCategoryId)) { - var fromCategory = entry.Key; - var toCategory = entry.Value.Category; - if (toCategory != null && categoryNameMap.TryGetValue(toCategory, out var toCategoryId)) - { - categoryNameMap[fromCategory] = toCategoryId; - } + categoryNameMap[fromCategory] = toCategoryId; } + } - foreach (var forumCategory in forumCategories) + foreach (var forumCategory in forumCategories) + { + if (!categoryNameMap.TryGetValue(forumCategory.CategoryName, out var categoryId)) { - if (!categoryNameMap.TryGetValue(forumCategory.CategoryName, out var categoryId)) - { - if (maxCategoryId > 0) $"Inserting Category: {forumCategory.CategoryName}".Print(); + if (maxCategoryId > 0) $"Inserting Category: {forumCategory.CategoryName}".Print(); - categoryId = (int)db.Insert(ToCategory(forumCategory), selectIdentity: true); - categoryNameMap[forumCategory.CategoryName] = categoryId; - } + categoryId = (int)db.Insert(ToCategory(forumCategory), selectIdentity: true); + categoryNameMap[forumCategory.CategoryName] = categoryId; } + } - foreach (var entry in Site.CategoryImportRules) - { - entry.Value.CategoryId = categoryNameMap[entry.Value.Category]; - } + foreach (var entry in Site.CategoryImportRules) + { + entry.Value.CategoryId = categoryNameMap[entry.Value.Category]; + } - var forumUserIdsMap = db.Dictionary(db.From() - .Where(x => x.RefSource == Site.RefSource) - .Select(x => new { x.RefId, x.Id })); + var forumUserIdsMap = db.Dictionary(db.From() + .Where(x => x.RefSource == Site.RefSource) + .Select(x => new { x.RefId, x.Id })); - var forumUserNamesMap = db.Dictionary(db.From() - .Where(x => x.RefSource == Site.RefSource) - .Select(x => new { x.RefId, x.UserName })); + var forumUserNamesMap = db.Dictionary(db.From() + .Where(x => x.RefSource == Site.RefSource) + .Select(x => new { x.RefId, x.UserName })); - var forumPosts = dbForums.SqlList(@" + var forumPosts = dbForums.SqlList(@" select p.* from posts p where p.id > @maxPostId @@ -401,13 +401,13 @@ where p.id > @maxPostId and raw not like 'This topic is now %' order by topic_id", new { maxPostId }); - var topicIds = maxTopicId > 0 - ? new HashSet(forumPosts.Map(x => x.TopicId)).ToArray() - : TypeConstants.EmptyArray; - if (topicIds.Length == 0) - topicIds = new long[] {0}; //IN (0) + var topicIds = maxTopicId > 0 + ? new HashSet(forumPosts.Map(x => x.TopicId)).ToArray() + : TypeConstants.EmptyArray; + if (topicIds.Length == 0) + topicIds = new long[] {0}; //IN (0) - var forumTopics = dbForums.SqlList(@" + var forumTopics = dbForums.SqlList(@" select c.name as category_name, c.id as category_id, t.* from topics t join categories c on c.id = t.category_id @@ -417,84 +417,83 @@ from topics t and t.posts_count > 1 order by id", new { maxTopicId, maxPostId, topicIds }); - var forumPostsLookup = forumPosts.ToLookup(x => x.TopicId); + var forumPostsLookup = forumPosts.ToLookup(x => x.TopicId); - foreach (var forumTopic in forumTopics) - { - var categoryId = categoryNameMap[forumTopic.CategoryName]; - var userId = forumUserIdsMap[forumTopic.UserId]; - var userName = forumUserNamesMap[forumTopic.UserId]; + foreach (var forumTopic in forumTopics) + { + var categoryId = categoryNameMap[forumTopic.CategoryName]; + var userId = forumUserIdsMap[forumTopic.UserId]; + var userName = forumUserNamesMap[forumTopic.UserId]; - var forumTopicPosts = forumPostsLookup[forumTopic.Id].OrderBy(x => x.PostNumber).ToArray(); - if (forumTopicPosts.Length == 0) - break; + var forumTopicPosts = forumPostsLookup[forumTopic.Id].OrderBy(x => x.PostNumber).ToArray(); + if (forumTopicPosts.Length == 0) + break; - var firstPost = forumTopicPosts.FirstOrDefault(); - var firstPostBySameUser = firstPost?.UserId == forumTopic.UserId; + var firstPost = forumTopicPosts.FirstOrDefault(); + var firstPostBySameUser = firstPost?.UserId == forumTopic.UserId; - var post = db.Single(x => x.RefSource == Site.RefSource && x.RefId == forumTopic.Id); - var postId = post?.Id ?? 0; + var post = db.Single(x => x.RefSource == Site.RefSource && x.RefId == forumTopic.Id); + var postId = post?.Id ?? 0; - if (postId == 0) + if (postId == 0) + { + post = ApplyRules(ToPost(forumTopic, categoryId, userId, userName), forumTopic.CategoryName); + if (firstPostBySameUser) { - post = ApplyRules(ToPost(forumTopic, categoryId, userId, userName), forumTopic.CategoryName); - if (firstPostBySameUser) - { - post.Content = firstPost.Raw.StripHtml(); - post.ContentHtml = firstPost.Cooked; - } + post.Content = firstPost.Raw.StripHtml(); + post.ContentHtml = firstPost.Cooked; + } - if (maxTopicId > 0) $"Inserting Topic: {forumTopic.Title}".Print(); + if (maxTopicId > 0) $"Inserting Topic: {forumTopic.Title}".Print(); - postId = db.Insert(post, selectIdentity: true); - } + postId = db.Insert(post, selectIdentity: true); + } - var forumPostNumberCommentIdMap = new Dictionary(); - var firstIndex = firstPostBySameUser ? 1 : 0; + var forumPostNumberCommentIdMap = new Dictionary(); + var firstIndex = firstPostBySameUser ? 1 : 0; - for (var i = firstIndex; i < forumTopicPosts.Length; i++) + for (var i = firstIndex; i < forumTopicPosts.Length; i++) + { + var forumPost = forumTopicPosts[i]; + userId = forumUserIdsMap[forumPost.UserId]; + userName = forumUserNamesMap[forumPost.UserId]; + + if (forumPost.ReplyToPostNumber == null) { - var forumPost = forumTopicPosts[i]; - userId = forumUserIdsMap[forumPost.UserId]; - userName = forumUserNamesMap[forumPost.UserId]; + if (maxPostId > 0) $"Inserting Comment: {forumPost.Raw.SafeSubstring(0, 50)}".Print(); - if (forumPost.ReplyToPostNumber == null) - { - if (maxPostId > 0) $"Inserting Comment: {forumPost.Raw.SafeSubstring(0, 50)}".Print(); + var commentId = db.Insert(ToPostComment(forumPost, postId, userId, userName), selectIdentity: true); + forumPostNumberCommentIdMap[forumPost.PostNumber] = commentId; + } + else + { + if (maxPostId > 0) $"Inserting Reply Comment: {forumPost.Raw.SafeSubstring(0, 50)} replyTo: {forumPost.ReplyToPostNumber}".Print(); - var commentId = db.Insert(ToPostComment(forumPost, postId, userId, userName), selectIdentity: true); - forumPostNumberCommentIdMap[forumPost.PostNumber] = commentId; - } - else + forumPostNumberCommentIdMap.TryGetValue(forumPost.ReplyToPostNumber.Value, out var replyId); + if (replyId == 0) { - if (maxPostId > 0) $"Inserting Reply Comment: {forumPost.Raw.SafeSubstring(0, 50)} replyTo: {forumPost.ReplyToPostNumber}".Print(); + var replyFormPostId = forumTopicPosts.FirstOrDefault(x => x.PostNumber == forumPost.ReplyToPostNumber.Value)?.Id + ?? dbForums.Scalar("select id from posts where topic_id = @topicId and post_number = @replyPostNumber", + new { topicId = forumTopic.Id, replyPostNumber = forumPost.ReplyToPostNumber.Value }); - forumPostNumberCommentIdMap.TryGetValue(forumPost.ReplyToPostNumber.Value, out var replyId); - if (replyId == 0) + if (replyFormPostId > 0) { - var replyFormPostId = forumTopicPosts.FirstOrDefault(x => x.PostNumber == forumPost.ReplyToPostNumber.Value)?.Id - ?? dbForums.Scalar("select id from posts where topic_id = @topicId and post_number = @replyPostNumber", - new { topicId = forumTopic.Id, replyPostNumber = forumPost.ReplyToPostNumber.Value }); - - if (replyFormPostId > 0) - { - //replyId == 0 could be top-level post, so there's no parent - replyId = db.Scalar(db.From() - .Where(x => x.RefSource == Site.RefSource - && x.RefId == replyFormPostId)); - } + //replyId == 0 could be top-level post, so there's no parent + replyId = db.Scalar(db.From() + .Where(x => x.RefSource == Site.RefSource + && x.RefId == replyFormPostId)); } - var useReplyId = replyId > 0 ? replyId : (long?) null; - - var commentId = db.Insert(ToPostComment(forumPost, postId, userId, userName, useReplyId), selectIdentity: true); - forumPostNumberCommentIdMap[forumPost.PostNumber] = commentId; } + var useReplyId = replyId > 0 ? replyId : (long?) null; + + var commentId = db.Insert(ToPostComment(forumPost, postId, userId, userName, useReplyId), selectIdentity: true); + forumPostNumberCommentIdMap[forumPost.PostNumber] = commentId; } } + } - Cache.FlushAll(); + Cache.FlushAll(); - return logs; - } + return logs; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Messaging/BackgroundAdminServices.cs b/TechStacks.ServiceInterface/Messaging/BackgroundAdminServices.cs index 79f7d7e..1412153 100644 --- a/TechStacks.ServiceInterface/Messaging/BackgroundAdminServices.cs +++ b/TechStacks.ServiceInterface/Messaging/BackgroundAdminServices.cs @@ -1,41 +1,40 @@ using ServiceStack; using ServiceStack.Messaging; -namespace TechStacks.ServiceInterface.Messaging -{ - [Route("/mq/stop")] - public class MqStop : IReturn {} +namespace TechStacks.ServiceInterface.Messaging; + +[Route("/mq/stop")] +public class MqStop : IReturn {} - [Route("/mq/start")] - public class MqStart : IReturn {} +[Route("/mq/start")] +public class MqStart : IReturn {} - [Route("/mq/stats")] - public class MqStats : IReturn {} +[Route("/mq/stats")] +public class MqStats : IReturn {} - [Route("/mq/status")] - public class MqStatus : IReturn {} +[Route("/mq/status")] +public class MqStatus : IReturn {} - public class BackgroundAdminServices : Service - { - public IMessageService MqService { get; set; } +public class BackgroundAdminServices : Service +{ + public IMessageService MqService { get; set; } - [RequiredRole("Admin")] - public object Any(MqStart request) - { - MqService.Start(); - return "OK"; - } + [RequiredRole("Admin")] + public object Any(MqStart request) + { + MqService.Start(); + return "OK"; + } - [RequiredRole("Admin")] - public object Any(MqStop request) - { - MqService.Stop(); - return "OK"; - } + [RequiredRole("Admin")] + public object Any(MqStop request) + { + MqService.Stop(); + return "OK"; + } - public object Any(MqStats request) => MqService.GetStats(); + public object Any(MqStats request) => MqService.GetStats(); - [AddHeader(ContentType = MimeTypes.PlainText)] - public object Any(MqStatus request) => MqService.GetStatsDescription(); - } + [AddHeader(ContentType = MimeTypes.PlainText)] + public object Any(MqStatus request) => MqService.GetStatsDescription(); } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Notifications/EmailProvider.cs b/TechStacks.ServiceInterface/Notifications/EmailProvider.cs index 1f67588..117e958 100644 --- a/TechStacks.ServiceInterface/Notifications/EmailProvider.cs +++ b/TechStacks.ServiceInterface/Notifications/EmailProvider.cs @@ -5,94 +5,93 @@ using ServiceStack; using ServiceStack.Configuration; -namespace TechStacks.ServiceInterface.Notifications +namespace TechStacks.ServiceInterface.Notifications; + +public class MailTo { - public class MailTo - { - public string Email { get; set; } - public string Name { get; set; } - } + public string Email { get; set; } + public string Name { get; set; } +} - public class EmailMessage - { - public MailTo To { get; set; } - public MailTo Cc { get; set; } - public MailTo Bcc { get; set; } - public MailTo From { get; set; } - public string Subject { get; set; } - public string Body { get; set; } - public string BodyHtml { get; set; } - } +public class EmailMessage +{ + public MailTo To { get; set; } + public MailTo Cc { get; set; } + public MailTo Bcc { get; set; } + public MailTo From { get; set; } + public string Subject { get; set; } + public string Body { get; set; } + public string BodyHtml { get; set; } +} - public class EmailProvider - { - public string UserName { get; set; } - public string Password { get; set; } - public string Host { get; set; } - public int Port { get; set; } - public bool EnableSsl { get; set; } - public string Bcc { get; set; } +public class EmailProvider +{ + public string UserName { get; set; } + public string Password { get; set; } + public string Host { get; set; } + public int Port { get; set; } + public bool EnableSsl { get; set; } + public string Bcc { get; set; } - public Action BeforeSend { get; set; } + public Action BeforeSend { get; set; } - public Action OnException { get; set; } + public Action OnException { get; set; } - public void Send(EmailMessage email) - { - try - { - using (var client = new SmtpClient(Host, Port)) - { - client.Credentials = new System.Net.NetworkCredential(UserName, Password); - client.EnableSsl = EnableSsl; + public void Send(EmailMessage email) + { + try + { + using (var client = new SmtpClient(Host, Port)) + { + client.Credentials = new System.Net.NetworkCredential(UserName, Password); + client.EnableSsl = EnableSsl; - var emailTo = email.To.ToMailAddress(); - var emailFrom = email.From.ToMailAddress(); + var emailTo = email.To.ToMailAddress(); + var emailFrom = email.From.ToMailAddress(); - var msg = new MailMessage(emailFrom, emailTo) - { - Subject = email.Subject, - Body = email.Body ?? email.BodyHtml, - IsBodyHtml = email.Body == null, - }; + var msg = new MailMessage(emailFrom, emailTo) + { + Subject = email.Subject, + Body = email.Body ?? email.BodyHtml, + IsBodyHtml = email.Body == null, + }; - if (!msg.IsBodyHtml && email.BodyHtml != null) - { - var mimeType = new ContentType(MimeTypes.Html); - var alternate = AlternateView.CreateAlternateViewFromString(email.BodyHtml, mimeType); - msg.AlternateViews.Add(alternate); - } + if (!msg.IsBodyHtml && email.BodyHtml != null) + { + var mimeType = new ContentType(MimeTypes.Html); + var alternate = AlternateView.CreateAlternateViewFromString(email.BodyHtml, mimeType); + msg.AlternateViews.Add(alternate); + } - if (email.Cc != null) - { - msg.CC.Add(email.Cc.ToMailAddress()); - } + if (email.Cc != null) + { + msg.CC.Add(email.Cc.ToMailAddress()); + } - if (!string.IsNullOrEmpty(Bcc)) - { - msg.Bcc.Add(new MailAddress(Bcc)); - } + if (!string.IsNullOrEmpty(Bcc)) + { + msg.Bcc.Add(new MailAddress(Bcc)); + } - BeforeSend?.Invoke(email, msg); + BeforeSend?.Invoke(email, msg); - client.Send(msg); - } - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + client.Send(msg); + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; } } +} - public static class EmailProviderUtils +public static class EmailProviderUtils +{ + public static MailAddress ToMailAddress(this MailTo from) { - public static MailAddress ToMailAddress(this MailTo from) - { - return string.IsNullOrEmpty(from.Name) - ? new MailAddress(from.Email) - : new MailAddress(from.Email, from.Name); - } + return string.IsNullOrEmpty(from.Name) + ? new MailAddress(from.Email) + : new MailAddress(from.Email, from.Name); } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/Notifications/TwitterUpdates.cs b/TechStacks.ServiceInterface/Notifications/TwitterUpdates.cs index ee75a79..ec31dea 100644 --- a/TechStacks.ServiceInterface/Notifications/TwitterUpdates.cs +++ b/TechStacks.ServiceInterface/Notifications/TwitterUpdates.cs @@ -8,35 +8,35 @@ using ServiceStack.Auth; using ServiceStack.Configuration; -namespace TechStacks.ServiceInterface.Notifications +namespace TechStacks.ServiceInterface.Notifications; + +public class TwitterUpdates : ITwitterUpdates { - public class TwitterUpdates : ITwitterUpdates - { - private readonly TwitterGateway gateway; - private readonly string accessToken; - private readonly string accessTokenSecret; + private readonly TwitterGateway gateway; + private readonly string accessToken; + private readonly string accessTokenSecret; - public string BaseUrl { get; set; } + public string BaseUrl { get; set; } - public TwitterUpdates( - string consumerKey, string consumerSecret, - string accessToken, string accessTokenSecret) + public TwitterUpdates( + string consumerKey, string consumerSecret, + string accessToken, string accessTokenSecret) + { + this.accessToken = accessToken; + this.accessTokenSecret = accessTokenSecret; + this.gateway = new TwitterGateway { - this.accessToken = accessToken; - this.accessTokenSecret = accessTokenSecret; - this.gateway = new TwitterGateway - { - TwitterAuthProvider = new TwitterAuthProvider(new DictionarySettings( - new Dictionary { - {"oauth.twitter.ConsumerKey", consumerKey}, - {"oauth.twitter.ConsumerSecret", consumerSecret}, - })) - }; - } + TwitterAuthProvider = new TwitterAuthProvider(new DictionarySettings( + new Dictionary { + {"oauth.twitter.ConsumerKey", consumerKey}, + {"oauth.twitter.ConsumerSecret", consumerSecret}, + })) + }; + } - public string Tweet(string status) - { - var response = gateway.Send(new PostStatusTwitter + public string Tweet(string status) + { + var response = gateway.Send(new PostStatusTwitter { AccessToken = accessToken, AccessTokenSecret = accessTokenSecret, @@ -44,78 +44,77 @@ public string Tweet(string status) }) .FirstOrDefault(); - return response.StartsWithIgnoreCase("ERROR: ") ? response : null; - } + return response.StartsWithIgnoreCase("ERROR: ") ? response : null; } +} - public class PostStatusTwitter - { - public string AccessToken { get; set; } - public string AccessTokenSecret { get; set; } - public string Status { get; set; } - } +public class PostStatusTwitter +{ + public string AccessToken { get; set; } + public string AccessTokenSecret { get; set; } + public string Status { get; set; } +} - public class TwitterGateway - { - public TwitterAuthProvider TwitterAuthProvider { get; set; } +public class TwitterGateway +{ + public TwitterAuthProvider TwitterAuthProvider { get; set; } - public List Send(params PostStatusTwitter[] messages) + public List Send(params PostStatusTwitter[] messages) + { + var results = new List(); + foreach (var message in messages) { - var results = new List(); - foreach (var message in messages) + try { - try - { - var response = PostToUrl(TwitterAuthProvider, - "https://api.twitter.com/1.1/statuses/update.json", - message.AccessToken, message.AccessTokenSecret, - new Dictionary { { "status", message.Status } }); + var response = PostToUrl(TwitterAuthProvider, + "https://api.twitter.com/1.1/statuses/update.json", + message.AccessToken, message.AccessTokenSecret, + new Dictionary { { "status", message.Status } }); - results.Add(response); - } - catch (Exception ex) - { - results.Add("ERROR: " + ex); - } + results.Add(response); } - return results; - } - - public static string PostToUrl(TwitterAuthProvider oAuthProvider, string url, string accessToken, string accessTokenSecret, Dictionary args, string acceptType = MimeTypes.Json) - { - var uri = new Uri(url); - var webReq = (HttpWebRequest)WebRequest.Create(uri); - webReq.Accept = acceptType; - webReq.Method = HttpMethods.Post; - - string data = null; - if (args != null) + catch (Exception ex) { - var sb = new StringBuilder(); - foreach (var arg in args) - { - if (sb.Length > 0) - sb.Append("&"); - sb.Append($"{arg.Key}={OAuthUtils.PercentEncode(arg.Value)}"); - } - data = sb.ToString(); + results.Add("ERROR: " + ex); } + } + return results; + } - webReq.Headers[HttpRequestHeader.Authorization] = OAuthAuthorizer.AuthorizeRequest( - oAuthProvider, accessToken, accessTokenSecret, "POST", uri, data); + public static string PostToUrl(TwitterAuthProvider oAuthProvider, string url, string accessToken, string accessTokenSecret, Dictionary args, string acceptType = MimeTypes.Json) + { + var uri = new Uri(url); + var webReq = (HttpWebRequest)WebRequest.Create(uri); + webReq.Accept = acceptType; + webReq.Method = HttpMethods.Post; - if (data != null) + string data = null; + if (args != null) + { + var sb = new StringBuilder(); + foreach (var arg in args) { - webReq.ContentType = MimeTypes.FormUrlEncoded; - using (var writer = new StreamWriter(webReq.GetRequestStream())) - writer.Write(data); + if (sb.Length > 0) + sb.Append("&"); + sb.Append($"{arg.Key}={OAuthUtils.PercentEncode(arg.Value)}"); } + data = sb.ToString(); + } - using (var webRes = webReq.GetResponse()) - { - return webRes.ReadToEnd(); - } + webReq.Headers[HttpRequestHeader.Authorization] = OAuthAuthorizer.AuthorizeRequest( + oAuthProvider, accessToken, accessTokenSecret, "POST", uri, data); + + if (data != null) + { + webReq.ContentType = MimeTypes.FormUrlEncoded; + using (var writer = new StreamWriter(webReq.GetRequestStream())) + writer.Write(data); } + using (var webRes = webReq.GetResponse()) + { + return webRes.ReadToEnd(); + } } + } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/OrganizationServices.cs b/TechStacks.ServiceInterface/OrganizationServices.cs index e542f34..03c8b30 100644 --- a/TechStacks.ServiceInterface/OrganizationServices.cs +++ b/TechStacks.ServiceInterface/OrganizationServices.cs @@ -8,532 +8,532 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +[CacheResponse(Duration = 600)] +public class PublicOrganizationServices : Service { - [CacheResponse(Duration = 600)] - public class PublicOrganizationServices : Service + public async Task Get(GetOrganization request) { - public async Task Get(GetOrganization request) - { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); - - return await GetOrganization(await Db.SingleByIdAsync(request.Id)); - } + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); - public async Task Get(GetOrganizationBySlug request) - { - if (string.IsNullOrEmpty(request.Slug)) - throw new ArgumentNullException(nameof(request.Slug)); + return await GetOrganization(await Db.SingleByIdAsync(request.Id)); + } - return await GetOrganization(await Db.SingleAsync(x => x.Slug == request.Slug)); - } + public async Task Get(GetOrganizationBySlug request) + { + if (string.IsNullOrEmpty(request.Slug)) + throw new ArgumentNullException(nameof(request.Slug)); - private async Task GetOrganization(Organization organization) - { - PostServicesBase.AssertCanViewOrganization(Db, organization, SessionAs(), out _); + return await GetOrganization(await Db.SingleAsync(x => x.Slug == request.Slug)); + } - var members = await Db.SelectAsync(x => x.OrganizationId == organization.Id && (x.IsOwner || x.IsModerator)); + private async Task GetOrganization(Organization organization) + { + PostServicesBase.AssertCanViewOrganization(Db, organization, SessionAs(), out _); - return new GetOrganizationResponse - { - Cache = Stopwatch.GetTimestamp(), - Id = organization.Id, - Slug = organization.Slug, - Organization = organization, - Labels = await Db.SelectAsync(x => x.OrganizationId == organization.Id), - Categories = (await Db.SelectAsync(x => x.OrganizationId == organization.Id && x.Deleted == null)).OrderBy(x => x.Name).ToList(), - Owners = members.Where(x => x.IsOwner).ToList(), - Moderators = members.Where(x => x.IsModerator).ToList(), - MembersCount = PostServicesBase.GetOrganizationMembersCount(organization.Id), //display only, can be stale - }; - } + var members = await Db.SelectAsync(x => x.OrganizationId == organization.Id && (x.IsOwner || x.IsModerator)); - public async Task Get(GetOrganizationMembers request) + return new GetOrganizationResponse { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); + Cache = Stopwatch.GetTimestamp(), + Id = organization.Id, + Slug = organization.Slug, + Organization = organization, + Labels = await Db.SelectAsync(x => x.OrganizationId == organization.Id), + Categories = (await Db.SelectAsync(x => x.OrganizationId == organization.Id && x.Deleted == null)).OrderBy(x => x.Name).ToList(), + Owners = members.Where(x => x.IsOwner).ToList(), + Moderators = members.Where(x => x.IsModerator).ToList(), + MembersCount = PostServicesBase.GetOrganizationMembersCount(organization.Id), //display only, can be stale + }; + } - PostServicesBase.AssertCanViewOrganization(Db, request.Id, SessionAs(), out _, out _); + public async Task Get(GetOrganizationMembers request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); - return new GetOrganizationMembersResponse - { - OrganizationId = request.Id, - Results = await Db.SelectAsync(x => x.OrganizationId == request.Id), - }; - } + PostServicesBase.AssertCanViewOrganization(Db, request.Id, SessionAs(), out _, out _); + + return new GetOrganizationMembersResponse + { + OrganizationId = request.Id, + Results = await Db.SelectAsync(x => x.OrganizationId == request.Id), + }; } +} - [Authenticate] - public class OrganizationServices : PostServicesBase +[Authenticate] +public class OrganizationServices : PostServicesBase +{ + public const string Uncategorized = nameof(Uncategorized); + + public async Task Get(GetOrganizationAdmin request) { - public const string Uncategorized = nameof(Uncategorized); + var user = GetUser(); + AssertOrganizationModerator(Db, request.Id, user, out var org, out var orgMember); - public async Task Get(GetOrganizationAdmin request) + return new GetOrganizationAdminResponse { - var user = GetUser(); - AssertOrganizationModerator(Db, request.Id, user, out var org, out var orgMember); - - return new GetOrganizationAdminResponse - { - Labels = await Db.SelectAsync(Db.From().Where(x => x.OrganizationId == org.Id)), + Labels = await Db.SelectAsync(Db.From().Where(x => x.OrganizationId == org.Id)), - Members = await Db.SelectAsync(Db.From().Where(x => x.OrganizationId == org.Id).OrderByDescending(x => new{ x.IsOwner, x.IsModerator })), - MemberInvites = await Db.SelectAsync(x => x.OrganizationId == org.Id && x.Approved == null && x.OrganizationMemberId == null), + Members = await Db.SelectAsync(Db.From().Where(x => x.OrganizationId == org.Id).OrderByDescending(x => new{ x.IsOwner, x.IsModerator })), + MemberInvites = await Db.SelectAsync(x => x.OrganizationId == org.Id && x.Approved == null && x.OrganizationMemberId == null), - ReportedPosts = await Db.SelectAsync(Db.From() - .Join().Where(x => x.Acknowledged == null && x.Dismissed == null)), - ReportedPostComments = await Db.SelectAsync(Db.From() - .Join().Where(x => x.Acknowledged == null && x.Dismissed == null)), - }; - } + ReportedPosts = await Db.SelectAsync(Db.From() + .Join().Where(x => x.Acknowledged == null && x.Dismissed == null)), + ReportedPostComments = await Db.SelectAsync(Db.From() + .Join().Where(x => x.Acknowledged == null && x.Dismissed == null)), + }; + } - public async Task Post(CreateOrganizationForTechnology request) + public async Task Post(CreateOrganizationForTechnology request) + { + if (request.TechnologyId == null && request.TechStackId == null) + throw new ArgumentNullException(nameof(request.TechnologyId)); + + var user = GetUser(); + var userId = user.GetUserId(); + + var type = request.TechnologyId != null + ? typeof(Technology) + : typeof(TechnologyStack); + + var technology = request.TechnologyId != null + ? Db.SingleById(request.TechnologyId.Value) + : null; + var techstack = request.TechStackId != null + ? Db.SingleById(request.TechStackId.Value) + : null; + + if (technology == null && techstack == null) + throw HttpError.NotFound(type.Name + " does not exist"); + + var name = technology?.Name ?? techstack?.Name; + + var alreadyLinked = technology?.CommentsPostId != null || techstack?.CommentsPostId != null; + if (alreadyLinked) + throw HttpError.Conflict($"{name} {type.Name} Post has already been linked"); + + var id = technology?.Id ?? techstack.Id; + var techOrgId = technology?.OrganizationId ?? techstack?.OrganizationId; + var techSlug = technology?.Slug ?? techstack?.Slug; + var techRoute = techstack != null + ? "stacks" + : "tech"; + var organization = techOrgId == null + ? Db.Single(x => x.Slug == techSlug) + : Db.SingleById(techOrgId); + + using (var trans = Db.OpenTransaction()) { - if (request.TechnologyId == null && request.TechStackId == null) - throw new ArgumentNullException(nameof(request.TechnologyId)); - - var user = GetUser(); - var userId = user.GetUserId(); - - var type = request.TechnologyId != null - ? typeof(Technology) - : typeof(TechnologyStack); - - var technology = request.TechnologyId != null - ? Db.SingleById(request.TechnologyId.Value) - : null; - var techstack = request.TechStackId != null - ? Db.SingleById(request.TechStackId.Value) - : null; - - if (technology == null && techstack == null) - throw HttpError.NotFound(type.Name + " does not exist"); - - var name = technology?.Name ?? techstack?.Name; - - var alreadyLinked = technology?.CommentsPostId != null || techstack?.CommentsPostId != null; - if (alreadyLinked) - throw HttpError.Conflict($"{name} {type.Name} Post has already been linked"); - - var id = technology?.Id ?? techstack.Id; - var techOrgId = technology?.OrganizationId ?? techstack?.OrganizationId; - var techSlug = technology?.Slug ?? techstack?.Slug; - var techRoute = techstack != null - ? "stacks" - : "tech"; - var organization = techOrgId == null - ? Db.Single(x => x.Slug == techSlug) - : Db.SingleById(techOrgId); - - using (var trans = Db.OpenTransaction()) - { - var postTitle = $"{name} Page Comments"; - var postContent = $"### Comments for [{name} {type.Name}](/{techRoute}/{techSlug}) page"; - var now = DateTime.Now; + var postTitle = $"{name} Page Comments"; + var postContent = $"### Comments for [{name} {type.Name}](/{techRoute}/{techSlug}) page"; + var now = DateTime.Now; - if (organization == null) + if (organization == null) + { + var description = technology?.Description ?? techstack?.Description; + organization = new Organization { - var description = technology?.Description ?? techstack?.Description; - organization = new Organization + Name = name, + Slug = techSlug, + Description = description, + DescriptionHtml = MarkdownConfig.Transform(description), + PostTypes = new [] { - Name = name, - Slug = techSlug, - Description = description, - DescriptionHtml = MarkdownConfig.Transform(description), - PostTypes = new [] - { - PostType.Announcement.ToString(), - PostType.Post.ToString(), - PostType.Showcase.ToString(), - }, - RefId = id, - RefSource = type.Name, - RefUrn = $"urn:{type.Name}:{id}", - Created = now, - CreatedBy = user.UserName, - Modified = now, - ModifiedBy = user.UserName, - }; - - organization.Id = (int)await Db.InsertAsync(organization, selectIdentity: true); - } - - var post = new Post - { - OrganizationId = organization.Id, - Type = PostType.Post, - Title = postTitle, - Slug = postTitle.GenerateSlug(), - Content = postContent, - ContentHtml = $"
{MarkdownConfig.Transform(postContent)}
", - RefId = organization.RefId, - RefSource = organization.RefSource, - RefUrn = organization.RefUrn, + PostType.Announcement.ToString(), + PostType.Post.ToString(), + PostType.Showcase.ToString(), + }, + RefId = id, + RefSource = type.Name, + RefUrn = $"urn:{type.Name}:{id}", Created = now, CreatedBy = user.UserName, Modified = now, ModifiedBy = user.UserName, - Hidden = now, - HiddenBy = "webstacks", - UserId = userId, - UpVotes = 0, - Rank = 0, - TechnologyIds = technology != null ? new[] { (int)technology.Id } : null, }; - post.Id = await Db.InsertAsync(post, selectIdentity: true); - - if (technology != null) - { - await Db.UpdateOnlyAsync(() => new Technology { - OrganizationId = organization.Id, - CommentsPostId = post.Id, - }, where:x => x.Id == technology.Id); - } - else - { - await Db.UpdateOnlyAsync(() => new TechnologyStack { - OrganizationId = organization.Id, - CommentsPostId = post.Id, - }, where: x => x.Id == techstack.Id); - } - - trans.Commit(); - SendSystemEmail(nameof(CreateOrganizationForTechnology), - $"New {name} {type.Name} Organization was created by {user.UserName} at /{techSlug}"); + organization.Id = (int)await Db.InsertAsync(organization, selectIdentity: true); + } - ClearOrganizationCaches(); - ClearPostCaches(); + var post = new Post + { + OrganizationId = organization.Id, + Type = PostType.Post, + Title = postTitle, + Slug = postTitle.GenerateSlug(), + Content = postContent, + ContentHtml = $"
{MarkdownConfig.Transform(postContent)}
", + RefId = organization.RefId, + RefSource = organization.RefSource, + RefUrn = organization.RefUrn, + Created = now, + CreatedBy = user.UserName, + Modified = now, + ModifiedBy = user.UserName, + Hidden = now, + HiddenBy = "webstacks", + UserId = userId, + UpVotes = 0, + Rank = 0, + TechnologyIds = technology != null ? new[] { (int)technology.Id } : null, + }; + post.Id = await Db.InsertAsync(post, selectIdentity: true); - return new CreateOrganizationForTechnologyResponse - { + if (technology != null) + { + await Db.UpdateOnlyAsync(() => new Technology { OrganizationId = organization.Id, - OrganizationSlug = organization.Slug, CommentsPostId = post.Id, - CommentsPostSlug = post.Slug, - }; + }, where:x => x.Id == technology.Id); } - } - - public object Post(CreateOrganization request) - { - var slugExists = Db.Exists(x => x.Slug == request.Slug); - if (slugExists) - throw new ArgumentException("Slug already exists", nameof(request.Slug)); - - var user = GetUser(); - - var org = request.ConvertTo(); - var orgMember = new OrganizationMember + else { - UserId = user.GetUserId(), - UserName = user.UserName, - IsOwner = true - }; - - org.Created = org.Modified = orgMember.Created = DateTime.Now; - org.CreatedBy = org.ModifiedBy = orgMember.CreatedBy = user.UserName; - - orgMember.OrganizationId = (int)Db.Insert(org, selectIdentity: true); + await Db.UpdateOnlyAsync(() => new TechnologyStack { + OrganizationId = organization.Id, + CommentsPostId = post.Id, + }, where: x => x.Id == techstack.Id); + } - Db.Insert(orgMember); + trans.Commit(); - SendSystemEmail(nameof(CreateOrganization), - $"New {request.Name} Organization was created by {user.UserName} at /{request.Slug}"); + SendSystemEmail(nameof(CreateOrganizationForTechnology), + $"New {name} {type.Name} Organization was created by {user.UserName} at /{techSlug}"); ClearOrganizationCaches(); + ClearPostCaches(); - return new CreateOrganizationResponse + return new CreateOrganizationForTechnologyResponse { - Id = orgMember.OrganizationId, - Slug = request.Slug, + OrganizationId = organization.Id, + OrganizationSlug = organization.Slug, + CommentsPostId = post.Id, + CommentsPostSlug = post.Slug, }; } + } - public async Task Put(UpdateOrganization request) - { - var user = GetUser(); - AssertOrganizationOwner(Db, request.Id, user, out var organization, out var orgMember); + public object Post(CreateOrganization request) + { + var slugExists = Db.Exists(x => x.Slug == request.Slug); + if (slugExists) + throw new ArgumentException("Slug already exists", nameof(request.Slug)); - var slug = request.Slug; + var user = GetUser(); - var slugExists = await Db.ExistsAsync(x => x.Slug == slug && x.Id != request.Id); - if (slugExists) - throw new ArgumentException("Slug already exists", nameof(request.Slug)); + var org = request.ConvertTo(); + var orgMember = new OrganizationMember + { + UserId = user.GetUserId(), + UserName = user.UserName, + IsOwner = true + }; - if (organization.Description != request.Description) - { - organization.DescriptionHtml = await Markdown.TransformAsync(request.Description, user.GetGitHubToken()); - } + org.Created = org.Modified = orgMember.Created = DateTime.Now; + org.CreatedBy = org.ModifiedBy = orgMember.CreatedBy = user.UserName; - organization.PopulateWith(request); - organization.Slug = slug; - organization.Modified = DateTime.Now; - organization.ModifiedBy = user.UserName; + orgMember.OrganizationId = (int)Db.Insert(org, selectIdentity: true); - await Db.UpdateAsync(organization); + Db.Insert(orgMember); - ClearOrganizationCaches(); + SendSystemEmail(nameof(CreateOrganization), + $"New {request.Name} Organization was created by {user.UserName} at /{request.Slug}"); - return new UpdateOrganizationResponse(); - } + ClearOrganizationCaches(); - public void Delete(DeleteOrganization request) + return new CreateOrganizationResponse { - var user = GetUser(); - AssertOrganizationOwner(Db, request.Id, user, out var org, out var orgMember); + Id = orgMember.OrganizationId, + Slug = request.Slug, + }; + } - Db.UpdateOnly(() => new Organization - { - Deleted = DateTime.Now, - DeletedBy = user.UserName, - }, - where: x => x.Id == request.Id); + public async Task Put(UpdateOrganization request) + { + var user = GetUser(); + AssertOrganizationOwner(Db, request.Id, user, out var organization, out var orgMember); - ClearOrganizationCaches(); - } + var slug = request.Slug; + + var slugExists = await Db.ExistsAsync(x => x.Slug == slug && x.Id != request.Id); + if (slugExists) + throw new ArgumentException("Slug already exists", nameof(request.Slug)); - public void Put(LockOrganization request) + if (organization.Description != request.Description) { - var user = GetUser(); - AssertOrganizationOwner(Db, request.Id, user, out var organization, out var orgMember); + organization.DescriptionHtml = await Markdown.TransformAsync(request.Description, user.GetGitHubToken()); + } + + organization.PopulateWith(request); + organization.Slug = slug; + organization.Modified = DateTime.Now; + organization.ModifiedBy = user.UserName; + + await Db.UpdateAsync(organization); + + ClearOrganizationCaches(); + + return new UpdateOrganizationResponse(); + } - if (request.Lock) + public void Delete(DeleteOrganization request) + { + var user = GetUser(); + AssertOrganizationOwner(Db, request.Id, user, out var org, out var orgMember); + + Db.UpdateOnly(() => new Organization { - organization.Locked = DateTime.Now; - organization.LockedBy = user.UserName; - if (!string.IsNullOrEmpty(request.Reason)) - { - organization.Notes = request.Reason; - } - } - else + Deleted = DateTime.Now, + DeletedBy = user.UserName, + }, + where: x => x.Id == request.Id); + + ClearOrganizationCaches(); + } + + public void Put(LockOrganization request) + { + var user = GetUser(); + AssertOrganizationOwner(Db, request.Id, user, out var organization, out var orgMember); + + if (request.Lock) + { + organization.Locked = DateTime.Now; + organization.LockedBy = user.UserName; + if (!string.IsNullOrEmpty(request.Reason)) { - organization.Locked = null; - organization.LockedBy = null; + organization.Notes = request.Reason; } + } + else + { + organization.Locked = null; + organization.LockedBy = null; + } - Db.Update(organization); + Db.Update(organization); - ClearOrganizationCaches(); - } + ClearOrganizationCaches(); + } - public object Post(AddOrganizationLabel request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + public object Post(AddOrganizationLabel request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - if (string.IsNullOrEmpty(request.Slug)) - throw new ArgumentNullException(nameof(request.Slug)); + if (string.IsNullOrEmpty(request.Slug)) + throw new ArgumentNullException(nameof(request.Slug)); - if (string.IsNullOrEmpty(request.Color)) - throw new ArgumentNullException(nameof(request.Color)); + if (string.IsNullOrEmpty(request.Color)) + throw new ArgumentNullException(nameof(request.Color)); - var existingLabel = Db.Exists(x => - x.OrganizationId == request.OrganizationId && x.Slug == request.Slug); - if (existingLabel) - throw new ArgumentException("Label has already been added", nameof(request.Slug)); + var existingLabel = Db.Exists(x => + x.OrganizationId == request.OrganizationId && x.Slug == request.Slug); + if (existingLabel) + throw new ArgumentException("Label has already been added", nameof(request.Slug)); - var label = request.ConvertTo(); - label.Created = label.Modified = DateTime.Now; - label.CreatedBy = label.ModifiedBy = user.UserName; - Db.Insert(label); + var label = request.ConvertTo(); + label.Created = label.Modified = DateTime.Now; + label.CreatedBy = label.ModifiedBy = user.UserName; + Db.Insert(label); - ClearOrganizationCaches(); + ClearOrganizationCaches(); - return new AddOrganizationMemberResponse(); - } + return new AddOrganizationMemberResponse(); + } - public object Put(UpdateOrganizationLabel request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + public object Put(UpdateOrganizationLabel request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - if (string.IsNullOrEmpty(request.Slug)) - throw new ArgumentNullException(nameof(request.Slug)); + if (string.IsNullOrEmpty(request.Slug)) + throw new ArgumentNullException(nameof(request.Slug)); - if (string.IsNullOrEmpty(request.Color)) - throw new ArgumentNullException(nameof(request.Color)); + if (string.IsNullOrEmpty(request.Color)) + throw new ArgumentNullException(nameof(request.Color)); - var label = Db.Single(x => - x.OrganizationId == request.OrganizationId && x.Slug == request.Slug); + var label = Db.Single(x => + x.OrganizationId == request.OrganizationId && x.Slug == request.Slug); - if (label == null) - throw HttpError.NotFound("Label does not exist"); + if (label == null) + throw HttpError.NotFound("Label does not exist"); - label.PopulateWith(request); - label.Modified = DateTime.Now; - label.ModifiedBy = user.UserName; + label.PopulateWith(request); + label.Modified = DateTime.Now; + label.ModifiedBy = user.UserName; - Db.Update(label); + Db.Update(label); - ClearOrganizationCaches(); + ClearOrganizationCaches(); - return new UpdateOrganizationMemberResponse(); - } + return new UpdateOrganizationMemberResponse(); + } - public void Delete(RemoveOrganizationLabel request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + public void Delete(RemoveOrganizationLabel request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - Db.Delete(x => - x.OrganizationId == request.OrganizationId && x.Slug == request.Slug); + Db.Delete(x => + x.OrganizationId == request.OrganizationId && x.Slug == request.Slug); - ClearOrganizationCaches(); - } + ClearOrganizationCaches(); + } - public object Post(AddOrganizationCategory request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - - var slugExists = Db.Exists(x => x.Slug == request.Slug && x.OrganizationId == request.OrganizationId); - if (slugExists) - throw new ArgumentException("Slug already exists", nameof(request.Slug)); + public object Post(AddOrganizationCategory request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - var category = request.ConvertTo(); - category.Created = category.Modified = DateTime.Now; - category.CreatedBy = category.ModifiedBy = user.UserName; + var slugExists = Db.Exists(x => x.Slug == request.Slug && x.OrganizationId == request.OrganizationId); + if (slugExists) + throw new ArgumentException("Slug already exists", nameof(request.Slug)); - var id = (int)Db.Insert(category, selectIdentity: true); + var category = request.ConvertTo(); + category.Created = category.Modified = DateTime.Now; + category.CreatedBy = category.ModifiedBy = user.UserName; - ClearOrganizationCaches(); + var id = (int)Db.Insert(category, selectIdentity: true); - return new AddOrganizationCategoryResponse - { - Id = id, - Slug = request.Slug, - }; - } + ClearOrganizationCaches(); - public object Put(UpdateOrganizationCategory request) + return new AddOrganizationCategoryResponse { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + Id = id, + Slug = request.Slug, + }; + } - var category = Db.Single(x => x.Id == request.Id && x.OrganizationId == request.OrganizationId); - if (category == null) - throw HttpError.NotFound("Category does not exist"); + public object Put(UpdateOrganizationCategory request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - category.PopulateWith(request); - category.Modified = DateTime.Now; - category.ModifiedBy = user.UserName; + var category = Db.Single(x => x.Id == request.Id && x.OrganizationId == request.OrganizationId); + if (category == null) + throw HttpError.NotFound("Category does not exist"); - Db.Update(category); + category.PopulateWith(request); + category.Modified = DateTime.Now; + category.ModifiedBy = user.UserName; - ClearOrganizationCaches(); + Db.Update(category); - return new UpdateOrganizationCategoryResponse(); - } + ClearOrganizationCaches(); - public void Delete(DeleteOrganizationCategory request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + return new UpdateOrganizationCategoryResponse(); + } - Db.UpdateOnly(() => new Category - { - Deleted = DateTime.Now, - DeletedBy = user.UserName, - }, - where: x => x.Id == request.Id && x.OrganizationId == request.OrganizationId); + public void Delete(DeleteOrganizationCategory request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - ClearOrganizationCaches(); - } + Db.UpdateOnly(() => new Category + { + Deleted = DateTime.Now, + DeletedBy = user.UserName, + }, + where: x => x.Id == request.Id && x.OrganizationId == request.OrganizationId); - public object Post(AddOrganizationMember request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + ClearOrganizationCaches(); + } - var requiresOwner = request.IsModerator || request.IsOwner; - if (requiresOwner && !user.IsOrganizationOwner(orgMember)) - throw HttpError.Forbidden("This action is limited to Organization Owners"); + public object Post(AddOrganizationMember request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - var memberUser = Db.Single(x => x.UserName.ToLower() == request.UserName.ToLower()); - if (memberUser == null) - throw HttpError.NotFound("User does not exist"); + var requiresOwner = request.IsModerator || request.IsOwner; + if (requiresOwner && !user.IsOrganizationOwner(orgMember)) + throw HttpError.Forbidden("This action is limited to Organization Owners"); - var existingMember = Db.Exists(x => - x.OrganizationId == request.OrganizationId && x.UserId == memberUser.Id); - if (existingMember) - throw new ArgumentException("Member has already been added", nameof(request.UserName)); + var memberUser = Db.Single(x => x.UserName.ToLower() == request.UserName.ToLower()); + if (memberUser == null) + throw HttpError.NotFound("User does not exist"); - var member = request.ConvertTo(); - member.UserId = memberUser.Id; - member.UserName = memberUser.UserName; - member.Created = member.Modified = DateTime.Now; - member.CreatedBy = member.ModifiedBy = user.UserName; - Db.Insert(member); + var existingMember = Db.Exists(x => + x.OrganizationId == request.OrganizationId && x.UserId == memberUser.Id); + if (existingMember) + throw new ArgumentException("Member has already been added", nameof(request.UserName)); - ClearOrganizationCaches(); + var member = request.ConvertTo(); + member.UserId = memberUser.Id; + member.UserName = memberUser.UserName; + member.Created = member.Modified = DateTime.Now; + member.CreatedBy = member.ModifiedBy = user.UserName; + Db.Insert(member); - return new AddOrganizationMemberResponse(); - } + ClearOrganizationCaches(); - public object Put(UpdateOrganizationMember request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + return new AddOrganizationMemberResponse(); + } - var member = Db.Single(x => - x.UserId == request.UserId && x.OrganizationId == request.OrganizationId); + public object Put(UpdateOrganizationMember request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - if (member == null) - throw HttpError.NotFound("Member does not exist"); + var member = Db.Single(x => + x.UserId == request.UserId && x.OrganizationId == request.OrganizationId); - var requiresOwner = member.IsOwner || member.IsModerator || - member.IsModerator != request.IsModerator || - member.IsOwner != request.IsOwner; + if (member == null) + throw HttpError.NotFound("Member does not exist"); - if (requiresOwner && !user.IsOrganizationOwner(orgMember)) - throw HttpError.Forbidden("This action is limited to Organization Owners"); + var requiresOwner = member.IsOwner || member.IsModerator || + member.IsModerator != request.IsModerator || + member.IsOwner != request.IsOwner; - member.PopulateWith(request); + if (requiresOwner && !user.IsOrganizationOwner(orgMember)) + throw HttpError.Forbidden("This action is limited to Organization Owners"); - Db.Update(member); + member.PopulateWith(request); - ClearOrganizationCaches(); + Db.Update(member); - return new UpdateOrganizationMemberResponse(); - } + ClearOrganizationCaches(); - public void Delete(RemoveOrganizationMember request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + return new UpdateOrganizationMemberResponse(); + } - var member = Db.Single(x => - x.UserId == request.UserId && x.OrganizationId == request.OrganizationId); + public void Delete(RemoveOrganizationMember request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - var requiresOwner = member.IsOwner || member.IsModerator; - if (requiresOwner && !user.IsOrganizationOwner(orgMember)) - throw HttpError.Forbidden("This action is limited to Organization Owners"); + var member = Db.Single(x => + x.UserId == request.UserId && x.OrganizationId == request.OrganizationId); - Db.Delete(x => x.UserId == request.UserId && x.OrganizationId == request.OrganizationId); - Db.Delete(x => x.UserId == request.UserId && x.OrganizationId == request.OrganizationId); + var requiresOwner = member.IsOwner || member.IsModerator; + if (requiresOwner && !user.IsOrganizationOwner(orgMember)) + throw HttpError.Forbidden("This action is limited to Organization Owners"); - SendSystemEmail(nameof(RemoveOrganizationMember), - $"@{member.UserName} was removed from {org.Name} by {user.UserName}"); + Db.Delete(x => x.UserId == request.UserId && x.OrganizationId == request.OrganizationId); + Db.Delete(x => x.UserId == request.UserId && x.OrganizationId == request.OrganizationId); - ClearOrganizationCaches(); - } + SendSystemEmail(nameof(RemoveOrganizationMember), + $"@{member.UserName} was removed from {org.Name} by {user.UserName}"); - public object Post(SetOrganizationMembers request) - { - var user = GetUser(); - AssertOrganizationOwner(Db, request.OrganizationId, user, out var organization, out var orgMember); + ClearOrganizationCaches(); + } - var githubUserNames = request.GithubUserNames ?? TypeConstants.EmptyStringArray; - var twitterUserNames = request.TwitterUserNames ?? TypeConstants.EmptyStringArray; - var emails = request.Emails ?? TypeConstants.EmptyStringArray; + public object Post(SetOrganizationMembers request) + { + var user = GetUser(); + AssertOrganizationOwner(Db, request.OrganizationId, user, out var organization, out var orgMember); - var users = Db.SqlList<(int userId, string userName)>( - @"select c.id, c.user_name + var githubUserNames = request.GithubUserNames ?? TypeConstants.EmptyStringArray; + var twitterUserNames = request.TwitterUserNames ?? TypeConstants.EmptyStringArray; + var emails = request.Emails ?? TypeConstants.EmptyStringArray; + + var users = Db.SqlList<(int userId, string userName)>( + @"select c.id, c.user_name from custom_user_auth c where c.email in (@emails) or exists (select * from user_auth_details d @@ -541,177 +541,176 @@ or exists (select * from user_auth_details d and (email in (@emails) or (user_name in (@twitterUserNames) and provider = 'twitter') or (user_name in (@githubUserNames) and provider = 'github')))", - new { githubUserNames, twitterUserNames, emails }); + new { githubUserNames, twitterUserNames, emails }); - var userIdsMap = new Dictionary(); - users.Each(x => userIdsMap[x.userId] = x.userName); + var userIdsMap = new Dictionary(); + users.Each(x => userIdsMap[x.userId] = x.userName); - var existingUserIds = Db.Column(Db.From() - .Where(x => x.OrganizationId == request.OrganizationId) - .Select(x => x.UserId)); + var existingUserIds = Db.Column(Db.From() + .Where(x => x.OrganizationId == request.OrganizationId) + .Select(x => x.UserId)); - var userIdsAdded = new List(); - var userIdsToRemove = existingUserIds.Where(x => !userIdsMap.ContainsKey(x)).ToArray(); + var userIdsAdded = new List(); + var userIdsToRemove = existingUserIds.Where(x => !userIdsMap.ContainsKey(x)).ToArray(); - var response = new SetOrganizationMembersResponse { - }; + var response = new SetOrganizationMembersResponse { + }; - var now = DateTime.Now; - using (var trans = Db.OpenTransaction()) + var now = DateTime.Now; + using (var trans = Db.OpenTransaction()) + { + foreach (var entry in userIdsMap) { - foreach (var entry in userIdsMap) - { - if (existingUserIds.Contains(entry.Key)) - continue; - - var member = request.ConvertTo(); - member.UserId = entry.Key; - member.UserName = entry.Value; - member.Created = member.Modified = now; - member.CreatedBy = member.ModifiedBy = user.UserName; + if (existingUserIds.Contains(entry.Key)) + continue; + + var member = request.ConvertTo(); + member.UserId = entry.Key; + member.UserName = entry.Value; + member.Created = member.Modified = now; + member.CreatedBy = member.ModifiedBy = user.UserName; - Db.Insert(member); - userIdsAdded.Add(entry.Key); - } + Db.Insert(member); + userIdsAdded.Add(entry.Key); + } - if (request.RemoveUnspecifiedMembers) - { - Db.Delete(x => x.OrganizationId == request.OrganizationId - && userIdsToRemove.Contains(x.UserId)); - - response.UserIdsRemoved = userIdsToRemove; - } + if (request.RemoveUnspecifiedMembers) + { + Db.Delete(x => x.OrganizationId == request.OrganizationId + && userIdsToRemove.Contains(x.UserId)); - trans.Commit(); + response.UserIdsRemoved = userIdsToRemove; } - response.UserIdsAdded = userIdsAdded.ToArray(); + trans.Commit(); + } - ClearOrganizationCaches(); + response.UserIdsAdded = userIdsAdded.ToArray(); - return response; - } + ClearOrganizationCaches(); - - public async Task Get(GetOrganizationMemberInvites request) - { - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + return response; + } - return new GetOrganizationMemberInvitesResponse - { - Results = await Db.SelectAsync(x => x.OrganizationId == request.OrganizationId), - }; - } + + public async Task Get(GetOrganizationMemberInvites request) + { + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - public async Task Post(RequestOrganizationMemberInvite request) + return new GetOrganizationMemberInvitesResponse { - if (request.OrganizationId <= 0) - throw new ArgumentNullException(nameof(request.OrganizationId)); + Results = await Db.SelectAsync(x => x.OrganizationId == request.OrganizationId), + }; + } + + public async Task Post(RequestOrganizationMemberInvite request) + { + if (request.OrganizationId <= 0) + throw new ArgumentNullException(nameof(request.OrganizationId)); - var user = GetUser(); - var userId = user.GetUserId(); + var user = GetUser(); + var userId = user.GetUserId(); - var memberExists = Db.Exists(x => - x.UserId == userId && x.OrganizationId == request.OrganizationId); + var memberExists = Db.Exists(x => + x.UserId == userId && x.OrganizationId == request.OrganizationId); - if (memberExists) - throw HttpError.Conflict("Already a member"); + if (memberExists) + throw HttpError.Conflict("Already a member"); - var inviteExists = Db.Exists(x => - x.UserId == userId && x.OrganizationId == request.OrganizationId); + var inviteExists = Db.Exists(x => + x.UserId == userId && x.OrganizationId == request.OrganizationId); - if (inviteExists) - throw HttpError.Conflict("Member Invite already requested"); + if (inviteExists) + throw HttpError.Conflict("Member Invite already requested"); - await Db.InsertAsync(new OrganizationMemberInvite - { - UserId = userId, - UserName = user.UserName, - OrganizationId = request.OrganizationId, - Created = DateTime.Now, - }); + await Db.InsertAsync(new OrganizationMemberInvite + { + UserId = userId, + UserName = user.UserName, + OrganizationId = request.OrganizationId, + Created = DateTime.Now, + }); - SendSystemEmail(nameof(RequestOrganizationMemberInvite), - $"@{user.UserName} requested member invite for {request.OrganizationId}"); + SendSystemEmail(nameof(RequestOrganizationMemberInvite), + $"@{user.UserName} requested member invite for {request.OrganizationId}"); - ClearOrganizationCaches(); + ClearOrganizationCaches(); - return new RequestOrganizationMemberInviteResponse - { - OrganizationId = request.OrganizationId, - }; - } - - public async Task Put(UpdateOrganizationMemberInvite request) + return new RequestOrganizationMemberInviteResponse { - if (request.OrganizationId <= 0) - throw new ArgumentNullException(nameof(request.OrganizationId)); - - if (string.IsNullOrEmpty(request.UserName)) - throw new ArgumentNullException(nameof(request.UserName)); + OrganizationId = request.OrganizationId, + }; + } - var user = GetUser(); - AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); + public async Task Put(UpdateOrganizationMemberInvite request) + { + if (request.OrganizationId <= 0) + throw new ArgumentNullException(nameof(request.OrganizationId)); - var userId = Db.Scalar(Db.From() - .Where(x => x.UserName == request.UserName) - .Select(x => x.Id)); + if (string.IsNullOrEmpty(request.UserName)) + throw new ArgumentNullException(nameof(request.UserName)); - if (userId == default(int)) - throw HttpError.NotFound("User does not exist"); + var user = GetUser(); + AssertOrganizationModerator(Db, request.OrganizationId, user, out var org, out var orgMember); - var now = DateTime.Now; - if (request.Approve) - { - var hasOwnersOrModerators = Db.Exists(x => - x.OrganizationId == request.OrganizationId && (x.IsOwner || x.IsModerator)); + var userId = Db.Scalar(Db.From() + .Where(x => x.UserName == request.UserName) + .Select(x => x.Id)); - var id = await Db.InsertAsync(new OrganizationMember - { - OrganizationId = request.OrganizationId, - UserId = userId, - UserName = request.UserName, - IsOwner = !hasOwnersOrModerators, - Created = now, - CreatedBy = user.UserName, - Modified = now, - ModifiedBy = user.UserName, - }, selectIdentity:true); - - await Db.UpdateOnlyAsync(() => new OrganizationMemberInvite - { - Approved = now, - ApprovedBy = user.UserName, - OrganizationMemberId = (int)id, - }, - where: x => x.UserId == userId && x.OrganizationId == request.OrganizationId); + if (userId == default(int)) + throw HttpError.NotFound("User does not exist"); - SendSystemEmail(nameof(UpdateOrganizationMemberInvite), - $"@{request.UserName} invitation for {org.Name} was approved by {user.UserName}"); + var now = DateTime.Now; + if (request.Approve) + { + var hasOwnersOrModerators = Db.Exists(x => + x.OrganizationId == request.OrganizationId && (x.IsOwner || x.IsModerator)); - } - else if (request.Dismiss) + var id = await Db.InsertAsync(new OrganizationMember { - await Db.UpdateOnlyAsync(() => new OrganizationMemberInvite - { - Dismissed = now, - DismissedBy = user.UserName, - }, + OrganizationId = request.OrganizationId, + UserId = userId, + UserName = request.UserName, + IsOwner = !hasOwnersOrModerators, + Created = now, + CreatedBy = user.UserName, + Modified = now, + ModifiedBy = user.UserName, + }, selectIdentity:true); + + await Db.UpdateOnlyAsync(() => new OrganizationMemberInvite + { + Approved = now, + ApprovedBy = user.UserName, + OrganizationMemberId = (int)id, + }, where: x => x.UserId == userId && x.OrganizationId == request.OrganizationId); - SendSystemEmail(nameof(UpdateOrganizationMemberInvite), - $"@{request.UserName} invitation for {org.Name} was dismissed by {user.UserName}"); - } - else - { - throw new Exception("Must Approve or Dismiss"); - } + SendSystemEmail(nameof(UpdateOrganizationMemberInvite), + $"@{request.UserName} invitation for {org.Name} was approved by {user.UserName}"); - ClearOrganizationCaches(); + } + else if (request.Dismiss) + { + await Db.UpdateOnlyAsync(() => new OrganizationMemberInvite + { + Dismissed = now, + DismissedBy = user.UserName, + }, + where: x => x.UserId == userId && x.OrganizationId == request.OrganizationId); - return new UpdateOrganizationMemberInviteResponse(); + SendSystemEmail(nameof(UpdateOrganizationMemberInvite), + $"@{request.UserName} invitation for {org.Name} was dismissed by {user.UserName}"); + } + else + { + throw new Exception("Must Approve or Dismiss"); } + ClearOrganizationCaches(); + + return new UpdateOrganizationMemberInviteResponse(); } + } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/PostPublicServices.cs b/TechStacks.ServiceInterface/PostPublicServices.cs index b46e9be..241bb69 100644 --- a/TechStacks.ServiceInterface/PostPublicServices.cs +++ b/TechStacks.ServiceInterface/PostPublicServices.cs @@ -7,72 +7,70 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public class PostPublicServices : PostServicesBase { - - public class PostPublicServices : PostServicesBase - { - public IAutoQueryDb AutoQuery { get; set; } + public IAutoQueryDb AutoQuery { get; set; } - public object Any(QueryPosts request) - { - var q = AutoQuery.CreateQuery(request, Request.GetRequestParams()); - q.Where(x => x.Deleted == null); + public object Any(QueryPosts request) + { + var q = AutoQuery.CreateQuery(request, Request.GetRequestParams()); + q.Where(x => x.Deleted == null); - var states = request.Is ?? TypeConstants.EmptyStringArray; - if (states.Contains("closed") || states.Contains("completed") || states.Contains("declined")) - q.And(x => x.Status == "closed"); - else - q.And(x => x.Hidden == null && (x.Status == null || x.Status != "closed")); + var states = request.Is ?? TypeConstants.EmptyStringArray; + if (states.Contains("closed") || states.Contains("completed") || states.Contains("declined")) + q.And(x => x.Status == "closed"); + else + q.And(x => x.Hidden == null && (x.Status == null || x.Status != "closed")); - if (states.Length > 0) - { - var labelSlugs = states.Where(x => x != "closed" && x != "open") - .Map(x => x.GenerateSlug()); - if (labelSlugs.Count > 0) - q.And($"ARRAY[{new SqlInValues(labelSlugs).ToSqlInString()}] && labels"); - } - - if (!request.AnyTechnologyIds.IsEmpty()) - { - var techIds = request.AnyTechnologyIds.Join(","); - var orgIds = request.AnyTechnologyIds.Map(id => GetOrganizationByTechnologyId(Db, id)) - .Where(x => x != null) - .Select(x => x.Id) - .Join(","); - if (string.IsNullOrEmpty(orgIds)) - orgIds = "NULL"; + if (states.Length > 0) + { + var labelSlugs = states.Where(x => x != "closed" && x != "open") + .Map(x => x.GenerateSlug()); + if (labelSlugs.Count > 0) + q.And($"ARRAY[{new SqlInValues(labelSlugs).ToSqlInString()}] && labels"); + } - q.And($"(ARRAY[{techIds}] && technology_ids OR organization_id in ({orgIds}))"); - } + if (!request.AnyTechnologyIds.IsEmpty()) + { + var techIds = request.AnyTechnologyIds.Join(","); + var orgIds = request.AnyTechnologyIds.Map(id => GetOrganizationByTechnologyId(Db, id)) + .Where(x => x != null) + .Select(x => x.Id) + .Join(","); + if (string.IsNullOrEmpty(orgIds)) + orgIds = "NULL"; - return AutoQuery.Execute(request, q); + q.And($"(ARRAY[{techIds}] && technology_ids OR organization_id in ({orgIds}))"); } - public async Task Get(GetPost request) - { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); + return AutoQuery.Execute(request, q); + } - var user = SessionAs(); - var post = await Db.SingleByIdAsync(request.Id); - OrganizationMember groupMember = null; - if (post != null) - AssertCanViewOrganization(Db, post.OrganizationId, user, out _, out groupMember); + public async Task Get(GetPost request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); - if (post == null || post.Deleted != null && !user.IsOrganizationModerator(groupMember)) - throw HttpError.NotFound("Post does not exist"); + var user = SessionAs(); + var post = await Db.SingleByIdAsync(request.Id); + OrganizationMember groupMember = null; + if (post != null) + AssertCanViewOrganization(Db, post.OrganizationId, user, out _, out groupMember); - var postComments = request.Include == "comments" - ? await Db.SelectAsync(x => x.PostId == request.Id && x.Deleted == null) - : TypeConstants.EmptyList; + if (post == null || post.Deleted != null && !user.IsOrganizationModerator(groupMember)) + throw HttpError.NotFound("Post does not exist"); - return new GetPostResponse - { - Cache = Stopwatch.GetTimestamp(), - Post = post, - Comments = postComments, - }; - } + var postComments = request.Include == "comments" + ? await Db.SelectAsync(x => x.PostId == request.Id && x.Deleted == null) + : TypeConstants.EmptyList; + + return new GetPostResponse + { + Cache = Stopwatch.GetTimestamp(), + Post = post, + Comments = postComments, + }; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/PostServices.cs b/TechStacks.ServiceInterface/PostServices.cs index 69356b0..fed8786 100644 --- a/TechStacks.ServiceInterface/PostServices.cs +++ b/TechStacks.ServiceInterface/PostServices.cs @@ -5,510 +5,503 @@ using ServiceStack.Configuration; using ServiceStack.Logging; using ServiceStack.OrmLite; -using ServiceStack.Text; -using TechStacks.ServiceInterface.Admin; -using TechStacks.ServiceInterface.DataModel; using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +[Authenticate] +public class PostServices : PostServicesBase { - [Authenticate] - public class PostServices : PostServicesBase + static readonly ILog Log = LogManager.GetLogger(typeof(PostServices)); + + public IAppSettings AppSettings { get; set; } + + public async Task Post(CreatePost request) { - static readonly ILog Log = LogManager.GetLogger(typeof(PostServices)); + var user = GetUser(); + AssertCanPostToOrganization(Db, request.OrganizationId, user, out var org, out var orgMember); + AssertCanPostTypeToOrganization(request.Type, org, orgMember, user); - public IAppSettings AppSettings { get; set; } + var existingPost = request.Url != null + ? await Db.SingleAsync(x => x.Url == request.Url && x.Deleted == null && x.Hidden == null && !x.Archived) + : null; - public async Task Post(CreatePost request) + if (existingPost != null) + throw new ArgumentException($"URL already used in unarchived /posts/{existingPost.Id}/{existingPost.Slug}", nameof(request.Url)); + + var post = request.ConvertTo(); + post.Slug = request.Title.GenerateSlug(); + post.Created = post.Modified = DateTime.Now; + post.CreatedBy = post.ModifiedBy = user.UserName; + post.UserId = user.UserAuthId.ToInt(); + post.UpVotes = 0; + post.Points = 1; + post.ContentHtml = await Markdown.TransformAsync(post.Content, user.GetGitHubToken()); + post.Rank = 0; + + if (!user.IsOrganizationModerator(orgMember)) { - var user = GetUser(); - AssertCanPostToOrganization(Db, request.OrganizationId, user, out var org, out var orgMember); - AssertCanPostTypeToOrganization(request.Type, org, orgMember, user); + post.Labels = null; + } - var existingPost = request.Url != null - ? await Db.SingleAsync(x => x.Url == request.Url && x.Deleted == null && x.Hidden == null && !x.Archived) - : null; + if (string.IsNullOrEmpty(post.ImageUrl) && Request.Files.Length > 0) + { + post.ImageUrl = Request.Files[0].UploadToImgur(AppSettings.GetString("oauth.imgur.ClientId"), + nameof(post.ImageUrl), minWidth: 200, minHeight: 200, maxWidth: 4000, maxHeight: 4000); + } - if (existingPost != null) - throw new ArgumentException($"URL already used in unarchived /posts/{existingPost.Id}/{existingPost.Slug}", nameof(request.Url)); - - var post = request.ConvertTo(); - post.Slug = request.Title.GenerateSlug(); - post.Created = post.Modified = DateTime.Now; - post.CreatedBy = post.ModifiedBy = user.UserName; - post.UserId = user.UserAuthId.ToInt(); - post.UpVotes = 0; - post.Points = 1; - post.ContentHtml = await Markdown.TransformAsync(post.Content, user.GetGitHubToken()); - post.Rank = 0; - - if (!user.IsOrganizationModerator(orgMember)) - { - post.Labels = null; - } + var id = await Db.InsertAsync(post, selectIdentity: true); - if (string.IsNullOrEmpty(post.ImageUrl) && Request.Files.Length > 0) - { - post.ImageUrl = Request.Files[0].UploadToImgur(AppSettings.GetString("oauth.imgur.ClientId"), - nameof(post.ImageUrl), minWidth: 200, minHeight: 200, maxWidth: 4000, maxHeight: 4000); - } + await Db.UpdateAddAsync(() => new UserActivity { PostsCount = 1 }, + where: x => x.Id == post.UserId); - var id = await Db.InsertAsync(post, selectIdentity: true); + await SendNotificationAsync(nameof(CreatePost), nameof(Post), id); - await Db.UpdateAddAsync(() => new UserActivity { PostsCount = 1 }, - where: x => x.Id == post.UserId); + ClearPostCaches(); - await SendNotificationAsync(nameof(CreatePost), nameof(Post), id); + return new CreatePostResponse + { + Id = id, + Slug = post.Slug, + }; + } - ClearPostCaches(); + public async Task Put(UpdatePost request) + { + var user = GetUser(); + var post = await AssertPostAsync(request.Id); + AssertCanPostToOrganization(Db, request.OrganizationId, user, out var org, out var orgMember); + AssertCanPostTypeToOrganization(request.Type, org, orgMember, user); + AssertCanUpdatePost(post, user, orgMember); - return new CreatePostResponse - { - Id = id, - Slug = post.Slug, - }; + if (post.Content != request.Content) + { + post.ContentHtml = await Markdown.TransformAsync(request.Content, user.GetGitHubToken()); } - public async Task Put(UpdatePost request) + if (!user.IsOrganizationModerator(orgMember)) { - var user = GetUser(); - var post = await AssertPostAsync(request.Id); - AssertCanPostToOrganization(Db, request.OrganizationId, user, out var org, out var orgMember); - AssertCanPostTypeToOrganization(request.Type, org, orgMember, user); - AssertCanUpdatePost(post, user, orgMember); + request.Labels = post.Labels; + } - if (post.Content != request.Content) - { - post.ContentHtml = await Markdown.TransformAsync(request.Content, user.GetGitHubToken()); - } + post.PopulateWith(request); + post.ModifiedBy = user.UserName; + post.Modified = DateTime.Now; + post.Rank = 0; - if (!user.IsOrganizationModerator(orgMember)) - { - request.Labels = post.Labels; - } + if (Request.Files.Length > 0) + { + post.ImageUrl = Request.Files[0].UploadToImgur(AppSettings.GetString("oauth.imgur.ClientId"), + nameof(post.ImageUrl), minWidth: 200, minHeight: 200, maxWidth: 4000, maxHeight: 4000); + } - post.PopulateWith(request); - post.ModifiedBy = user.UserName; - post.Modified = DateTime.Now; - post.Rank = 0; + await Db.UpdateAsync(post); - if (Request.Files.Length > 0) - { - post.ImageUrl = Request.Files[0].UploadToImgur(AppSettings.GetString("oauth.imgur.ClientId"), - nameof(post.ImageUrl), minWidth: 200, minHeight: 200, maxWidth: 4000, maxHeight: 4000); - } + ClearPostCaches(); - await Db.UpdateAsync(post); + return new UpdatePostResponse(); + } - ClearPostCaches(); + public object Delete(DeletePost request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); - return new UpdatePostResponse(); - } + var user = GetUser(); + var post = AssertPost(request.Id); + AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); + AssertCanUpdatePost(post, user, orgMember); - public object Delete(DeletePost request) + var userId = user.GetUserId(); + + var now = DateTime.Now; + if (!user.IsOrganizationModerator(orgMember)) + { + Db.UpdateOnly(() => new Post + { + Deleted = now, + DeletedBy = user.UserName, + Modified = now, + }, + where: x => x.Id == request.Id && x.UserId == userId); + } + else { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); + Db.UpdateOnly(() => new Post + { + Deleted = now, + DeletedBy = user.UserName, + Modified = now, + }, + where: x => x.Id == request.Id); + } - var user = GetUser(); - var post = AssertPost(request.Id); - AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); - AssertCanUpdatePost(post, user, orgMember); + ClearPostCaches(); - var userId = user.GetUserId(); + return new DeletePostResponse + { + Id = request.Id, + }; + } - var now = DateTime.Now; - if (!user.IsOrganizationModerator(orgMember)) - { - Db.UpdateOnly(() => new Post - { - Deleted = now, - DeletedBy = user.UserName, - Modified = now, - }, - where: x => x.Id == request.Id && x.UserId == userId); - } - else - { - Db.UpdateOnly(() => new Post - { - Deleted = now, - DeletedBy = user.UserName, - Modified = now, - }, - where: x => x.Id == request.Id); - } + public void Put(LockPost request) + { + var user = GetUser(); + var post = AssertPost(request.Id); + AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); - ClearPostCaches(); + if (!user.IsOrganizationModerator(orgMember)) + throw HttpError.Forbidden("Access Denied"); - return new DeletePostResponse + var now = DateTime.Now; + if (request.Lock) + { + post.Locked = now; + post.LockedBy = user.UserName; + if (!string.IsNullOrEmpty(request.Reason)) { - Id = request.Id, - }; + post.Notes = request.Reason; + } } - - public void Put(LockPost request) + else { - var user = GetUser(); - var post = AssertPost(request.Id); - AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); + post.Locked = null; + post.LockedBy = null; + } - if (!user.IsOrganizationModerator(orgMember)) - throw HttpError.Forbidden("Access Denied"); + Db.Update(post); - var now = DateTime.Now; - if (request.Lock) - { - post.Locked = now; - post.LockedBy = user.UserName; - if (!string.IsNullOrEmpty(request.Reason)) - { - post.Notes = request.Reason; - } - } - else - { - post.Locked = null; - post.LockedBy = null; - } + Db.Insert(new PostChangeHistory { + ChangedName = nameof(post.Locked), + ChangedValue = request.Lock.ToString(), + ChangedReason = request.Reason, + Created = now, + CreatedBy = user.UserName, + }); - Db.Update(post); + ClearPostCaches(); + } - Db.Insert(new PostChangeHistory { - ChangedName = nameof(post.Locked), - ChangedValue = request.Lock.ToString(), - ChangedReason = request.Reason, - Created = now, - CreatedBy = user.UserName, - }); + public void Put(HidePost request) + { + var user = GetUser(); + var post = AssertPost(request.Id); + AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); - ClearPostCaches(); - } + if (!user.IsOrganizationModerator(orgMember)) + throw HttpError.Forbidden("Access Denied"); - public void Put(HidePost request) + var now = DateTime.Now; + if (request.Hide) { - var user = GetUser(); - var post = AssertPost(request.Id); - AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); - - if (!user.IsOrganizationModerator(orgMember)) - throw HttpError.Forbidden("Access Denied"); - - var now = DateTime.Now; - if (request.Hide) + post.Hidden = now; + post.HiddenBy = user.UserName; + if (!string.IsNullOrEmpty(request.Reason)) { - post.Hidden = now; - post.HiddenBy = user.UserName; - if (!string.IsNullOrEmpty(request.Reason)) - { - post.Notes = request.Reason; - } - } - else - { - post.Hidden = null; - post.HiddenBy = null; + post.Notes = request.Reason; } + } + else + { + post.Hidden = null; + post.HiddenBy = null; + } - Db.Update(post); + Db.Update(post); - Db.Insert(new PostChangeHistory { - ChangedName = nameof(post.Hidden), - ChangedValue = request.Hide.ToString(), - ChangedReason = request.Reason, - Created = now, - CreatedBy = user.UserName, - }); - - ClearPostCaches(); - } + Db.Insert(new PostChangeHistory { + ChangedName = nameof(post.Hidden), + ChangedValue = request.Hide.ToString(), + ChangedReason = request.Reason, + Created = now, + CreatedBy = user.UserName, + }); + + ClearPostCaches(); + } - public void Put(ChangeStatusPost request) - { - var user = GetUser(); - var post = AssertPost(request.Id); - AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); + public void Put(ChangeStatusPost request) + { + var user = GetUser(); + var post = AssertPost(request.Id); + AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); - if (!user.IsOrganizationModerator(orgMember)) - throw HttpError.Forbidden("Access Denied"); + if (!user.IsOrganizationModerator(orgMember)) + throw HttpError.Forbidden("Access Denied"); - if (string.IsNullOrEmpty(request.Status)) - throw new ArgumentNullException(nameof(request.Status)); + if (string.IsNullOrEmpty(request.Status)) + throw new ArgumentNullException(nameof(request.Status)); - var now = DateTime.Now; - post.Status = request.Status; - post.StatusBy = user.UserName; - post.StatusDate = now; + var now = DateTime.Now; + post.Status = request.Status; + post.StatusBy = user.UserName; + post.StatusDate = now; - Db.Update(post); + Db.Update(post); - Db.Insert(new PostChangeHistory { - ChangedName = nameof(post.Status), - ChangedValue = request.Status, - ChangedReason = request.Reason, - Created = now, - CreatedBy = user.UserName, - }); - - ClearPostCaches(); - } - - public void Post(ActionPostReport request) - { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); + Db.Insert(new PostChangeHistory { + ChangedName = nameof(post.Status), + ChangedValue = request.Status, + ChangedReason = request.Reason, + Created = now, + CreatedBy = user.UserName, + }); + + ClearPostCaches(); + } - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); + public void Post(ActionPostReport request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); - var user = GetUser(); - var post = AssertPost(request.PostId); + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); - AssertOrganizationModerator(Db, post.OrganizationId, user, out var org, out var orgMember); + var user = GetUser(); + var post = AssertPost(request.PostId); - var now = DateTime.Now; - if (request.ReportAction == ReportAction.Dismiss) - { - Db.UpdateOnly(() => new PostReport { Dismissed = now, DismissedBy = user.UserName }, - where: x => x.OrganizationId == org.Id && x.Id == request.Id && x.PostId == post.Id); - } - else if (request.ReportAction == ReportAction.Delete) - { - Db.UpdateOnly(() => new Post { Deleted = now, DeletedBy = user.UserName }, - where: x => x.OrganizationId == org.Id && x.Id == post.Id); + AssertOrganizationModerator(Db, post.OrganizationId, user, out var org, out var orgMember); - Db.UpdateOnly(() => new PostReport { Acknowledged = now, AcknowledgedBy = user.UserName }, - where: x => x.OrganizationId == org.Id && x.Id == request.Id && x.PostId == post.Id); - } + var now = DateTime.Now; + if (request.ReportAction == ReportAction.Dismiss) + { + Db.UpdateOnly(() => new PostReport { Dismissed = now, DismissedBy = user.UserName }, + where: x => x.OrganizationId == org.Id && x.Id == request.Id && x.PostId == post.Id); + } + else if (request.ReportAction == ReportAction.Delete) + { + Db.UpdateOnly(() => new Post { Deleted = now, DeletedBy = user.UserName }, + where: x => x.OrganizationId == org.Id && x.Id == post.Id); - ClearPostCaches(); + Db.UpdateOnly(() => new PostReport { Acknowledged = now, AcknowledgedBy = user.UserName }, + where: x => x.OrganizationId == org.Id && x.Id == request.Id && x.PostId == post.Id); } - public async Task Post(CreatePostComment request) - { - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); - if (string.IsNullOrEmpty(request.Content)) - throw new ArgumentNullException(nameof(request.Content)); + ClearPostCaches(); + } + + public async Task Post(CreatePostComment request) + { + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); + if (string.IsNullOrEmpty(request.Content)) + throw new ArgumentNullException(nameof(request.Content)); - var user = GetUser(); - var post = await AssertPostAsync(request.PostId); - var groupMember = AssertCanCommentToOrganization(Db, post.OrganizationId, user); - AssertCanContributeToPost(post, user, groupMember); + var user = GetUser(); + var post = await AssertPostAsync(request.PostId); + var groupMember = AssertCanCommentToOrganization(Db, post.OrganizationId, user); + AssertCanContributeToPost(post, user, groupMember); - var userId = user.GetUserId(); - var comment = request.ConvertTo(); - comment.UserId = userId; - comment.CreatedBy = user.UserName; - comment.Created = comment.Modified = DateTime.Now; - comment.ContentHtml = await Markdown.TransformAsync(comment.Content, user.GetGitHubToken()); - comment.UpVotes = 0; + var userId = user.GetUserId(); + var comment = request.ConvertTo(); + comment.UserId = userId; + comment.CreatedBy = user.UserName; + comment.Created = comment.Modified = DateTime.Now; + comment.ContentHtml = await Markdown.TransformAsync(comment.Content, user.GetGitHubToken()); + comment.UpVotes = 0; - var id = await Db.InsertAsync(comment, selectIdentity: true); + var id = await Db.InsertAsync(comment, selectIdentity: true); - var now = DateTime.Now; + var now = DateTime.Now; - await Db.UpdateAddAsync(() => new UserActivity { Modified = now, PostCommentsCount = 1 }, - where: x => x.Id == comment.UserId); + await Db.UpdateAddAsync(() => new UserActivity { Modified = now, PostCommentsCount = 1 }, + where: x => x.Id == comment.UserId); - await Db.UpdateOnlyAsync(() => + await Db.UpdateOnlyAsync(() => new Post { LastCommentDate = now, LastCommentId = id, LastCommentUserId = userId }, - where: x => x.Id == request.PostId); + where: x => x.Id == request.PostId); - ClearPostCaches(); + ClearPostCaches(); - return new CreatePostCommentResponse - { - Id = id, - PostId = comment.PostId, - }; - } - - public async Task Put(UpdatePostComment request) + return new CreatePostCommentResponse { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); - if (string.IsNullOrEmpty(request.Content)) - throw new ArgumentNullException(nameof(request.Content)); - - var user = GetUser(); - var post = await AssertPostAsync(request.PostId); - var groupMember = AssertCanCommentToOrganization(Db, post.OrganizationId, user); - AssertCanContributeToPost(post, user, groupMember); - - var userId = user.GetUserId(); - - var html = await Markdown.TransformAsync(request.Content, user.GetGitHubToken()); - var rowsUpdated = !user.IsAdmin() - ? await Db.UpdateOnlyAsync(() => new PostComment { + Id = id, + PostId = comment.PostId, + }; + } + + public async Task Put(UpdatePostComment request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); + if (string.IsNullOrEmpty(request.Content)) + throw new ArgumentNullException(nameof(request.Content)); + + var user = GetUser(); + var post = await AssertPostAsync(request.PostId); + var groupMember = AssertCanCommentToOrganization(Db, post.OrganizationId, user); + AssertCanContributeToPost(post, user, groupMember); + + var userId = user.GetUserId(); + + var html = await Markdown.TransformAsync(request.Content, user.GetGitHubToken()); + var rowsUpdated = !user.IsAdmin() + ? await Db.UpdateOnlyAsync(() => new PostComment { Content = request.Content, ContentHtml = html, Modified = DateTime.Now, - }, - where: x => x.Id == request.Id && x.PostId == request.PostId && x.UserId == userId) - : await Db.UpdateOnlyAsync(() => new PostComment - { + }, + where: x => x.Id == request.Id && x.PostId == request.PostId && x.UserId == userId) + : await Db.UpdateOnlyAsync(() => new PostComment + { Content = request.Content, ContentHtml = html, Modified = DateTime.Now, - }, - where: x => x.Id == request.Id); + }, + where: x => x.Id == request.Id); - if (rowsUpdated == 0) - throw HttpError.NotFound("Comment does not exist"); + if (rowsUpdated == 0) + throw HttpError.NotFound("Comment does not exist"); - var now = DateTime.Now; - await Db.UpdateOnlyAsync(() => - new Post { LastCommentDate = now, LastCommentId = request.Id, LastCommentUserId = userId }, - where: x => x.Id == request.PostId); + var now = DateTime.Now; + await Db.UpdateOnlyAsync(() => + new Post { LastCommentDate = now, LastCommentId = request.Id, LastCommentUserId = userId }, + where: x => x.Id == request.PostId); - ClearPostCaches(); + ClearPostCaches(); - return new UpdatePostCommentResponse(); - } - - public object Delete(DeletePostComment request) - { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); + return new UpdatePostCommentResponse(); + } - var user = GetUser(); - var post = AssertPost(request.PostId); - var groupMember = AssertCanCommentToOrganization(Db, post.OrganizationId, user); - AssertCanContributeToPost(post, user, groupMember); + public object Delete(DeletePostComment request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); - var now = DateTime.Now; - var userId = user.GetUserId(); + var user = GetUser(); + var post = AssertPost(request.PostId); + var groupMember = AssertCanCommentToOrganization(Db, post.OrganizationId, user); + AssertCanContributeToPost(post, user, groupMember); - if (!user.IsOrganizationModerator(groupMember)) - { - Db.UpdateOnly(() => new PostComment - { - Deleted = now, - DeletedBy = user.UserName, - Modified = now, - }, - where: x => x.Id == request.Id && x.PostId == request.PostId && x.UserId == userId); - } - else - { - Db.UpdateOnly(() => new PostComment - { - Deleted = now, - DeletedBy = user.UserName, - Modified = now, - }, - where: x => x.Id == request.Id && x.PostId == request.PostId); - } + var now = DateTime.Now; + var userId = user.GetUserId(); - ClearPostCaches(); - - return new DeletePostCommentResponse - { - Id = request.Id, - PostId = request.PostId - }; + if (!user.IsOrganizationModerator(groupMember)) + { + Db.UpdateOnly(() => new PostComment + { + Deleted = now, + DeletedBy = user.UserName, + Modified = now, + }, + where: x => x.Id == request.Id && x.PostId == request.PostId && x.UserId == userId); } + else + { + Db.UpdateOnly(() => new PostComment + { + Deleted = now, + DeletedBy = user.UserName, + Modified = now, + }, + where: x => x.Id == request.Id && x.PostId == request.PostId); + } + + ClearPostCaches(); - public void Post(ActionPostCommentReport request) + return new DeletePostCommentResponse { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); + Id = request.Id, + PostId = request.PostId + }; + } - if (request.PostCommentId <= 0) - throw new ArgumentNullException(nameof(request.PostCommentId)); + public void Post(ActionPostCommentReport request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); + if (request.PostCommentId <= 0) + throw new ArgumentNullException(nameof(request.PostCommentId)); - var comment = AssertPostComment(request.PostCommentId); + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); - if (comment.PostId != request.PostId) - throw new ArgumentException("Invalid PostId", nameof(request.PostId)); + var comment = AssertPostComment(request.PostCommentId); - var user = GetUser(); - var post = AssertPost(comment.PostId); - AssertOrganizationModerator(Db, post.OrganizationId, user, out var org, out var orgMember); + if (comment.PostId != request.PostId) + throw new ArgumentException("Invalid PostId", nameof(request.PostId)); - var now = DateTime.Now; - if (request.ReportAction == ReportAction.Dismiss) - { - Db.UpdateOnly(() => new PostCommentReport { Dismissed = now, DismissedBy = user.UserName }, - where: x => x.OrganizationId == org.Id && x.Id == request.Id && x.PostCommentId == request.PostCommentId); - } - else if (request.ReportAction == ReportAction.Delete) - { - Db.UpdateOnly(() => new PostComment { Deleted = now, DeletedBy = user.UserName }, - where: x => x.PostId == post.Id && x.Id == request.PostCommentId); + var user = GetUser(); + var post = AssertPost(comment.PostId); + AssertOrganizationModerator(Db, post.OrganizationId, user, out var org, out var orgMember); - Db.UpdateOnly(() => new PostCommentReport { Acknowledged = now, AcknowledgedBy = user.UserName }, - where: x => x.OrganizationId == org.Id && x.Id == request.Id && x.PostCommentId == request.PostCommentId); - } + var now = DateTime.Now; + if (request.ReportAction == ReportAction.Dismiss) + { + Db.UpdateOnly(() => new PostCommentReport { Dismissed = now, DismissedBy = user.UserName }, + where: x => x.OrganizationId == org.Id && x.Id == request.Id && x.PostCommentId == request.PostCommentId); + } + else if (request.ReportAction == ReportAction.Delete) + { + Db.UpdateOnly(() => new PostComment { Deleted = now, DeletedBy = user.UserName }, + where: x => x.PostId == post.Id && x.Id == request.PostCommentId); - ClearPostCaches(); + Db.UpdateOnly(() => new PostCommentReport { Acknowledged = now, AcknowledgedBy = user.UserName }, + where: x => x.OrganizationId == org.Id && x.Id == request.Id && x.PostCommentId == request.PostCommentId); } - public object Get(GetUserPostCommentVotes request) - { - var userId = GetUserId(); + ClearPostCaches(); + } - var q = Db.From() - .Where(x => x.UserId == userId && x.PostId == request.PostId) - .Select(x => new { x.PostCommentId, x.Weight }); + public object Get(GetUserPostCommentVotes request) + { + var userId = GetUserId(); - var commentVotes = Db.Select<(long commentId, int weight)>(q); + var q = Db.From() + .Where(x => x.UserId == userId && x.PostId == request.PostId) + .Select(x => new { x.PostCommentId, x.Weight }); - return new GetUserPostCommentVotesResponse - { - PostId = request.PostId, - UpVotedCommentIds = commentVotes.Where(x => x.weight > 0).Map(x => x.commentId), - DownVotedCommentIds = commentVotes.Where(x => x.weight < 0).Map(x => x.commentId), - }; - } + var commentVotes = Db.Select<(long commentId, int weight)>(q); - public object Put(PinPostComment request) + return new GetUserPostCommentVotesResponse { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); + PostId = request.PostId, + UpVotedCommentIds = commentVotes.Where(x => x.weight > 0).Map(x => x.commentId), + DownVotedCommentIds = commentVotes.Where(x => x.weight < 0).Map(x => x.commentId), + }; + } - var user = GetUser(); - var post = AssertPost(request.PostId); - AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); - AssertCanContributeToPost(post, user, orgMember); + public object Put(PinPostComment request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); - if (post.UserId != user.GetUserId() && !user.IsOrganizationModerator(orgMember)) - throw HttpError.Forbidden("Only Post author can pin comments"); + var user = GetUser(); + var post = AssertPost(request.PostId); + AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); + AssertCanContributeToPost(post, user, orgMember); - Db.UpdateOnly(() => new Post - { - PinCommentId = request.Pin ? request.Id : (long?)null, - Modified = DateTime.Now, - ModifiedBy = user.UserName - }, - where: x => x.Id == request.PostId); + if (post.UserId != user.GetUserId() && !user.IsOrganizationModerator(orgMember)) + throw HttpError.Forbidden("Only Post author can pin comments"); - Db.ExecuteSql( - @"update user_activity set + Db.UpdateOnly(() => new Post + { + PinCommentId = request.Pin ? request.Id : (long?)null, + Modified = DateTime.Now, + ModifiedBy = user.UserName + }, + where: x => x.Id == request.PostId); + + Db.ExecuteSql( + @"update user_activity set pinned_comment_count = (select count(*) from post p join post_comment c on (p.pin_comment_id = c.id and p.user_id <> user_activity.id) where c.user_id = user_activity.id) where id = (select user_id from post_comment c where c.id = @id)", - new { id = request.Id }); + new { id = request.Id }); - ClearPostCaches(); + ClearPostCaches(); - return new PinPostCommentResponse(); - } + return new PinPostCommentResponse(); } - - - -} +} \ No newline at end of file diff --git a/TechStacks.ServiceInterface/PostServicesBase.cs b/TechStacks.ServiceInterface/PostServicesBase.cs index 8a93d79..d5f9edd 100644 --- a/TechStacks.ServiceInterface/PostServicesBase.cs +++ b/TechStacks.ServiceInterface/PostServicesBase.cs @@ -10,79 +10,79 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public class PostServicesBase : Service { - public class PostServicesBase : Service - { - public IMarkdownProvider Markdown { get; set; } + public IMarkdownProvider Markdown { get; set; } - public AuthUserSession GetUser() => SessionAs(); - public int GetUserId() => SessionAs().UserAuthId.ToInt(); + public AuthUserSession GetUser() => SessionAs(); + public int GetUserId() => SessionAs().UserAuthId.ToInt(); - private static readonly object syncObject = new object(); - private static List allOrgs; - private static List orgs; - internal static ILookup orgMembersLookup; - private static DateTime lastSynced = DateTime.MinValue; - private static readonly TimeSpan cacheExpiry = TimeSpan.FromMinutes(5); + private static readonly object syncObject = new object(); + private static List allOrgs; + private static List orgs; + internal static ILookup orgMembersLookup; + private static DateTime lastSynced = DateTime.MinValue; + private static readonly TimeSpan cacheExpiry = TimeSpan.FromMinutes(5); - protected void ClearPostCaches() => Cache.FlushAll(); + protected void ClearPostCaches() => Cache.FlushAll(); - protected void ClearOrganizationCaches() - { - ClearOrganizationCache(); - Cache.FlushAll(); - } + protected void ClearOrganizationCaches() + { + ClearOrganizationCache(); + Cache.FlushAll(); + } - public static void ClearOrganizationCache() - { - lastSynced = DateTime.MinValue; - } + public static void ClearOrganizationCache() + { + lastSynced = DateTime.MinValue; + } - public static Organization GetOrganization(IDbConnection db, int organizationId) - { - return GetOrganizations(db).FirstOrDefault(x => x.Id == organizationId); - } + public static Organization GetOrganization(IDbConnection db, int organizationId) + { + return GetOrganizations(db).FirstOrDefault(x => x.Id == organizationId); + } - public static Organization GetOrganizationByTechnologyId(IDbConnection db, int technologyId) - { - return GetOrganizations(db).FirstOrDefault(x => x.RefId == technologyId && x.RefSource == nameof(Technology)); - } + public static Organization GetOrganizationByTechnologyId(IDbConnection db, int technologyId) + { + return GetOrganizations(db).FirstOrDefault(x => x.RefId == technologyId && x.RefSource == nameof(Technology)); + } - public static List GetOrganizations(IDbConnection db) + public static List GetOrganizations(IDbConnection db) + { + if (orgs == null || DateTime.Now - lastSynced > cacheExpiry) { - if (orgs == null || DateTime.Now - lastSynced > cacheExpiry) - { - ReloadOrganizations(db); - PeriodicUpdateTableCaches(db); - } - return orgs; + ReloadOrganizations(db); + PeriodicUpdateTableCaches(db); } + return orgs; + } - public static void ReloadOrganizations(IDbConnection db) + public static void ReloadOrganizations(IDbConnection db) + { + lock (syncObject) { - lock (syncObject) - { - var now = DateTime.Now; - allOrgs = db.Select(); - orgs = allOrgs.Where(x => x.Deleted == null).ToList(); - orgMembersLookup = db.Select().ToLookup(x => x.OrganizationId); - lastSynced = now; - } + var now = DateTime.Now; + allOrgs = db.Select(); + orgs = allOrgs.Where(x => x.Deleted == null).ToList(); + orgMembersLookup = db.Select().ToLookup(x => x.OrganizationId); + lastSynced = now; } + } - public static void PeriodicUpdateTableCaches(IDbConnection db) => UpdatePostRanks(db); - public static void UpdatePostRanks(IDbConnection db) - { - db.ExecuteSql(@"update post + public static void PeriodicUpdateTableCaches(IDbConnection db) => UpdatePostRanks(db); + public static void UpdatePostRanks(IDbConnection db) + { + db.ExecuteSql(@"update post set rank = r.rank from (select rank() over(order by bumped desc nulls last, modified desc nulls last), p.id from post p) r where post.id = r.id"); - db.ExecuteSql(@"update post + db.ExecuteSql(@"update post set points = GREATEST(1 + (up_votes - down_votes) + points_modifier, 0)"); - db.ExecuteSql(@"update organization set + db.ExecuteSql(@"update organization set posts_count = (select count(*) from post p where organization_id = organization.id or (organization.ref_source = 'Technology' and p.technology_ids @> ARRAY[organization.ref_id]::int[])), comments_count = (select count(*) from post_comment c join post p on (c.post_id = p.id) where p.organization_id = organization.id), subscribers = (select count(*) from subscription where organization_id = organization.id), @@ -90,199 +90,198 @@ public static void UpdatePostRanks(IDbConnection db) up_votes = (select count(*) from post_vote v join post p on (v.post_id = p.id) where organization_id = organization.id and weight > 0), down_votes = (select count(*) from post_vote v join post p on (v.post_id = p.id) where organization_id = organization.id and weight < 0)"); - db.ExecuteSql(@"update organization + db.ExecuteSql(@"update organization set rank = r.rank from (select rank() over(order by posts_count desc), o.id from organization o) r where organization.id = r.id"); - } + } - private static void LoadOrganizations(IDbConnection db) => GetOrganizations(db); + private static void LoadOrganizations(IDbConnection db) => GetOrganizations(db); - public static List GetOrganizationMembers(int organizationId) - { - return orgMembersLookup[organizationId].ToList(); - } + public static List GetOrganizationMembers(int organizationId) + { + return orgMembersLookup[organizationId].ToList(); + } - public static int GetOrganizationMembersCount(int organizationId) - { - return orgMembersLookup?[organizationId].Count() ?? 0; - } + public static int GetOrganizationMembersCount(int organizationId) + { + return orgMembersLookup?[organizationId].Count() ?? 0; + } - public static void AssertOrganizationOwner(IDbConnection db, int orgId, AuthUserSession user, - out Organization organization, out OrganizationMember orgMember) - { - AssertCanViewOrganization(db, orgId, user, out organization, out orgMember); + public static void AssertOrganizationOwner(IDbConnection db, int orgId, AuthUserSession user, + out Organization organization, out OrganizationMember orgMember) + { + AssertCanViewOrganization(db, orgId, user, out organization, out orgMember); - if (!user.IsOrganizationOwner(orgMember)) - throw HttpError.Forbidden("This action is limited to Organization Owners"); - } + if (!user.IsOrganizationOwner(orgMember)) + throw HttpError.Forbidden("This action is limited to Organization Owners"); + } - public static void AssertOrganizationModerator(IDbConnection db, int orgId, AuthUserSession user, - out Organization organization, out OrganizationMember orgMember) - { - AssertCanViewOrganization(db, orgId, user, out organization, out orgMember); + public static void AssertOrganizationModerator(IDbConnection db, int orgId, AuthUserSession user, + out Organization organization, out OrganizationMember orgMember) + { + AssertCanViewOrganization(db, orgId, user, out organization, out orgMember); - if (!user.IsOrganizationModerator(orgMember)) - throw HttpError.Forbidden("This action is limited to Organization Moderators"); - } + if (!user.IsOrganizationModerator(orgMember)) + throw HttpError.Forbidden("This action is limited to Organization Moderators"); + } - public static OrganizationMember AssertCanAnnotateOnOrganization(IDbConnection db, int organizationId, AuthUserSession user) - { - return AssertCanAnnotateOnOrganization(db, organizationId, user, out var org); - } + public static OrganizationMember AssertCanAnnotateOnOrganization(IDbConnection db, int organizationId, AuthUserSession user) + { + return AssertCanAnnotateOnOrganization(db, organizationId, user, out var org); + } - public static OrganizationMember AssertCanAnnotateOnOrganization(IDbConnection db, int organizationId, AuthUserSession user, out Organization org) - { - AssertCanViewOrganization(db, organizationId, user, out org, out var orgMember); + public static OrganizationMember AssertCanAnnotateOnOrganization(IDbConnection db, int organizationId, AuthUserSession user, out Organization org) + { + AssertCanViewOrganization(db, organizationId, user, out org, out var orgMember); - if (orgMember?.DenyAll == true) - throw HttpError.Forbidden("Access has been suspended"); + if (orgMember?.DenyAll == true) + throw HttpError.Forbidden("Access has been suspended"); - if (org.Locked != null && orgMember == null) - throw HttpError.NotFound("Organization is locked to members only"); + if (org.Locked != null && orgMember == null) + throw HttpError.NotFound("Organization is locked to members only"); - return orgMember; - } + return orgMember; + } - public static OrganizationMember AssertCanCommentToOrganization(IDbConnection db, int organizationId, AuthUserSession user) - { - AssertCanViewOrganization(db, organizationId, user, out var org, out var orgMember); + public static OrganizationMember AssertCanCommentToOrganization(IDbConnection db, int organizationId, AuthUserSession user) + { + AssertCanViewOrganization(db, organizationId, user, out var org, out var orgMember); - if (orgMember?.DenyComments == true || orgMember?.DenyAll == true) - throw HttpError.Forbidden("Access has been suspended"); + if (orgMember?.DenyComments == true || orgMember?.DenyAll == true) + throw HttpError.Forbidden("Access has been suspended"); - if (org.Locked != null && orgMember == null) - throw HttpError.NotFound("Organization is locked to members only"); + if (org.Locked != null && orgMember == null) + throw HttpError.NotFound("Organization is locked to members only"); - return orgMember; - } + return orgMember; + } - public static void AssertCanPostToOrganization(IDbConnection db, int organizationId, AuthUserSession user, - out Organization org, out OrganizationMember orgMember) - { - AssertCanViewOrganization(db, organizationId, user, out org, out orgMember); + public static void AssertCanPostToOrganization(IDbConnection db, int organizationId, AuthUserSession user, + out Organization org, out OrganizationMember orgMember) + { + AssertCanViewOrganization(db, organizationId, user, out org, out orgMember); - if (orgMember?.DenyPosts == true || orgMember?.DenyAll == true) - throw HttpError.Forbidden("Access has been suspended"); + if (orgMember?.DenyPosts == true || orgMember?.DenyAll == true) + throw HttpError.Forbidden("Access has been suspended"); - if (org.Locked != null && orgMember == null) - throw HttpError.NotFound("Organization is locked to members only"); - } + if (org.Locked != null && orgMember == null) + throw HttpError.NotFound("Organization is locked to members only"); + } - public static void AssertCanPostTypeToOrganization(PostType postType, Organization org, - OrganizationMember orgMember, AuthUserSession user) + public static void AssertCanPostTypeToOrganization(PostType postType, Organization org, + OrganizationMember orgMember, AuthUserSession user) + { + if (user.IsOrganizationModerator(orgMember)) { - if (user.IsOrganizationModerator(orgMember)) + if (!org.ModeratorPostTypes.IsEmpty()) { - if (!org.ModeratorPostTypes.IsEmpty()) - { - if (!org.ModeratorPostTypes.Contains(postType.ToString())) - throw HttpError.Forbidden(postType + " is not an allowed Type for Moderators"); - return; - } + if (!org.ModeratorPostTypes.Contains(postType.ToString())) + throw HttpError.Forbidden(postType + " is not an allowed Type for Moderators"); + return; } + } - if (!org.PostTypes.IsEmpty()) - { - if (!org.PostTypes.Contains(postType.ToString())) - throw HttpError.Forbidden($"You cannot submit {postType} posts here"); - } + if (!org.PostTypes.IsEmpty()) + { + if (!org.PostTypes.Contains(postType.ToString())) + throw HttpError.Forbidden($"You cannot submit {postType} posts here"); } + } - public static void AssertCanViewOrganization(IDbConnection db, int organizationId, AuthUserSession user) => - AssertCanViewOrganization(db, organizationId, user, out _, out _); + public static void AssertCanViewOrganization(IDbConnection db, int organizationId, AuthUserSession user) => + AssertCanViewOrganization(db, organizationId, user, out _, out _); - public static void AssertCanViewOrganization(IDbConnection db, int organizationId, AuthUserSession user, - out Organization organization, out OrganizationMember organizationMember) - { - LoadOrganizations(db); - organization = allOrgs.FirstOrDefault(x => x.Id == organizationId); + public static void AssertCanViewOrganization(IDbConnection db, int organizationId, AuthUserSession user, + out Organization organization, out OrganizationMember organizationMember) + { + LoadOrganizations(db); + organization = allOrgs.FirstOrDefault(x => x.Id == organizationId); - AssertCanViewOrganization(db, organization, user, out organizationMember); - } + AssertCanViewOrganization(db, organization, user, out organizationMember); + } - public static void AssertCanViewOrganization(IDbConnection db, Organization organization, AuthUserSession user, out OrganizationMember organizationMember) - { - LoadOrganizations(db); + public static void AssertCanViewOrganization(IDbConnection db, Organization organization, AuthUserSession user, out OrganizationMember organizationMember) + { + LoadOrganizations(db); - var userId = user?.GetUserId(); - organizationMember = organization != null && userId != null - ? orgMembersLookup[organization.Id]?.FirstOrDefault(x => x.UserId == userId) - : null; + var userId = user?.GetUserId(); + organizationMember = organization != null && userId != null + ? orgMembersLookup[organization.Id]?.FirstOrDefault(x => x.UserId == userId) + : null; - if (organization == null || (organization.Deleted != null && !user.IsOrganizationModerator(organizationMember))) - throw HttpError.NotFound("Organization does not exist"); - } + if (organization == null || (organization.Deleted != null && !user.IsOrganizationModerator(organizationMember))) + throw HttpError.NotFound("Organization does not exist"); + } - protected Post AssertPost(long postId) - { - var post = Db.SingleById(postId); - return post ?? throw HttpError.NotFound("Post does not exist"); - } + protected Post AssertPost(long postId) + { + var post = Db.SingleById(postId); + return post ?? throw HttpError.NotFound("Post does not exist"); + } - protected PostComment AssertPostComment(long postCommentId) - { - var comment = Db.SingleById(postCommentId); - return comment ?? throw HttpError.NotFound("Comment does not exist"); - } + protected PostComment AssertPostComment(long postCommentId) + { + var comment = Db.SingleById(postCommentId); + return comment ?? throw HttpError.NotFound("Comment does not exist"); + } - protected async Task AssertPostAsync(long postId) - { - var post = await Db.SingleByIdAsync(postId); - return post ?? throw HttpError.NotFound("Post does not exist"); - } + protected async Task AssertPostAsync(long postId) + { + var post = await Db.SingleByIdAsync(postId); + return post ?? throw HttpError.NotFound("Post does not exist"); + } - public static void AssertCanAnnotatePost(Post post, AuthUserSession session, OrganizationMember organizationMember) - { - if (post == null || post.Deleted != null && !session.IsOrganizationModerator(organizationMember)) - throw HttpError.NotFound("Post does not exist"); + public static void AssertCanAnnotatePost(Post post, AuthUserSession session, OrganizationMember organizationMember) + { + if (post == null || post.Deleted != null && !session.IsOrganizationModerator(organizationMember)) + throw HttpError.NotFound("Post does not exist"); - if (post.Archived) - throw HttpError.NotFound("Post is archived"); - } + if (post.Archived) + throw HttpError.NotFound("Post is archived"); + } - protected void AssertCanContributeToPost(Post post, AuthUserSession session, OrganizationMember organizationMember) - { - AssertCanAnnotatePost(post, session, organizationMember); + protected void AssertCanContributeToPost(Post post, AuthUserSession session, OrganizationMember organizationMember) + { + AssertCanAnnotatePost(post, session, organizationMember); - if (post.Locked != null && !session.IsOrganizationModerator(organizationMember)) - throw HttpError.Unauthorized("Post is locked"); - } + if (post.Locked != null && !session.IsOrganizationModerator(organizationMember)) + throw HttpError.Unauthorized("Post is locked"); + } - protected void AssertCanUpdatePost(Post post, AuthUserSession session, OrganizationMember organizationMember) - { - AssertCanContributeToPost(post, session, organizationMember); + protected void AssertCanUpdatePost(Post post, AuthUserSession session, OrganizationMember organizationMember) + { + AssertCanContributeToPost(post, session, organizationMember); - if (post.UserId != session.GetUserId() && !session.IsOrganizationModerator(organizationMember)) - throw HttpError.Unauthorized("Access Denied"); - } + if (post.UserId != session.GetUserId() && !session.IsOrganizationModerator(organizationMember)) + throw HttpError.Unauthorized("Access Denied"); + } - Notification ToNotification(string eventName, string refType, long refId) => new Notification { - Event = eventName, - RefId = refId, - RefType = refType, - RefUrn = $"urn:{refType}:{refId}", - Created = DateTime.Now, - }; + Notification ToNotification(string eventName, string refType, long refId) => new Notification { + Event = eventName, + RefId = refId, + RefType = refType, + RefUrn = $"urn:{refType}:{refId}", + Created = DateTime.Now, + }; - public async Task SendNotificationAsync(string eventName, string refType, long refId) - { - var notificationId = await Db.InsertAsync(ToNotification(eventName, refType, refId), selectIdentity:true); - PublishMessage(new SendNotification { Id = notificationId }); - } + public async Task SendNotificationAsync(string eventName, string refType, long refId) + { + var notificationId = await Db.InsertAsync(ToNotification(eventName, refType, refId), selectIdentity:true); + PublishMessage(new SendNotification { Id = notificationId }); + } - public void SendNotification(string eventName, string refType, long refId) - { - var notificationId = Db.Insert(ToNotification(eventName, refType, refId), selectIdentity:true); - PublishMessage(new SendNotification { Id = notificationId }); - } + public void SendNotification(string eventName, string refType, long refId) + { + var notificationId = Db.Insert(ToNotification(eventName, refType, refId), selectIdentity:true); + PublishMessage(new SendNotification { Id = notificationId }); + } - public void SendSystemEmail(string subject, string body) - { - PublishMessage(new SendSystemEmail { - Subject = subject, - Body = body, - }); - } + public void SendSystemEmail(string subject, string body) + { + PublishMessage(new SendSystemEmail { + Subject = subject, + Body = body, + }); } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/PostUserServices.cs b/TechStacks.ServiceInterface/PostUserServices.cs index b9b58af..ad616eb 100644 --- a/TechStacks.ServiceInterface/PostUserServices.cs +++ b/TechStacks.ServiceInterface/PostUserServices.cs @@ -7,102 +7,102 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public class PostPublicUserServices : Service { - public class PostPublicUserServices : Service + public object Any(GetUsersByEmails request) { - public object Any(GetUsersByEmails request) - { - if (request.Emails.IsEmpty()) - return new GetUsersByEmailsResponse { Results = new List() }; + if (request.Emails.IsEmpty()) + return new GetUsersByEmailsResponse { Results = new List() }; - var users = Db.Select( - @"select distinct c.id, c.user_name, COALESCE(c.email,d.email) as email, c.ref_id, c.ref_source, c.ref_urn + var users = Db.Select( + @"select distinct c.id, c.user_name, COALESCE(c.email,d.email) as email, c.ref_id, c.ref_source, c.ref_urn from custom_user_auth c left join user_auth_details d on d.user_auth_id = c.id where c.email in (@Emails) or d.email in (@Emails)", new { request.Emails }); - return new GetUsersByEmailsResponse { - Results = users - }; - } + return new GetUsersByEmailsResponse { + Results = users + }; } +} - [Authenticate] - public class PostUserServices : PostServicesBase +[Authenticate] +public class PostUserServices : PostServicesBase +{ + public async Task Get(GetUserPostActivity request) { - public async Task Get(GetUserPostActivity request) - { - var userId = GetUserId(); - var recentVotes = DateTime.Now.AddMonths(-6); + var userId = GetUserId(); + var recentVotes = DateTime.Now.AddMonths(-6); - var q = Db.From() - .Where(x => x.UserId == userId && x.Created > recentVotes) - .Select(x => new { x.PostId, x.Weight }); + var q = Db.From() + .Where(x => x.UserId == userId && x.Created > recentVotes) + .Select(x => new { x.PostId, x.Weight }); - var postVotes = await Db.SelectAsync<(long postId, int weight)>(q); + var postVotes = await Db.SelectAsync<(long postId, int weight)>(q); - var favoritePostIds = await Db.ColumnAsync(Db.From() - .Where(x => x.UserId == userId) - .Select(x => x.PostId)); + var favoritePostIds = await Db.ColumnAsync(Db.From() + .Where(x => x.UserId == userId) + .Select(x => x.PostId)); - return new GetUserPostActivityResponse - { - UpVotedPostIds = postVotes.Where(x => x.weight > 0).Map(x => x.postId), - DownVotedPostIds = postVotes.Where(x => x.weight < 0).Map(x => x.postId), - FavoritePostIds = favoritePostIds, - }; - } - - public async Task Get(GetUserOrganizations request) + return new GetUserPostActivityResponse { - var userId = GetUserId(); - return new GetUserOrganizationsResponse - { - Members = await Db.SelectAsync(x => x.UserId == userId), - MemberInvites = await Db.SelectAsync(x => x.UserId == userId && x.Approved == null && x.Dismissed == null), - Subscriptions = await Db.SelectAsync(x => x.UserId == userId), - }; - } + UpVotedPostIds = postVotes.Where(x => x.weight > 0).Map(x => x.postId), + DownVotedPostIds = postVotes.Where(x => x.weight < 0).Map(x => x.postId), + FavoritePostIds = favoritePostIds, + }; + } - public async Task Put(UserPostVote request) + public async Task Get(GetUserOrganizations request) + { + var userId = GetUserId(); + return new GetUserOrganizationsResponse { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); + Members = await Db.SelectAsync(x => x.UserId == userId), + MemberInvites = await Db.SelectAsync(x => x.UserId == userId && x.Approved == null && x.Dismissed == null), + Subscriptions = await Db.SelectAsync(x => x.UserId == userId), + }; + } - var user = GetUser(); - var post = await AssertPostAsync(request.Id); - var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user); - AssertCanAnnotatePost(post, user, groupMember); + public async Task Put(UserPostVote request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); + + var user = GetUser(); + var post = await AssertPostAsync(request.Id); + var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user); + AssertCanAnnotatePost(post, user, groupMember); - var userId = GetUserId(); + var userId = GetUserId(); - await Db.DeleteAsync(x => x.UserId == userId && x.PostId == request.Id); - if (request.Weight != 0) + await Db.DeleteAsync(x => x.UserId == userId && x.PostId == request.Id); + if (request.Weight != 0) + { + await Db.InsertAsync(new PostVote { - await Db.InsertAsync(new PostVote - { - UserId = userId, - PostId = request.Id, - Weight = request.Weight < 0 ? -1 : 1, - Created = DateTime.Now, - }); - } - - await Db.ExecuteSqlAsync( - @"update post set + UserId = userId, + PostId = request.Id, + Weight = request.Weight < 0 ? -1 : 1, + Created = DateTime.Now, + }); + } + + await Db.ExecuteSqlAsync( + @"update post set up_votes = (select count(*) from post_vote where post_id = @id and weight > 0), down_votes = (select count(*) from post_vote where post_id = @id and weight < 0) where id = @id", new { id = request.Id }); - await Db.ExecuteSqlAsync(@"update post + await Db.ExecuteSqlAsync(@"update post set points = GREATEST(1 + (up_votes - down_votes) + points_modifier, 0) where id = @id", new { id = request.Id }); - await Db.ExecuteSqlAsync( - @"update user_activity set + await Db.ExecuteSqlAsync( + @"update user_activity set post_up_votes = (select count(*) from post_vote v join post p on p.id = v.post_id where p.user_id = user_activity.id and weight > 0), @@ -110,122 +110,122 @@ await Db.ExecuteSqlAsync( join post p on p.id = v.post_id where p.user_id = user_activity.id and weight < 0) where id = (select user_id from post where id = @id)", - new { id = request.Id }); + new { id = request.Id }); - ClearPostCaches(); + ClearPostCaches(); - return new UserPostVoteResponse(); - } + return new UserPostVoteResponse(); + } - public async Task Put(UserPostFavorite request) - { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); + public async Task Put(UserPostFavorite request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); - var user = GetUser(); - var post = await AssertPostAsync(request.Id); - var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user); - AssertCanAnnotatePost(post, user, groupMember); + var user = GetUser(); + var post = await AssertPostAsync(request.Id); + var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user); + AssertCanAnnotatePost(post, user, groupMember); - var userId = GetUserId(); + var userId = GetUserId(); - var exists = await Db.DeleteAsync(x => x.UserId == userId && x.PostId == request.Id); + var exists = await Db.DeleteAsync(x => x.UserId == userId && x.PostId == request.Id); - if (exists == 0) + if (exists == 0) + { + await Db.InsertAsync(new PostFavorite { - await Db.InsertAsync(new PostFavorite - { - UserId = userId, - PostId = request.Id, - Created = DateTime.Now, - }); - } - - await Db.ExecuteSqlAsync( - @"update post set + UserId = userId, + PostId = request.Id, + Created = DateTime.Now, + }); + } + + await Db.ExecuteSqlAsync( + @"update post set favorites = (select count(*) from post_favorite where post_id = @id) where id = @id", new { id = request.Id }); - return new UserPostFavoriteResponse(); - } - - public object Put(UserPostReport request) - { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); + return new UserPostFavoriteResponse(); + } - var user = GetUser(); - var post = AssertPost(request.Id); - var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user, out var org); - AssertCanAnnotatePost(post, user, groupMember); + public object Put(UserPostReport request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); - var userId = GetUserId(); + var user = GetUser(); + var post = AssertPost(request.Id); + var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user, out var org); + AssertCanAnnotatePost(post, user, groupMember); - Db.Delete(x => x.UserId == userId && x.PostId == request.Id); - var reportId = Db.Insert(new PostReport - { - UserId = userId, - UserName = user.UserName, - OrganizationId = org.Id, - PostId = request.Id, - FlagType = request.FlagType, - ReportNotes = request.ReportNotes, - Created = DateTime.Now, - }, selectIdentity:true); + var userId = GetUserId(); - var reportsCount = Db.Count(x => x.OrganizationId == org.Id && x.PostId == request.Id); - if (reportsCount >= org.DeletePostsWithReportCount) - { - Db.UpdateOnly(() => new Post { Deleted = DateTime.Now, DeletedBy = nameof(Organization.DeletePostsWithReportCount) }, - @where: x => x.Id == request.Id); - } + Db.Delete(x => x.UserId == userId && x.PostId == request.Id); + var reportId = Db.Insert(new PostReport + { + UserId = userId, + UserName = user.UserName, + OrganizationId = org.Id, + PostId = request.Id, + FlagType = request.FlagType, + ReportNotes = request.ReportNotes, + Created = DateTime.Now, + }, selectIdentity:true); + + var reportsCount = Db.Count(x => x.OrganizationId == org.Id && x.PostId == request.Id); + if (reportsCount >= org.DeletePostsWithReportCount) + { + Db.UpdateOnly(() => new Post { Deleted = DateTime.Now, DeletedBy = nameof(Organization.DeletePostsWithReportCount) }, + @where: x => x.Id == request.Id); + } - SendNotification(nameof(UserPostReport), nameof(PostReport), reportId); + SendNotification(nameof(UserPostReport), nameof(PostReport), reportId); - Db.ExecuteSql( - @"update post set + Db.ExecuteSql( + @"update post set report_count = (select count(*) from post_report where organization_id = @orgId and post_id = @id) where id = @id", new { orgId = org.Id, id = request.Id }); - return new UserPostReportResponse(); - } + return new UserPostReportResponse(); + } - public async Task Put(UserPostCommentVote request) - { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); + public async Task Put(UserPostCommentVote request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); - var user = GetUser(); - var post = await AssertPostAsync(request.PostId); - var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user); - AssertCanAnnotatePost(post, user, groupMember); + var user = GetUser(); + var post = await AssertPostAsync(request.PostId); + var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user); + AssertCanAnnotatePost(post, user, groupMember); - var userId = GetUserId(); + var userId = GetUserId(); - await Db.DeleteAsync(x => x.UserId == userId && x.PostCommentId == request.Id); - if (request.Weight != 0) + await Db.DeleteAsync(x => x.UserId == userId && x.PostCommentId == request.Id); + if (request.Weight != 0) + { + await Db.InsertAsync(new PostCommentVote { - await Db.InsertAsync(new PostCommentVote - { - UserId = userId, - PostId = request.PostId, - PostCommentId = request.Id, - Weight = request.Weight < 0 ? -1 : 1, - Created = DateTime.Now, - }); - } - - await Db.ExecuteSqlAsync( - @"update post_comment set + UserId = userId, + PostId = request.PostId, + PostCommentId = request.Id, + Weight = request.Weight < 0 ? -1 : 1, + Created = DateTime.Now, + }); + } + + await Db.ExecuteSqlAsync( + @"update post_comment set up_votes = (select count(*) from post_comment_vote where post_comment_id = @id and weight > 0), down_votes = (select count(*) from post_comment_vote where post_comment_id = @id and weight < 0) where id = @id", - new { id = request.Id }); + new { id = request.Id }); - await Db.ExecuteSqlAsync( - @"update user_activity set + await Db.ExecuteSqlAsync( + @"update user_activity set comment_up_votes = (select count(*) from post_comment_vote v join post_comment c on c.id = v.post_comment_id where c.user_id = user_activity.id and weight > 0), @@ -233,55 +233,54 @@ await Db.ExecuteSqlAsync( join post_comment c on c.id = v.post_comment_id where c.user_id = user_activity.id and weight < 0) where id = (select user_id from post_comment where id = @id)", - new { id = request.Id }); + new { id = request.Id }); - ClearPostCaches(); + ClearPostCaches(); - return new UserPostCommentVoteResponse(); - } + return new UserPostCommentVoteResponse(); + } - public object Put(UserPostCommentReport request) + public object Put(UserPostCommentReport request) + { + if (request.Id <= 0) + throw new ArgumentNullException(nameof(request.Id)); + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); + + var user = GetUser(); + var post = AssertPost(request.PostId); + var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user, out var org); + AssertCanAnnotatePost(post, user, groupMember); + + var userId = user.GetUserId(); + Db.Delete(x => x.UserId == userId && x.PostCommentId == request.Id); + var reportId = Db.Insert(new PostCommentReport { - if (request.Id <= 0) - throw new ArgumentNullException(nameof(request.Id)); - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); - - var user = GetUser(); - var post = AssertPost(request.PostId); - var groupMember = AssertCanAnnotateOnOrganization(Db, post.OrganizationId, user, out var org); - AssertCanAnnotatePost(post, user, groupMember); - - var userId = user.GetUserId(); - Db.Delete(x => x.UserId == userId && x.PostCommentId == request.Id); - var reportId = Db.Insert(new PostCommentReport - { - UserId = userId, - UserName = user.UserName, - OrganizationId = org.Id, - PostId = post.Id, - PostCommentId = request.Id, - FlagType = request.FlagType, - ReportNotes = request.ReportNotes, - Created = DateTime.Now, - }, selectIdentity:true); - - var reportsCount = Db.Count(x => x.OrganizationId == org.Id && x.PostCommentId == request.Id); - if (reportsCount >= org.DeletePostsWithReportCount) - { - Db.UpdateOnly(() => new PostComment { Deleted = DateTime.Now, DeletedBy = nameof(Organization.DeletePostsWithReportCount) }, - where: x => x.Id == request.Id); - } + UserId = userId, + UserName = user.UserName, + OrganizationId = org.Id, + PostId = post.Id, + PostCommentId = request.Id, + FlagType = request.FlagType, + ReportNotes = request.ReportNotes, + Created = DateTime.Now, + }, selectIdentity:true); + + var reportsCount = Db.Count(x => x.OrganizationId == org.Id && x.PostCommentId == request.Id); + if (reportsCount >= org.DeletePostsWithReportCount) + { + Db.UpdateOnly(() => new PostComment { Deleted = DateTime.Now, DeletedBy = nameof(Organization.DeletePostsWithReportCount) }, + where: x => x.Id == request.Id); + } - SendNotification(nameof(UserPostCommentReport), nameof(PostCommentReport), reportId); + SendNotification(nameof(UserPostCommentReport), nameof(PostCommentReport), reportId); - Db.ExecuteSql( - @"update post_comment set + Db.ExecuteSql( + @"update post_comment set report_count = (select count(*) from post_comment_report where organization_id = @orgId and post_comment_id = @Id) where id = @id", new { orgId = org.Id, request.Id }); - return new UserPostCommentReportResponse(); - } - + return new UserPostCommentReportResponse(); } + } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/PreRenderService.cs b/TechStacks.ServiceInterface/PreRenderService.cs index bb59f8c..52ec6b3 100644 --- a/TechStacks.ServiceInterface/PreRenderService.cs +++ b/TechStacks.ServiceInterface/PreRenderService.cs @@ -4,54 +4,53 @@ using ServiceStack.OrmLite; using TechStacks.ServiceModel; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public class PreRenderService : PostServicesBase { - public class PreRenderService : PostServicesBase - { - string GetPath() => Request.RawUrl.StartsWith("/prerender") - ? Request.RawUrl.Substring("/prerender".Length) - : Request.RawUrl; + string GetPath() => Request.RawUrl.StartsWith("/prerender") + ? Request.RawUrl.Substring("/prerender".Length) + : Request.RawUrl; // [Authenticate] - public async Task Put(StorePreRender request) - { - var user = GetUser(); + public async Task Put(StorePreRender request) + { + var user = GetUser(); - var bytes = request.RequestStream.ReadFully(); + var bytes = request.RequestStream.ReadFully(); - var path = GetPath(); + var path = GetPath(); - var updated = await Db.UpdateOnlyAsync(() => + var updated = await Db.UpdateOnlyAsync(() => new PreRender { Data = bytes, ContentType = Request.ContentType, Modified = DateTime.Now, ModfiedBy = user.UserName, }, - where:x => x.Path == path); + where:x => x.Path == path); - if (updated == 0) - { - Db.Insert(new PreRender { - Path = path, - Data = bytes, - ContentType = Request.ContentType, - Created = DateTime.Now, - CreatedBy = user.UserName, - Modified = DateTime.Now, - ModfiedBy = user.UserName, - }); - } + if (updated == 0) + { + Db.Insert(new PreRender { + Path = path, + Data = bytes, + ContentType = Request.ContentType, + Created = DateTime.Now, + CreatedBy = user.UserName, + Modified = DateTime.Now, + ModfiedBy = user.UserName, + }); } + } - public async Task Get(GetPreRender request) - { - var path = GetPath(); - var prerender = Db.SingleById(path); - if (prerender == null) - throw HttpError.NotFound(path); + public async Task Get(GetPreRender request) + { + var path = GetPath(); + var prerender = Db.SingleById(path); + if (prerender == null) + throw HttpError.NotFound(path); - await Response.WriteBytesToResponse(prerender.Data, prerender.ContentType); - } + await Response.WriteBytesToResponse(prerender.Data, prerender.ContentType); } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/SessionInfoServices.cs b/TechStacks.ServiceInterface/SessionInfoServices.cs index 1ed3d10..9cf357b 100644 --- a/TechStacks.ServiceInterface/SessionInfoServices.cs +++ b/TechStacks.ServiceInterface/SessionInfoServices.cs @@ -3,34 +3,33 @@ using ServiceStack.Auth; using TechStacks.ServiceModel; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +[Authenticate] +[CacheResponse(Duration = 3600, VaryByUser = true)] +public class SessionInfoServices : Service { - [Authenticate] - [CacheResponse(Duration = 3600, VaryByUser = true)] - public class SessionInfoServices : Service + public async Task Any(SessionInfo request) { - public async Task Any(SessionInfo request) - { - var session = SessionAs(); - var response = session.ConvertTo(); + var session = SessionAs(); + var response = session.ConvertTo(); - var userInfoTask = Gateway.SendAsync(new GetUserInfo { - UserName = session.UserName, - }); + var userInfoTask = Gateway.SendAsync(new GetUserInfo { + UserName = session.UserName, + }); - var userOrgTask = Gateway.SendAsync(new GetUserOrganizations()); + var userOrgTask = Gateway.SendAsync(new GetUserOrganizations()); - var userInfo = await userInfoTask; - response.PopulateWith(userInfo); - if (response.ProfileUrl == null) - response.ProfileUrl = response.AvatarUrl; + var userInfo = await userInfoTask; + response.PopulateWith(userInfo); + if (response.ProfileUrl == null) + response.ProfileUrl = response.AvatarUrl; - var userOrg = await userOrgTask; - response.Members = userOrg.Members; - response.MemberInvites = userOrg.MemberInvites; - response.Subscriptions = userOrg.Subscriptions; + var userOrg = await userOrgTask; + response.Members = userOrg.Members; + response.MemberInvites = userOrg.MemberInvites; + response.Subscriptions = userOrg.Subscriptions; - return response; - } + return response; } -} +} \ No newline at end of file diff --git a/TechStacks.ServiceInterface/SubscriptionServices.cs b/TechStacks.ServiceInterface/SubscriptionServices.cs index 3507357..c366d0a 100644 --- a/TechStacks.ServiceInterface/SubscriptionServices.cs +++ b/TechStacks.ServiceInterface/SubscriptionServices.cs @@ -6,95 +6,94 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +[Authenticate] +public class SubscriptionServices : PostServicesBase { - [Authenticate] - public class SubscriptionServices : PostServicesBase + public void Put(SubscribeToOrganization request) { - public void Put(SubscribeToOrganization request) - { - if (request.OrganizationId <= 0) - throw new ArgumentNullException(nameof(request.OrganizationId)); + if (request.OrganizationId <= 0) + throw new ArgumentNullException(nameof(request.OrganizationId)); - var user = GetUser(); - var userId = user.GetUserId(); + var user = GetUser(); + var userId = user.GetUserId(); - AssertCanViewOrganization(Db, request.OrganizationId, user); + AssertCanViewOrganization(Db, request.OrganizationId, user); - var rowsDeleted = Db.Delete(x => - x.OrganizationId == request.OrganizationId && x.UserId == userId); + var rowsDeleted = Db.Delete(x => + x.OrganizationId == request.OrganizationId && x.UserId == userId); - var now = DateTime.Now; - var sub = new OrganizationSubscription - { - OrganizationId = request.OrganizationId, - UserId = userId, - UserName = user.UserName, - PostTypes = request.PostTypes.Select(x => x.ToString()).ToArray(), - FrequencyDays = request.Frequency != null ? (int)request.Frequency.Value : (int?)null, - Created = now, - }; + var now = DateTime.Now; + var sub = new OrganizationSubscription + { + OrganizationId = request.OrganizationId, + UserId = userId, + UserName = user.UserName, + PostTypes = request.PostTypes.Select(x => x.ToString()).ToArray(), + FrequencyDays = request.Frequency != null ? (int)request.Frequency.Value : (int?)null, + Created = now, + }; - Db.Insert(sub); + Db.Insert(sub); - ClearOrganizationCaches(); - } + ClearOrganizationCaches(); + } - public void Put(SubscribeToPost request) - { - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); + public void Put(SubscribeToPost request) + { + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); - var user = GetUser(); - var userId = user.GetUserId(); + var user = GetUser(); + var userId = user.GetUserId(); - var post = AssertPost(request.PostId); + var post = AssertPost(request.PostId); - AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); - AssertCanAnnotatePost(post, user, orgMember); + AssertCanPostToOrganization(Db, post.OrganizationId, user, out var org, out var orgMember); + AssertCanAnnotatePost(post, user, orgMember); - var rowsDeleted = Db.Delete(x => - x.PostId == request.PostId && x.UserId == userId); + var rowsDeleted = Db.Delete(x => + x.PostId == request.PostId && x.UserId == userId); - var now = DateTime.Now; - var sub = new SubscribePost - { - PostId = request.PostId, - OrganizationId = post.OrganizationId, - UserId = userId, - UserName = user.UserName, - Created = now, - }; + var now = DateTime.Now; + var sub = new SubscribePost + { + PostId = request.PostId, + OrganizationId = post.OrganizationId, + UserId = userId, + UserName = user.UserName, + Created = now, + }; - Db.Insert(sub); + Db.Insert(sub); - ClearPostCaches(); - } + ClearPostCaches(); + } - public void Delete(DeleteOrganizationSubscription request) - { - if (request.OrganizationId <= 0) - throw new ArgumentNullException(nameof(request.OrganizationId)); + public void Delete(DeleteOrganizationSubscription request) + { + if (request.OrganizationId <= 0) + throw new ArgumentNullException(nameof(request.OrganizationId)); - var userId = GetUserId(); + var userId = GetUserId(); - var rowsDeleted = Db.Delete(x => - x.OrganizationId == request.OrganizationId && x.UserId == userId); + var rowsDeleted = Db.Delete(x => + x.OrganizationId == request.OrganizationId && x.UserId == userId); - ClearOrganizationCaches(); - } + ClearOrganizationCaches(); + } - public void Delete(DeletePostSubscription request) - { - if (request.PostId <= 0) - throw new ArgumentNullException(nameof(request.PostId)); + public void Delete(DeletePostSubscription request) + { + if (request.PostId <= 0) + throw new ArgumentNullException(nameof(request.PostId)); - var userId = GetUserId(); + var userId = GetUserId(); - var rowsDeleted = Db.Delete(x => - x.PostId == request.PostId && x.UserId == userId); + var rowsDeleted = Db.Delete(x => + x.PostId == request.PostId && x.UserId == userId); - ClearPostCaches(); - } + ClearPostCaches(); } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/TechExtensions.cs b/TechStacks.ServiceInterface/TechExtensions.cs index d51a690..949ee69 100644 --- a/TechStacks.ServiceInterface/TechExtensions.cs +++ b/TechStacks.ServiceInterface/TechExtensions.cs @@ -9,77 +9,77 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public static class TechExtensions { - public static class TechExtensions - { - private static ILog Log = LogManager.GetLogger(typeof(TechExtensions)); + private static ILog Log = LogManager.GetLogger(typeof(TechExtensions)); - public static TechnologyInStack ToTechnologyInStack(this TechnologyChoice technologyChoice) - { - var result = technologyChoice.ConvertTo(); - result.PopulateWith(technologyChoice.Technology); - result.Id = technologyChoice.Id; - return result; - } + public static TechnologyInStack ToTechnologyInStack(this TechnologyChoice technologyChoice) + { + var result = technologyChoice.ConvertTo(); + result.PopulateWith(technologyChoice.Technology); + result.Id = technologyChoice.Id; + return result; + } - private static readonly Regex InvalidCharsRegex = new Regex(@"[^a-z0-9\s-]", RegexOptions.Compiled); - private static readonly Regex CollapseSpacesRegex = new Regex(@"\s+", RegexOptions.Compiled); - private static readonly Regex SpacesRegex = new Regex(@"\s", RegexOptions.Compiled); - private static readonly Regex CollapseHyphensRegex = new Regex("-+", RegexOptions.Compiled); + private static readonly Regex InvalidCharsRegex = new Regex(@"[^a-z0-9\s-]", RegexOptions.Compiled); + private static readonly Regex CollapseSpacesRegex = new Regex(@"\s+", RegexOptions.Compiled); + private static readonly Regex SpacesRegex = new Regex(@"\s", RegexOptions.Compiled); + private static readonly Regex CollapseHyphensRegex = new Regex("-+", RegexOptions.Compiled); - /// - /// From http://stackoverflow.com/a/2921135/670151 - /// - /// - /// - public static string GenerateSlug(this string phrase) - { - var str = phrase.RemoveAccent().ToLower() - .Replace("#", "sharp") // c#, f# => csharp, fsharp - .Replace("++", "pp"); // c++ => cpp + /// + /// From http://stackoverflow.com/a/2921135/670151 + /// + /// + /// + public static string GenerateSlug(this string phrase) + { + var str = phrase.RemoveAccent().ToLower() + .Replace("#", "sharp") // c#, f# => csharp, fsharp + .Replace("++", "pp"); // c++ => cpp - str = InvalidCharsRegex.Replace(str, "-"); - //// convert multiple spaces into one space - //str = CollapseSpacesRegex.Replace(str, " ").Trim(); - // cut and trim - str = str.Substring(0, str.Length <= 100 ? str.Length : 100).Trim(); - str = SpacesRegex.Replace(str, "-"); - str = CollapseHyphensRegex.Replace(str, "-"); + str = InvalidCharsRegex.Replace(str, "-"); + //// convert multiple spaces into one space + //str = CollapseSpacesRegex.Replace(str, " ").Trim(); + // cut and trim + str = str.Substring(0, str.Length <= 100 ? str.Length : 100).Trim(); + str = SpacesRegex.Replace(str, "-"); + str = CollapseHyphensRegex.Replace(str, "-"); - if (string.IsNullOrEmpty(str)) - return null; + if (string.IsNullOrEmpty(str)) + return null; - if (str[0] == '-') - str = str.Substring(1); - if (str[str.Length - 1] == '-') - str = str.Substring(0, str.Length - 1); + if (str[0] == '-') + str = str.Substring(1); + if (str[str.Length - 1] == '-') + str = str.Substring(0, str.Length - 1); - return str; - } + return str; + } - public static string RemoveAccent(this string txt) - { - // https://github.com/dotnet/corefx/issues/9158 - // byte[] bytes = System.Text.Encoding.GetEncoding("Cyrillic").GetBytes(txt); - // return System.Text.Encoding.ASCII.GetString(bytes); - return txt; - } + public static string RemoveAccent(this string txt) + { + // https://github.com/dotnet/corefx/issues/9158 + // byte[] bytes = System.Text.Encoding.GetEncoding("Cyrillic").GetBytes(txt); + // return System.Text.Encoding.ASCII.GetString(bytes); + return txt; + } - public static Task RegisterPageView(this IDbConnectionFactory dbFactory, string id) - { - var db = dbFactory.Open(); + public static Task RegisterPageView(this IDbConnectionFactory dbFactory, string id) + { + var db = dbFactory.Open(); - return db.ExecuteSqlAsync("UPDATE page_stats SET view_count = view_count + 1 WHERE id = @id", new { id }) - .ContinueWith(t => + return db.ExecuteSqlAsync("UPDATE page_stats SET view_count = view_count + 1 WHERE id = @id", new { id }) + .ContinueWith(t => + { + var parts = id.Substring(1).SplitOnFirst('/'); + if (t.Result == 0 && parts.Length == 2) { - var parts = id.Substring(1).SplitOnFirst('/'); - if (t.Result == 0 && parts.Length == 2) - { - var type = parts[0]; - var slug = parts[1]; + var type = parts[0]; + var slug = parts[1]; - return db.InsertAsync(new PageStats + return db.InsertAsync(new PageStats { Id = id, RefType = type, @@ -88,10 +88,9 @@ public static Task RegisterPageView(this IDbConnectionFactory dbFactory, string LastModified = DateTime.UtcNow, }) .ContinueWith(t2 => (int)t2.Result); - } + } - return t; - }).ContinueWith(_ => db.Dispose()); - } + return t; + }).ContinueWith(_ => db.Dispose()); } -} +} \ No newline at end of file diff --git a/TechStacks.ServiceInterface/TechStackQueries.cs b/TechStacks.ServiceInterface/TechStackQueries.cs index b8ef391..3f366a3 100644 --- a/TechStacks.ServiceInterface/TechStackQueries.cs +++ b/TechStacks.ServiceInterface/TechStackQueries.cs @@ -6,36 +6,35 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public static class TechStackQueries { - public static class TechStackQueries + public static List GetTechstackDetails(this IDbConnection db, SqlExpression stackQuery) { - public static List GetTechstackDetails(this IDbConnection db, SqlExpression stackQuery) - { - //distinct - var latestStacks = db.Select(stackQuery) - .GroupBy(x => x.Id) - .Select(x => x.First()) - .ToList(); + //distinct + var latestStacks = db.Select(stackQuery) + .GroupBy(x => x.Id) + .Select(x => x.First()) + .ToList(); - if (latestStacks.Count == 0) - return new List(); + if (latestStacks.Count == 0) + return new List(); - var technologyChoices = - db.LoadSelect(db.From() - .Join() - .Join() - .Where(techChoice => - Sql.In(techChoice.TechnologyStackId, latestStacks.Select(x => x.Id)) - )); + var technologyChoices = + db.LoadSelect(db.From() + .Join() + .Join() + .Where(techChoice => + Sql.In(techChoice.TechnologyStackId, latestStacks.Select(x => x.Id)) + )); - var stackDetails = latestStacks.Map(x => x.ConvertTo()); - stackDetails.ForEach(stack => stack.TechnologyChoices = technologyChoices - .Map(x => x.ToTechnologyInStack()) - .Where(x => stack.Id == x.TechnologyStackId) - .ToList()); + var stackDetails = latestStacks.Map(x => x.ConvertTo()); + stackDetails.ForEach(stack => stack.TechnologyChoices = technologyChoices + .Map(x => x.ToTechnologyInStack()) + .Where(x => stack.Id == x.TechnologyStackId) + .ToList()); - return stackDetails; - } + return stackDetails; } -} +} \ No newline at end of file diff --git a/TechStacks.ServiceInterface/TechnologyServices.cs b/TechStacks.ServiceInterface/TechnologyServices.cs index 6339a7a..361c5e8 100644 --- a/TechStacks.ServiceInterface/TechnologyServices.cs +++ b/TechStacks.ServiceInterface/TechnologyServices.cs @@ -6,88 +6,87 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public class TechnologyServices : Service { - public class TechnologyServices : Service + public object Get(GetTechnologyPreviousVersions request) { - public object Get(GetTechnologyPreviousVersions request) - { - if (request.Slug == null) - throw new ArgumentNullException(nameof(request.Slug)); - - if (!long.TryParse(request.Slug, out var id)) - { - var tech = Db.Single(x => x.Slug == request.Slug.ToLower()); - id = tech.Id; - } + if (request.Slug == null) + throw new ArgumentNullException(nameof(request.Slug)); - return new GetTechnologyPreviousVersionsResponse - { - Results = Db.Select(Db.From() - .Where(x => x.TechnologyId == id) - .OrderByDescending(x => x.LastModified)) - }; + if (!long.TryParse(request.Slug, out var id)) + { + var tech = Db.Single(x => x.Slug == request.Slug.ToLower()); + id = tech.Id; } - public object Get(GetAllTechnologies request) + return new GetTechnologyPreviousVersionsResponse { - return new GetAllTechnologiesResponse - { - Results = Db.Select(Db.From().OrderByDescending(x => x.LastModified).Take(100)).ToList(), - Total = Db.Count(), - }; - } + Results = Db.Select(Db.From() + .Where(x => x.TechnologyId == id) + .OrderByDescending(x => x.LastModified)) + }; } - [CacheResponse(Duration = 3600)] - public class CachedTechnologyServices : Service + public object Get(GetAllTechnologies request) { - public IAutoQueryDb AutoQuery { get; set; } + return new GetAllTechnologiesResponse + { + Results = Db.Select(Db.From().OrderByDescending(x => x.LastModified).Take(100)).ToList(), + Total = Db.Count(), + }; + } +} - public object Any(FindTechnologies request) => - AutoQuery.Execute(request, AutoQuery.CreateQuery(request, Request.GetRequestParams())); +[CacheResponse(Duration = 3600)] +public class CachedTechnologyServices : Service +{ + public IAutoQueryDb AutoQuery { get; set; } - public object Any(QueryTechnology request) => - AutoQuery.Execute(request, AutoQuery.CreateQuery(request, Request.GetRequestParams())); + public object Any(FindTechnologies request) => + AutoQuery.Execute(request, AutoQuery.CreateQuery(request, Request.GetRequestParams())); - public object Get(GetTechnology request) - { - if (string.IsNullOrEmpty(request.Slug)) - throw new ArgumentNullException(nameof(request.Slug)); + public object Any(QueryTechnology request) => + AutoQuery.Execute(request, AutoQuery.CreateQuery(request, Request.GetRequestParams())); + + public object Get(GetTechnology request) + { + if (string.IsNullOrEmpty(request.Slug)) + throw new ArgumentNullException(nameof(request.Slug)); - var tech = int.TryParse(request.Slug, out var id) - ? Db.SingleById(id) - : Db.Single(x => x.Slug == request.Slug.ToLower()); + var tech = int.TryParse(request.Slug, out var id) + ? Db.SingleById(id) + : Db.Single(x => x.Slug == request.Slug.ToLower()); - if (tech == null) - throw HttpError.NotFound("Tech stack not found"); + if (tech == null) + throw HttpError.NotFound("Tech stack not found"); - var techStacks = Db.Select(Db.From() - .Join() - .Join() - .Where(x => x.TechnologyId == tech.Id) - .OrderByDescending(x => x.LastModified)); + var techStacks = Db.Select(Db.From() + .Join() + .Join() + .Where(x => x.TechnologyId == tech.Id) + .OrderByDescending(x => x.LastModified)); - return new GetTechnologyResponse - { - Technology = tech, - TechnologyStacks = techStacks - }; - } - - public object Get(GetTechnologyFavoriteDetails request) + return new GetTechnologyResponse { - var tech = int.TryParse(request.Slug, out var id) - ? Db.SingleById(id) - : Db.Single(x => x.Slug == request.Slug.ToLower()); + Technology = tech, + TechnologyStacks = techStacks + }; + } - var favoriteCount = - Db.Count(x => x.TechnologyId == tech.Id); + public object Get(GetTechnologyFavoriteDetails request) + { + var tech = int.TryParse(request.Slug, out var id) + ? Db.SingleById(id) + : Db.Single(x => x.Slug == request.Slug.ToLower()); - return new GetTechnologyFavoriteDetailsResponse - { - FavoriteCount = (int)favoriteCount - }; - } + var favoriteCount = + Db.Count(x => x.TechnologyId == tech.Id); + + return new GetTechnologyFavoriteDetailsResponse + { + FavoriteCount = (int)favoriteCount + }; } -} +} \ No newline at end of file diff --git a/TechStacks.ServiceInterface/TechnologyServicesAdmin.cs b/TechStacks.ServiceInterface/TechnologyServicesAdmin.cs index 6dca56d..5b0b27e 100644 --- a/TechStacks.ServiceInterface/TechnologyServicesAdmin.cs +++ b/TechStacks.ServiceInterface/TechnologyServicesAdmin.cs @@ -12,210 +12,209 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +[Authenticate] +public class TechnologyServicesAdmin : Service { - [Authenticate] - public class TechnologyServicesAdmin : Service - { - public static ILog Log = LogManager.GetLogger(typeof(TechnologyServicesAdmin)); + public static ILog Log = LogManager.GetLogger(typeof(TechnologyServicesAdmin)); - public IAppSettings AppSettings { get; set; } + public IAppSettings AppSettings { get; set; } - public ITwitterUpdates TwitterUpdates { get; set; } + public ITwitterUpdates TwitterUpdates { get; set; } - private const int TweetUrlLength = 22; + private const int TweetUrlLength = 22; - private string PostTwitterUpdate(string msgPrefix, List techIds, int maxLength) + private string PostTwitterUpdate(string msgPrefix, List techIds, int maxLength) + { + try { - try - { - var stackNames = Db.Column(Db.From() - .Where(x => techIds.Contains(x.Id)) - .Select(x => x.Name)); + var stackNames = Db.Column(Db.From() + .Where(x => techIds.Contains(x.Id)) + .Select(x => x.Name)); - var sb = new StringBuilder(msgPrefix); - for (int i = 0; i < stackNames.Count; i++) - { - var name = stackNames[i]; - if (sb.Length + name.Length + 3 > maxLength) - break; + var sb = new StringBuilder(msgPrefix); + for (int i = 0; i < stackNames.Count; i++) + { + var name = stackNames[i]; + if (sb.Length + name.Length + 3 > maxLength) + break; - if (i > 0) - sb.Append(","); + if (i > 0) + sb.Append(","); - sb.Append(" " + name); - } + sb.Append(" " + name); + } - return TwitterUpdates.Tweet(sb.ToString()); + return TwitterUpdates.Tweet(sb.ToString()); - } - catch (Exception ex) - { - Log.Error("Could not post to Twitter: " + msgPrefix, ex); - return null; - } } + catch (Exception ex) + { + Log.Error("Could not post to Twitter: " + msgPrefix, ex); + return null; + } + } - public object Post(CreateTechnology request) + public object Post(CreateTechnology request) + { + var slug = request.Slug; + var existingTech = Db.Single(q => q.Name == request.Name || q.Slug == slug); + if (existingTech != null) + throw new ArgumentException($"'{slug}' already exists", nameof(request.Name)); + + var tech = request.ConvertTo(); + var session = SessionAs(); + tech.CreatedBy = session.UserName; + tech.Created = DateTime.UtcNow; + tech.LastModifiedBy = session.UserName; + tech.LastModified = DateTime.UtcNow; + tech.OwnerId = session.UserAuthId; + tech.LogoApproved = true; + tech.Slug = slug; + tech.LogoUrl = request.LogoUrl; + + if (string.IsNullOrEmpty(tech.LogoUrl) && Request.Files.Length > 0) { - var slug = request.Slug; - var existingTech = Db.Single(q => q.Name == request.Name || q.Slug == slug); - if (existingTech != null) - throw new ArgumentException($"'{slug}' already exists", nameof(request.Name)); - - var tech = request.ConvertTo(); - var session = SessionAs(); - tech.CreatedBy = session.UserName; - tech.Created = DateTime.UtcNow; - tech.LastModifiedBy = session.UserName; - tech.LastModified = DateTime.UtcNow; - tech.OwnerId = session.UserAuthId; - tech.LogoApproved = true; - tech.Slug = slug; - tech.LogoUrl = request.LogoUrl; - - if (string.IsNullOrEmpty(tech.LogoUrl) && Request.Files.Length > 0) - { - tech.LogoUrl = Request.Files[0].UploadToImgur(AppSettings.GetString("oauth.imgur.ClientId"), - nameof(tech.LogoUrl), minWidth:100, minHeight:50, maxWidth:2000, maxHeight:1000); - } + tech.LogoUrl = Request.Files[0].UploadToImgur(AppSettings.GetString("oauth.imgur.ClientId"), + nameof(tech.LogoUrl), minWidth:100, minHeight:50, maxWidth:2000, maxHeight:1000); + } - if (string.IsNullOrEmpty(tech.LogoUrl)) - throw new ArgumentException("Logo is Required", nameof(request.LogoUrl)); + if (string.IsNullOrEmpty(tech.LogoUrl)) + throw new ArgumentException("Logo is Required", nameof(request.LogoUrl)); - var id = Db.Insert(tech, selectIdentity: true); - var createdTechStack = Db.SingleById(id); + var id = Db.Insert(tech, selectIdentity: true); + var createdTechStack = Db.SingleById(id); - var history = createdTechStack.ConvertTo(); - history.TechnologyId = id; - history.Operation = "INSERT"; + var history = createdTechStack.ConvertTo(); + history.TechnologyId = id; + history.Operation = "INSERT"; - Db.Insert(history); + Db.Insert(history); - Db.ExecuteSql( - @"update user_activity set + Db.ExecuteSql( + @"update user_activity set technology_count = (select count(*) from technology where owner_id = @userIdStr) where id = @userId", - new { userId = session.GetUserId(), userIdStr = session.UserAuthId }); + new { userId = session.GetUserId(), userIdStr = session.UserAuthId }); - Cache.FlushAll(); + Cache.FlushAll(); - var postUpdate = AppSettings.EnableTwitterUpdates(); - if (postUpdate) - { - var url = TwitterUpdates.BaseUrl.CombineWith(new ClientTechnology { Slug = tech.Slug }.ToUrl()); - var twitterSlug = tech.Slug.Replace("-", ""); - PostTwitterUpdate( - $"Who's using #{twitterSlug}? {url}", - Db.ColumnDistinct(Db.From() - .Where(x => x.TechnologyId == tech.Id) - .Select(x => x.TechnologyStackId)).ToList(), - maxLength: 140 - (TweetUrlLength - url.Length)); - } - - return new CreateTechnologyResponse - { - Result = createdTechStack, - }; + var postUpdate = AppSettings.EnableTwitterUpdates(); + if (postUpdate) + { + var url = TwitterUpdates.BaseUrl.CombineWith(new ClientTechnology { Slug = tech.Slug }.ToUrl()); + var twitterSlug = tech.Slug.Replace("-", ""); + PostTwitterUpdate( + $"Who's using #{twitterSlug}? {url}", + Db.ColumnDistinct(Db.From() + .Where(x => x.TechnologyId == tech.Id) + .Select(x => x.TechnologyStackId)).ToList(), + maxLength: 140 - (TweetUrlLength - url.Length)); } - public object Put(UpdateTechnology request) + return new CreateTechnologyResponse { - var tech = Db.SingleById(request.Id); - if (tech == null) - throw HttpError.NotFound("Tech not found"); + Result = createdTechStack, + }; + } - var session = SessionAs(); - var authRepo = HostContext.AppHost.GetAuthRepository(Request); - using (authRepo as IDisposable) - { - if (tech.IsLocked && !(tech.OwnerId == session.UserAuthId || session.HasRole(RoleNames.Admin, authRepo))) - throw HttpError.Unauthorized( - "This Technology is locked and can only be modified by its Owner or Admins."); - } + public object Put(UpdateTechnology request) + { + var tech = Db.SingleById(request.Id); + if (tech == null) + throw HttpError.NotFound("Tech not found"); - //Only Post an Update if there was no other update today - var postUpdate = AppSettings.EnableTwitterUpdates() - && tech.LastStatusUpdate.GetValueOrDefault(DateTime.MinValue) < DateTime.UtcNow.Date; + var session = SessionAs(); + var authRepo = HostContext.AppHost.GetAuthRepository(Request); + using (authRepo as IDisposable) + { + if (tech.IsLocked && !(tech.OwnerId == session.UserAuthId || session.HasRole(RoleNames.Admin, authRepo))) + throw HttpError.Unauthorized( + "This Technology is locked and can only be modified by its Owner or Admins."); + } - tech.PopulateWith(request); - tech.LastModifiedBy = session.UserName; - tech.LastModified = DateTime.UtcNow; + //Only Post an Update if there was no other update today + var postUpdate = AppSettings.EnableTwitterUpdates() + && tech.LastStatusUpdate.GetValueOrDefault(DateTime.MinValue) < DateTime.UtcNow.Date; - if (postUpdate) - tech.LastStatusUpdate = tech.LastModified; + tech.PopulateWith(request); + tech.LastModifiedBy = session.UserName; + tech.LastModified = DateTime.UtcNow; - if (Request.Files.Length > 0) - { - tech.LogoUrl = Request.Files[0].UploadToImgur(AppSettings.GetString("oauth.imgur.ClientId"), - nameof(tech.LogoUrl), minWidth:100, minHeight:50, maxWidth:2000, maxHeight:1000); - } + if (postUpdate) + tech.LastStatusUpdate = tech.LastModified; + + if (Request.Files.Length > 0) + { + tech.LogoUrl = Request.Files[0].UploadToImgur(AppSettings.GetString("oauth.imgur.ClientId"), + nameof(tech.LogoUrl), minWidth:100, minHeight:50, maxWidth:2000, maxHeight:1000); + } + + if (string.IsNullOrEmpty(tech.LogoUrl)) + throw new ArgumentException("Logo is Required", nameof(request.LogoUrl)); - if (string.IsNullOrEmpty(tech.LogoUrl)) - throw new ArgumentException("Logo is Required", nameof(request.LogoUrl)); + Db.Save(tech); - Db.Save(tech); + var history = tech.ConvertTo(); + history.TechnologyId = tech.Id; + history.Operation = "UPDATE"; + Db.Insert(history); - var history = tech.ConvertTo(); - history.TechnologyId = tech.Id; - history.Operation = "UPDATE"; - Db.Insert(history); + Cache.FlushAll(); - Cache.FlushAll(); + var response = new UpdateTechnologyResponse + { + Result = tech + }; + + if (postUpdate) + { + var url = TwitterUpdates.BaseUrl.CombineWith(new ClientTechnology { Slug = tech.Slug }.ToUrl()); + var twitterSlug = tech.Slug.Replace("-", ""); - var response = new UpdateTechnologyResponse + response.ResponseStatus = new ResponseStatus { - Result = tech + Message = PostTwitterUpdate( + $"Who's using #{twitterSlug}? {url}", + Db.ColumnDistinct(Db.From() + .Where(x => x.TechnologyId == tech.Id) + .Select(x => x.TechnologyStackId)).ToList(), + maxLength: 140 - (TweetUrlLength - url.Length)) }; + } - if (postUpdate) - { - var url = TwitterUpdates.BaseUrl.CombineWith(new ClientTechnology { Slug = tech.Slug }.ToUrl()); - var twitterSlug = tech.Slug.Replace("-", ""); - - response.ResponseStatus = new ResponseStatus - { - Message = PostTwitterUpdate( - $"Who's using #{twitterSlug}? {url}", - Db.ColumnDistinct(Db.From() - .Where(x => x.TechnologyId == tech.Id) - .Select(x => x.TechnologyStackId)).ToList(), - maxLength: 140 - (TweetUrlLength - url.Length)) - }; - } + return response; + } - return response; - } + public object Delete(DeleteTechnology request) + { + var existingTech = Db.SingleById(request.Id); + if (existingTech == null) + throw HttpError.NotFound("Tech not found"); - public object Delete(DeleteTechnology request) + var session = SessionAs(); + var authRepo = HostContext.AppHost.GetAuthRepository(Request); + using (authRepo as IDisposable) { - var existingTech = Db.SingleById(request.Id); - if (existingTech == null) - throw HttpError.NotFound("Tech not found"); - - var session = SessionAs(); - var authRepo = HostContext.AppHost.GetAuthRepository(Request); - using (authRepo as IDisposable) - { - if (existingTech.OwnerId != session.UserAuthId && !session.HasRole(RoleNames.Admin, authRepo)) - throw HttpError.Unauthorized("Only the Owner or Admins can delete this Technology"); - } + if (existingTech.OwnerId != session.UserAuthId && !session.HasRole(RoleNames.Admin, authRepo)) + throw HttpError.Unauthorized("Only the Owner or Admins can delete this Technology"); + } - Db.DeleteById(request.Id); + Db.DeleteById(request.Id); - var history = existingTech.ConvertTo(); - history.TechnologyId = existingTech.Id; - history.LastModified = DateTime.UtcNow; - history.LastModifiedBy = session.UserName; - history.Operation = "DELETE"; - Db.Insert(history); + var history = existingTech.ConvertTo(); + history.TechnologyId = existingTech.Id; + history.LastModified = DateTime.UtcNow; + history.LastModifiedBy = session.UserName; + history.Operation = "DELETE"; + Db.Insert(history); - Cache.FlushAll(); + Cache.FlushAll(); - return new DeleteTechnologyResponse - { - Result = new Technology { Id = (long)request.Id } - }; - } + return new DeleteTechnologyResponse + { + Result = new Technology { Id = (long)request.Id } + }; } } \ No newline at end of file diff --git a/TechStacks.ServiceInterface/TechnologyStackServices.cs b/TechStacks.ServiceInterface/TechnologyStackServices.cs index 6155e8b..0a2a03c 100644 --- a/TechStacks.ServiceInterface/TechnologyStackServices.cs +++ b/TechStacks.ServiceInterface/TechnologyStackServices.cs @@ -7,181 +7,181 @@ using TechStacks.ServiceModel; using TechStacks.ServiceModel.Types; -namespace TechStacks.ServiceInterface +namespace TechStacks.ServiceInterface; + +public class TechnologyStackServices : Service { - public class TechnologyStackServices : Service + public object Get(GetTechnologyStackPreviousVersions request) { - public object Get(GetTechnologyStackPreviousVersions request) - { - if (request.Slug == null) - throw new ArgumentNullException("Slug"); - - if (!long.TryParse(request.Slug, out var id)) - { - var techStack = Db.Single(x => x.Slug == request.Slug.ToLower()); - id = techStack.Id; - } + if (request.Slug == null) + throw new ArgumentNullException("Slug"); - return new GetTechnologyStackPreviousVersionsResponse - { - Results = Db.Select(Db.From() - .Where(x => x.TechnologyStackId == id) - .OrderByDescending(x => x.LastModified)) - }; + if (!long.TryParse(request.Slug, out var id)) + { + var techStack = Db.Single(x => x.Slug == request.Slug.ToLower()); + id = techStack.Id; } - public object Any(GetPageStats request) + return new GetTechnologyStackPreviousVersionsResponse { - var id = $"/{request.Type}/{request.Slug}"; - var pageStats = Db.SingleById(id); - return new GetPageStatsResponse - { - Type = request.Type, - Slug = request.Slug, - ViewCount = pageStats?.ViewCount ?? 0, - FavCount = request.Id == null - ? pageStats?.FavCount ?? 0 - : request.Type == "tech" - ? Db.Count(x => x.TechnologyId == request.Id) - : Db.Count(x => x.TechnologyStackId == request.Id), - }; - } + Results = Db.Select(Db.From() + .Where(x => x.TechnologyStackId == id) + .OrderByDescending(x => x.LastModified)) + }; + } - public object Any(ClearCache request) + public object Any(GetPageStats request) + { + var id = $"/{request.Type}/{request.Slug}"; + var pageStats = Db.SingleById(id); + return new GetPageStatsResponse { - Cache.FlushAll(); - PostServicesBase.PeriodicUpdateTableCaches(Db); - PostServicesBase.ClearOrganizationCache(); - Cache.FlushAll(); + Type = request.Type, + Slug = request.Slug, + ViewCount = pageStats?.ViewCount ?? 0, + FavCount = request.Id == null + ? pageStats?.FavCount ?? 0 + : request.Type == "tech" + ? Db.Count(x => x.TechnologyId == request.Id) + : Db.Count(x => x.TechnologyStackId == request.Id), + }; + } - return "OK"; - } + public object Any(ClearCache request) + { + Cache.FlushAll(); + PostServicesBase.PeriodicUpdateTableCaches(Db); + PostServicesBase.ClearOrganizationCache(); + Cache.FlushAll(); + + return "OK"; + } + + public object Any(HourlyTask request) + { + if (!request.Force) + return new HourlyTaskResponse(); + + var updatedTechIds = Db.ExecuteSql("UPDATE page_stats AS p SET ref_id = t.id FROM technology AS t WHERE t.slug = p.ref_slug and p.ref_type = 'tech' AND ref_id = 0"); + var updatedStackIds = Db.ExecuteSql("UPDATE page_stats AS p SET ref_id = t.id FROM technology_stack AS t WHERE t.slug = p.ref_slug and p.ref_type = 'stack' AND ref_id = 0"); - public object Any(HourlyTask request) + var techFavs = Db.Dictionary("SELECT technology_id, count(*) FROM user_favorite_technology GROUP BY technology_id"); + foreach (var techFav in techFavs) { - if (!request.Force) - return new HourlyTaskResponse(); + Db.ExecuteSql("UPDATE page_stats SET fav_count = @favCount WHERE ref_id = @refId and ref_type = 'tech'", + new { refId = techFav.Key, favCount = techFav.Value }); + } - var updatedTechIds = Db.ExecuteSql("UPDATE page_stats AS p SET ref_id = t.id FROM technology AS t WHERE t.slug = p.ref_slug and p.ref_type = 'tech' AND ref_id = 0"); - var updatedStackIds = Db.ExecuteSql("UPDATE page_stats AS p SET ref_id = t.id FROM technology_stack AS t WHERE t.slug = p.ref_slug and p.ref_type = 'stack' AND ref_id = 0"); + var stackFavs = Db.Dictionary("SELECT technology_stack_id, count(*) FROM user_favorite_technology_stack GROUP BY technology_stack_id"); + foreach (var stackFav in stackFavs) + { + Db.ExecuteSql("UPDATE page_stats SET fav_count = @favCount WHERE ref_id = @refId and ref_type = 'stack'", + new { refId = stackFav.Key, favCount = stackFav.Value }); + } - var techFavs = Db.Dictionary("SELECT technology_id, count(*) FROM user_favorite_technology GROUP BY technology_id"); - foreach (var techFav in techFavs) - { - Db.ExecuteSql("UPDATE page_stats SET fav_count = @favCount WHERE ref_id = @refId and ref_type = 'tech'", - new { refId = techFav.Key, favCount = techFav.Value }); - } + PostServicesBase.PeriodicUpdateTableCaches(Db); - var stackFavs = Db.Dictionary("SELECT technology_stack_id, count(*) FROM user_favorite_technology_stack GROUP BY technology_stack_id"); - foreach (var stackFav in stackFavs) + Any(new ClearCache()); + + return new HourlyTaskResponse + { + Meta = new Dictionary { - Db.ExecuteSql("UPDATE page_stats SET fav_count = @favCount WHERE ref_id = @refId and ref_type = 'stack'", - new { refId = stackFav.Key, favCount = stackFav.Value }); + { "updatedTechIds", updatedTechIds.ToString() }, + { "updatedStackIds", updatedStackIds.ToString() }, + { "techFavsCount", techFavs.Count.ToString() }, + { "stackFavsCount", stackFavs.Count.ToString() }, } + }; + } +} - PostServicesBase.PeriodicUpdateTableCaches(Db); +[CacheResponse(Duration = 3600)] +public class CachedTechnologyStackServices : Service +{ + public IAutoQueryDb AutoQuery { get; set; } - Any(new ClearCache()); - - return new HourlyTaskResponse - { - Meta = new Dictionary - { - { "updatedTechIds", updatedTechIds.ToString() }, - { "updatedStackIds", updatedStackIds.ToString() }, - { "techFavsCount", techFavs.Count.ToString() }, - { "stackFavsCount", stackFavs.Count.ToString() }, - } - }; - } + //Cached AutoQuery + public async Task Any(FindTechStacks request) + { + using var db = AutoQuery.GetDb(request, base.Request); + return await AutoQuery.ExecuteAsync(request, AutoQuery.CreateQuery(request, Request, db), db); } - [CacheResponse(Duration = 3600)] - public class CachedTechnologyStackServices : Service + public async Task Any(QueryTechStacks request) { - public IAutoQueryDb AutoQuery { get; set; } + using var db = AutoQuery.GetDb(request, base.Request); + return await AutoQuery.ExecuteAsync(request, AutoQuery.CreateQuery(request, Request, db), db); + } - //Cached AutoQuery - public async Task Any(FindTechStacks request) - { - using var db = AutoQuery.GetDb(request, base.Request); - return await AutoQuery.ExecuteAsync(request, AutoQuery.CreateQuery(request, Request, db), db); - } + private const int TechStacksAppId = 1; - public async Task Any(QueryTechStacks request) - { - using var db = AutoQuery.GetDb(request, base.Request); - return await AutoQuery.ExecuteAsync(request, AutoQuery.CreateQuery(request, Request, db), db); - } + public object Any(Overview request) + { + if (request.Reload) + Cache.FlushAll(); - private const int TechStacksAppId = 1; + var topTechByCategory = GetTopTechByCategory(); - public object Any(Overview request) + var map = new Dictionary>(); + foreach (var tech in topTechByCategory) { - if (request.Reload) - Cache.FlushAll(); - - var topTechByCategory = GetTopTechByCategory(); - - var map = new Dictionary>(); - foreach (var tech in topTechByCategory) - { - List techs = null; - var key = Enum.GetName(typeof(TechnologyTier), tech.Tier); - if (key != null && !map.TryGetValue(key, out techs)) - map[key] = techs = new List(); + List techs = null; + var key = Enum.GetName(typeof(TechnologyTier), tech.Tier); + if (key != null && !map.TryGetValue(key, out techs)) + map[key] = techs = new List(); - techs?.Add(tech); - } + techs?.Add(tech); + } - foreach (var tier in map.Keys) - { - var list = map[tier]; - list.Sort((x, y) => y.StacksCount - x.StacksCount); - if (list.Count > 5) - list.RemoveRange(5, list.Count - 5); - } + foreach (var tier in map.Keys) + { + var list = map[tier]; + list.Sort((x, y) => y.StacksCount - x.StacksCount); + if (list.Count > 5) + list.RemoveRange(5, list.Count - 5); + } - var allOrgs = Db.Select(Db.From() - .Where(g => g.Deleted == null)); + var allOrgs = Db.Select(Db.From() + .Where(g => g.Deleted == null)); - var allOrgsMap = allOrgs.ToDictionary(x => x.Id); + var allOrgsMap = allOrgs.ToDictionary(x => x.Id); - var orgLabels = Db.Select(); - foreach (var orgLabel in orgLabels) + var orgLabels = Db.Select(); + foreach (var orgLabel in orgLabels) + { + if (allOrgsMap.TryGetValue(orgLabel.OrganizationId, out var org)) { - if (allOrgsMap.TryGetValue(orgLabel.OrganizationId, out var org)) - { - (org.Labels ?? (org.Labels = new List())) - .Add(orgLabel.ConvertTo()); - } + (org.Labels ?? (org.Labels = new List())) + .Add(orgLabel.ConvertTo()); } + } - var orgCategories = Db.Select(x => x.Deleted == null); - foreach (var category in orgCategories) + var orgCategories = Db.Select(x => x.Deleted == null); + foreach (var category in orgCategories) + { + if (allOrgsMap.TryGetValue(category.OrganizationId, out var org)) { - if (allOrgsMap.TryGetValue(category.OrganizationId, out var org)) - { - (org.Categories ?? (org.Categories = new List())) - .Add(category.ConvertTo()); - } + (org.Categories ?? (org.Categories = new List())) + .Add(category.ConvertTo()); } + } - foreach (var entry in allOrgsMap) - { - var org = entry.Value; - org.MembersCount = PostServicesBase.GetOrganizationMembersCount(org.Id); - org.Categories?.Sort((x,y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal)); - } + foreach (var entry in allOrgsMap) + { + var org = entry.Value; + org.MembersCount = PostServicesBase.GetOrganizationMembersCount(org.Id); + org.Categories?.Sort((x,y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal)); + } - var response = new OverviewResponse - { - Created = DateTime.UtcNow, + var response = new OverviewResponse + { + Created = DateTime.UtcNow, - LatestTechStacks = Db.GetTechstackDetails(Db.From().OrderByDescending(x => x.LastModified).Limit(20)), + LatestTechStacks = Db.GetTechstackDetails(Db.From().OrderByDescending(x => x.LastModified).Limit(20)), - TopUsers = Db.Select( - @"select u.user_name as UserName, u.default_profile_url as AvatarUrl, COUNT(*) as StacksCount + TopUsers = Db.Select( + @"select u.user_name as UserName, u.default_profile_url as AvatarUrl, COUNT(*) as StacksCount from technology_stack ts left join user_favorite_technology_stack uf on (ts.id = uf.technology_stack_id) @@ -192,174 +192,173 @@ having count(*) > 0 order by StacksCount desc limit 20"), - TopTechnologies = topTechByCategory - .OrderByDescending(x => x.StacksCount) - .Take(50) - .ToList(), + TopTechnologies = topTechByCategory + .OrderByDescending(x => x.StacksCount) + .Take(50) + .ToList(), - PopularTechStacks = Db.Select( - Db.From() - .Join((s, p) => s.Id == p.RefId && p.RefType == "stack") - .OrderByDescending(p => p.ViewCount) - .Limit(12)), + PopularTechStacks = Db.Select( + Db.From() + .Join((s, p) => s.Id == p.RefId && p.RefType == "stack") + .OrderByDescending(p => p.ViewCount) + .Limit(12)), - AllOrganizations = allOrgs, + AllOrganizations = allOrgs, - TopTechnologiesByTier = map, - }; + TopTechnologiesByTier = map, + }; - //Lighten payload - response.LatestTechStacks.Each(x => { - x.Details = x.DetailsHtml = null; - x.TechnologyChoices.Each(y => { - y.Description = null; - }); + //Lighten payload + response.LatestTechStacks.Each(x => { + x.Details = x.DetailsHtml = null; + x.TechnologyChoices.Each(y => { + y.Description = null; }); + }); - //Put TechStacks entry first to provide a first good experience - var techStacksApp = response.LatestTechStacks.FirstOrDefault(x => x.Id == TechStacksAppId); - if (techStacksApp != null) - { - response.LatestTechStacks.RemoveAll(x => x.Id == TechStacksAppId); - response.LatestTechStacks.Insert(0, techStacksApp); - } - - return response; + //Put TechStacks entry first to provide a first good experience + var techStacksApp = response.LatestTechStacks.FirstOrDefault(x => x.Id == TechStacksAppId); + if (techStacksApp != null) + { + response.LatestTechStacks.RemoveAll(x => x.Id == TechStacksAppId); + response.LatestTechStacks.Insert(0, techStacksApp); } - private List GetTopTechByCategory(int minCount = 3) - { - var topTechByCategory = Db.Select( - @"select t.tier, t.slug as Slug, t.name, t.logo_url, COUNT(*) as StacksCount + return response; + } + + private List GetTopTechByCategory(int minCount = 3) + { + var topTechByCategory = Db.Select( + @"select t.tier, t.slug as Slug, t.name, t.logo_url, COUNT(*) as StacksCount from technology_choice tc inner join technology t on (tc.technology_id = t.id) group by t.tier, t.slug, t.name, t.logo_url having COUNT(*) >= {0} order by 4 desc".Fmt(minCount)); - return topTechByCategory; - } + return topTechByCategory; + } - public object Any(AppOverview request) - { - if (request.Reload) - Cache.FlushAll(); + public object Any(AppOverview request) + { + if (request.Reload) + Cache.FlushAll(); - var response = new AppOverviewResponse - { - Created = DateTime.UtcNow, - AllTiers = GetAllTiers(), - TopTechnologies = GetTopTechByCategory(minCount: 1) - .OrderByDescending(x => x.StacksCount) - .Take(100) - .ToList(), - }; + var response = new AppOverviewResponse + { + Created = DateTime.UtcNow, + AllTiers = GetAllTiers(), + TopTechnologies = GetTopTechByCategory(minCount: 1) + .OrderByDescending(x => x.StacksCount) + .Take(100) + .ToList(), + }; - response.AllTiers.Insert(0, new Option { Title = "[ Top 100 Technologies ]" }); + response.AllTiers.Insert(0, new Option { Title = "[ Top 100 Technologies ]" }); - return response; - } + return response; + } - public object Get(GetAllTechnologyStacks request) + public object Get(GetAllTechnologyStacks request) + { + return new GetAllTechnologyStacksResponse { - return new GetAllTechnologyStacksResponse - { - Results = Db.Select(Db.From().OrderByDescending(x => x.LastModified).Take(100)), - Total = Db.Count(), - }; - } + Results = Db.Select(Db.From().OrderByDescending(x => x.LastModified).Take(100)), + Total = Db.Count(), + }; + } - public object Get(GetTechnologyStack request) - { - if (string.IsNullOrEmpty(request.Slug)) - throw new ArgumentNullException(nameof(request.Slug)); + public object Get(GetTechnologyStack request) + { + if (string.IsNullOrEmpty(request.Slug)) + throw new ArgumentNullException(nameof(request.Slug)); - var techStack = int.TryParse(request.Slug, out var id) - ? Db.SingleById(id) - : Db.Single(x => x.Slug == request.Slug.ToLower()); + var techStack = int.TryParse(request.Slug, out var id) + ? Db.SingleById(id) + : Db.Single(x => x.Slug == request.Slug.ToLower()); - if (techStack == null) - throw HttpError.NotFound("Tech stack not found"); + if (techStack == null) + throw HttpError.NotFound("Tech stack not found"); - var techChoices = Db.LoadSelect(Db.From() - .Join() - .Join() - .Where(x => x.TechnologyStackId == techStack.Id)); + var techChoices = Db.LoadSelect(Db.From() + .Join() + .Join() + .Where(x => x.TechnologyStackId == techStack.Id)); - var result = techStack.ConvertTo(); - if (!string.IsNullOrEmpty(techStack.Details) && string.IsNullOrEmpty(techStack.DetailsHtml)) - { - result.DetailsHtml = MarkdownConfig.Transform(techStack.Details); - } - - result.TechnologyChoices = techChoices.Map(x => x.ToTechnologyInStack()); - - var response = new GetTechnologyStackResponse - { - Created = DateTime.UtcNow, - Result = result - }; - return response; + var result = techStack.ConvertTo(); + if (!string.IsNullOrEmpty(techStack.Details) && string.IsNullOrEmpty(techStack.DetailsHtml)) + { + result.DetailsHtml = MarkdownConfig.Transform(techStack.Details); } - public object Get(GetTechnologyStackFavoriteDetails request) + result.TechnologyChoices = techChoices.Map(x => x.ToTechnologyInStack()); + + var response = new GetTechnologyStackResponse { - var tech = int.TryParse(request.Slug, out var id) - ? Db.SingleById(id) - : Db.Single(x => x.Slug == request.Slug.ToLower()); + Created = DateTime.UtcNow, + Result = result + }; + return response; + } - if (tech == null) - throw HttpError.NotFound("TechStack not found"); + public object Get(GetTechnologyStackFavoriteDetails request) + { + var tech = int.TryParse(request.Slug, out var id) + ? Db.SingleById(id) + : Db.Single(x => x.Slug == request.Slug.ToLower()); - var favoriteCount = Db.Count(x => x.TechnologyStackId == tech.Id); + if (tech == null) + throw HttpError.NotFound("TechStack not found"); - return new GetTechnologyStackFavoriteDetailsResponse - { - FavoriteCount = (int)favoriteCount - }; - } + var favoriteCount = Db.Count(x => x.TechnologyStackId == tech.Id); - public object Any(GetConfig request) + return new GetTechnologyStackFavoriteDetailsResponse { - var allTiers = GetAllTiers(); + FavoriteCount = (int)favoriteCount + }; + } - return new GetConfigResponse - { - AllTiers = allTiers, - AllPostTypes = GetAllPostTypes(), - AllFlagTypes = GetAllFlagType(), - }; - } + public object Any(GetConfig request) + { + var allTiers = GetAllTiers(); - public static List