Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
Layoric committed Apr 6, 2024
2 parents d572b8c + a7f8563 commit f183ccb
Show file tree
Hide file tree
Showing 51 changed files with 34,965 additions and 3,152 deletions.
16 changes: 16 additions & 0 deletions MyApp.ServiceInterface/App/AnswerAddedToPostCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Data;
using MyApp.Data;
using MyApp.ServiceModel;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class AnswerAddedToPostCommand(IDbConnection db) : IExecuteCommandAsync<AnswerAddedToPost>
{
public async Task ExecuteAsync(AnswerAddedToPost request)
{
await db.UpdateAddAsync(() => new Post {
AnswerCount = 1,
}, x => x.Id == request.Id);
}
}
41 changes: 41 additions & 0 deletions MyApp.ServiceInterface/App/CompletePostJobsCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Data;
using MyApp.Data;
using MyApp.ServiceModel;
using ServiceStack.Messaging;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class CompletePostJobsCommand(IDbConnection Db, ModelWorkerQueue modelWorkers, IMessageProducer mqClient) : IExecuteCommandAsync<CompletePostJobs>
{
public async Task ExecuteAsync(CompletePostJobs request)
{
var jobIds = request.Ids;
await Db.UpdateOnlyAsync(() => new PostJob {
CompletedDate = DateTime.UtcNow,
},
x => jobIds.Contains(x.Id));
var postJobs = await Db.SelectAsync(Db.From<PostJob>()
.Where(x => jobIds.Contains(x.Id)));

foreach (var postJob in postJobs)
{
// If there's no outstanding model answer jobs for this post, add a rank job
if (!await Db.ExistsAsync(Db.From<PostJob>()
.Where(x => x.PostId == postJob.PostId && x.CompletedDate == null)))
{
var rankJob = new PostJob
{
PostId = postJob.PostId,
Model = "rank",
Title = postJob.Title,
CreatedDate = DateTime.UtcNow,
CreatedBy = nameof(DbWrites),
};
await Db.InsertAsync(rankJob);
modelWorkers.Enqueue(rankJob);
mqClient.Publish(new SearchTasks { AddPostToIndex = postJob.PostId });
}
}
}
}
104 changes: 104 additions & 0 deletions MyApp.ServiceInterface/App/CreateAnswerCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System.Data;
using MyApp.Data;
using MyApp.ServiceModel;
using ServiceStack;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class CreateAnswerCommand(AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<Post>
{
public async Task ExecuteAsync(Post answer)
{
if (answer.ParentId == null)
throw new ArgumentNullException(nameof(answer.ParentId));
if (answer.CreatedBy == null)
throw new ArgumentNullException(nameof(answer.CreatedBy));

var postId = answer.ParentId!.Value;
var refId = $"{postId}-{answer.CreatedBy}";
if (!await db.ExistsAsync(db.From<StatTotals>().Where(x => x.Id == refId)))
{
await db.InsertAsync(new StatTotals
{
Id = refId,
PostId = postId,
ViewCount = 0,
FavoriteCount = 0,
UpVotes = 0,
DownVotes = 0,
StartingUpVotes = 0,
CreatedBy = answer.CreatedBy,
});
}

var post = await db.SingleByIdAsync<Post>(postId);
if (post?.CreatedBy != null)
{
// Notify Post Author of new Answer
if (post.CreatedBy != answer.CreatedBy && appConfig.IsHuman(post.CreatedBy))
{
await db.InsertAsync(new Notification
{
UserName = post.CreatedBy,
Type = NotificationType.NewAnswer,
RefId = refId,
PostId = postId,
CreatedDate = answer.CreationDate,
Summary = answer.Summary.SubstringWithEllipsis(0, 100),
RefUserName = answer.CreatedBy,
});
appConfig.IncrUnreadNotificationsFor(post.CreatedBy);
}

// Notify any User Mentions in Answer
if (!string.IsNullOrEmpty(answer.Body))
{
var cleanBody = answer.Body.StripHtml().Trim();
var userNameMentions = cleanBody.FindUserNameMentions()
.Where(x => x != post.CreatedBy && x != answer.CreatedBy && appConfig.IsHuman(x)).ToList();
if (userNameMentions.Count > 0)
{
var existingUsers = await db.SelectAsync(db.From<ApplicationUser>()
.Where(x => userNameMentions.Contains(x.UserName!)));

foreach (var existingUser in existingUsers)
{
var firstMentionPos = cleanBody.IndexOf(existingUser.UserName!, StringComparison.Ordinal);
if (firstMentionPos < 0) continue;

var startPos = Math.Max(0, firstMentionPos - 50);
if (appConfig.IsHuman(existingUser.UserName))
{
await db.InsertAsync(new Notification
{
UserName = existingUser.UserName!,
Type = NotificationType.AnswerMention,
RefId = $"{postId}",
PostId = postId,
CreatedDate = answer.CreationDate,
Summary = cleanBody.SubstringWithEllipsis(startPos, 100),
RefUserName = answer.CreatedBy,
});
appConfig.IncrUnreadNotificationsFor(existingUser.UserName!);
}
}
}
}
}

if (appConfig.IsHuman(answer.CreatedBy))
{
await db.InsertAsync(new Achievement
{
UserName = answer.CreatedBy,
Type = AchievementType.NewAnswer,
RefId = refId,
PostId = postId,
Score = 1,
CreatedDate = DateTime.UtcNow,
});
appConfig.IncrUnreadAchievementsFor(answer.CreatedBy);
}
}
}
15 changes: 15 additions & 0 deletions MyApp.ServiceInterface/App/CreateNotificationCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Data;
using MyApp.Data;
using MyApp.ServiceModel;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class CreateNotificationCommand(AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<Notification>
{
public async Task ExecuteAsync(Notification request)
{
await db.InsertAsync(request);
appConfig.IncrUnreadNotificationsFor(request.UserName);
}
}
94 changes: 94 additions & 0 deletions MyApp.ServiceInterface/App/CreatePostCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Data;
using Microsoft.Extensions.Logging;
using MyApp.Data;
using MyApp.ServiceModel;
using ServiceStack;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class CreatePostCommand(ILogger log, AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<Post>
{
public async Task ExecuteAsync(Post post)
{
var body = post.Body;
post.Body = null;
post.Id = (int)await db.InsertAsync(post, selectIdentity: true);
var createdBy = post.CreatedBy;
if (createdBy != null && post.PostTypeId == 1)
{
await appConfig.ResetUserQuestionsAsync(db, createdBy);
}

try
{
await db.InsertAsync(new StatTotals
{
Id = $"{post.Id}",
PostId = post.Id,
UpVotes = 0,
DownVotes = 0,
StartingUpVotes = 0,
CreatedBy = post.CreatedBy,
});
}
catch (Exception e)
{
log.LogWarning("Couldn't insert StatTotals for Post {PostId}: '{Message}', updating instead...", post.Id,
e.Message);
await db.UpdateOnlyAsync(() => new StatTotals
{
PostId = post.Id,
CreatedBy = post.CreatedBy,
}, x => x.Id == $"{post.Id}");
}

if (!string.IsNullOrEmpty(body))
{
var cleanBody = body.StripHtml().Trim();
var userNameMentions = cleanBody.FindUserNameMentions()
.Where(x => x != createdBy && appConfig.IsHuman(x)).ToList();
if (userNameMentions.Count > 0)
{
var existingUsers = await db.SelectAsync(db.From<ApplicationUser>()
.Where(x => userNameMentions.Contains(x.UserName!)));

foreach (var existingUser in existingUsers)
{
var firstMentionPos = cleanBody.IndexOf(existingUser.UserName!, StringComparison.Ordinal);
if (firstMentionPos < 0) continue;

var startPos = Math.Max(0, firstMentionPos - 50);
if (appConfig.IsHuman(existingUser.UserName))
{
await db.InsertAsync(new Notification
{
UserName = existingUser.UserName!,
Type = NotificationType.QuestionMention,
RefId = $"{post.Id}",
PostId = post.Id,
CreatedDate = post.CreationDate,
Summary = cleanBody.SubstringWithEllipsis(startPos, 100),
RefUserName = createdBy,
});
appConfig.IncrUnreadNotificationsFor(existingUser.UserName!);
}
}
}
}

if (appConfig.IsHuman(post.CreatedBy))
{
await db.InsertAsync(new Achievement
{
UserName = post.CreatedBy!,
Type = AchievementType.NewQuestion,
RefId = $"{post.Id}",
PostId = post.Id,
Score = 1,
CreatedDate = DateTime.UtcNow,
});
appConfig.IncrUnreadAchievementsFor(post.CreatedBy!);
}
}
}
14 changes: 14 additions & 0 deletions MyApp.ServiceInterface/App/CreatePostJobsCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Data;
using MyApp.Data;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class CreatePostJobsCommand(IDbConnection db, ModelWorkerQueue modelWorkers) : IExecuteCommandAsync<CreatePostJobs>
{
public async Task ExecuteAsync(CreatePostJobs request)
{
await db.SaveAllAsync(request.PostJobs);
request.PostJobs.ForEach(modelWorkers.Enqueue);
}
}
55 changes: 55 additions & 0 deletions MyApp.ServiceInterface/App/CreatePostVotesCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Data;
using MyApp.Data;
using MyApp.ServiceModel;
using ServiceStack.Messaging;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class CreatePostVotesCommand(AppConfig appConfig, IDbConnection db, IMessageProducer mqClient) : IExecuteCommandAsync<Vote>
{
public async Task ExecuteAsync(Vote vote)
{
if (string.IsNullOrEmpty(vote.RefId))
throw new ArgumentNullException(nameof(vote.RefId));
if (string.IsNullOrEmpty(vote.UserName))
throw new ArgumentNullException(nameof(vote.UserName));

var isAnswer = vote.RefId.IndexOf('-') >= 0;
var voteUp = isAnswer ? AchievementType.AnswerUpVote : AchievementType.QuestionUpVote;
var voteDown = isAnswer ? AchievementType.AnswerDownVote : AchievementType.QuestionDownVote;

var rowsDeleted = await db.DeleteAsync<Vote>(new { vote.RefId, vote.UserName });
if (rowsDeleted > 0 && vote.RefUserName != null)
{
// If they rescinded their previous vote, also remove the Ref User's previous achievement for that Q or A
await db.ExecuteNonQueryAsync(
"DELETE FROM Achievement WHERE UserName = @TargetUser AND RefUserName = @VoterUserName AND RefId = @RefId AND Type IN (@voteUp,@voteDown)",
new { TargetUser = vote.RefUserName, VoterUserName = vote.UserName , vote.RefId, voteUp, voteDown });
}

if (vote.Score != 0)
{
await db.InsertAsync(vote);

if (appConfig.IsHuman(vote.RefUserName))
{
await db.InsertAsync(new Achievement
{
UserName = vote.RefUserName!, // User who's Q or A was voted on
RefUserName = vote.UserName, // User who voted
PostId = vote.PostId,
RefId = vote.RefId,
Type = vote.Score > 0 ? voteUp : voteDown,
Score = vote.Score > 0 ? 10 : -1, // 10 points for UpVote, -1 point for DownVote
CreatedDate = DateTime.UtcNow,
});
appConfig.IncrUnreadAchievementsFor(vote.RefUserName!);
}
}

mqClient.Publish(new RenderComponent {
RegenerateMeta = new() { ForPost = vote.PostId },
});
}
}
20 changes: 20 additions & 0 deletions MyApp.ServiceInterface/App/DeleteCommentCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Data;
using MyApp.Data;
using MyApp.ServiceModel;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class DeleteCommentCommand(AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<DeleteComment>
{
public async Task ExecuteAsync(DeleteComment request)
{
var refId = $"{request.Id}-{request.Created}";
var rowsAffected = await db.DeleteAsync(db.From<Notification>()
.Where(x => x.RefId == refId && x.RefUserName == request.CreatedBy));
if (rowsAffected > 0)
{
appConfig.ResetUsersUnreadNotifications(db);
}
}
}
20 changes: 20 additions & 0 deletions MyApp.ServiceInterface/App/DeletePostCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Data;
using MyApp.Data;
using MyApp.ServiceModel;
using ServiceStack.OrmLite;

namespace MyApp.ServiceInterface.App;

public class DeletePostCommand(AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<DeletePost>
{
public async Task ExecuteAsync(DeletePost request)
{
foreach (var postId in request.Ids)
{
await db.DeleteAsync<PostJob>(x => x.PostId == postId);
await db.DeleteAsync<Vote>(x => x.PostId == postId);
await db.DeleteByIdAsync<Post>(postId);
appConfig.ResetInitialPostId(db);
}
}
}
Loading

0 comments on commit f183ccb

Please sign in to comment.