Skip to content

Commit f183ccb

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents d572b8c + a7f8563 commit f183ccb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+34965
-3152
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Data;
2+
using MyApp.Data;
3+
using MyApp.ServiceModel;
4+
using ServiceStack.OrmLite;
5+
6+
namespace MyApp.ServiceInterface.App;
7+
8+
public class AnswerAddedToPostCommand(IDbConnection db) : IExecuteCommandAsync<AnswerAddedToPost>
9+
{
10+
public async Task ExecuteAsync(AnswerAddedToPost request)
11+
{
12+
await db.UpdateAddAsync(() => new Post {
13+
AnswerCount = 1,
14+
}, x => x.Id == request.Id);
15+
}
16+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Data;
2+
using MyApp.Data;
3+
using MyApp.ServiceModel;
4+
using ServiceStack.Messaging;
5+
using ServiceStack.OrmLite;
6+
7+
namespace MyApp.ServiceInterface.App;
8+
9+
public class CompletePostJobsCommand(IDbConnection Db, ModelWorkerQueue modelWorkers, IMessageProducer mqClient) : IExecuteCommandAsync<CompletePostJobs>
10+
{
11+
public async Task ExecuteAsync(CompletePostJobs request)
12+
{
13+
var jobIds = request.Ids;
14+
await Db.UpdateOnlyAsync(() => new PostJob {
15+
CompletedDate = DateTime.UtcNow,
16+
},
17+
x => jobIds.Contains(x.Id));
18+
var postJobs = await Db.SelectAsync(Db.From<PostJob>()
19+
.Where(x => jobIds.Contains(x.Id)));
20+
21+
foreach (var postJob in postJobs)
22+
{
23+
// If there's no outstanding model answer jobs for this post, add a rank job
24+
if (!await Db.ExistsAsync(Db.From<PostJob>()
25+
.Where(x => x.PostId == postJob.PostId && x.CompletedDate == null)))
26+
{
27+
var rankJob = new PostJob
28+
{
29+
PostId = postJob.PostId,
30+
Model = "rank",
31+
Title = postJob.Title,
32+
CreatedDate = DateTime.UtcNow,
33+
CreatedBy = nameof(DbWrites),
34+
};
35+
await Db.InsertAsync(rankJob);
36+
modelWorkers.Enqueue(rankJob);
37+
mqClient.Publish(new SearchTasks { AddPostToIndex = postJob.PostId });
38+
}
39+
}
40+
}
41+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System.Data;
2+
using MyApp.Data;
3+
using MyApp.ServiceModel;
4+
using ServiceStack;
5+
using ServiceStack.OrmLite;
6+
7+
namespace MyApp.ServiceInterface.App;
8+
9+
public class CreateAnswerCommand(AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<Post>
10+
{
11+
public async Task ExecuteAsync(Post answer)
12+
{
13+
if (answer.ParentId == null)
14+
throw new ArgumentNullException(nameof(answer.ParentId));
15+
if (answer.CreatedBy == null)
16+
throw new ArgumentNullException(nameof(answer.CreatedBy));
17+
18+
var postId = answer.ParentId!.Value;
19+
var refId = $"{postId}-{answer.CreatedBy}";
20+
if (!await db.ExistsAsync(db.From<StatTotals>().Where(x => x.Id == refId)))
21+
{
22+
await db.InsertAsync(new StatTotals
23+
{
24+
Id = refId,
25+
PostId = postId,
26+
ViewCount = 0,
27+
FavoriteCount = 0,
28+
UpVotes = 0,
29+
DownVotes = 0,
30+
StartingUpVotes = 0,
31+
CreatedBy = answer.CreatedBy,
32+
});
33+
}
34+
35+
var post = await db.SingleByIdAsync<Post>(postId);
36+
if (post?.CreatedBy != null)
37+
{
38+
// Notify Post Author of new Answer
39+
if (post.CreatedBy != answer.CreatedBy && appConfig.IsHuman(post.CreatedBy))
40+
{
41+
await db.InsertAsync(new Notification
42+
{
43+
UserName = post.CreatedBy,
44+
Type = NotificationType.NewAnswer,
45+
RefId = refId,
46+
PostId = postId,
47+
CreatedDate = answer.CreationDate,
48+
Summary = answer.Summary.SubstringWithEllipsis(0, 100),
49+
RefUserName = answer.CreatedBy,
50+
});
51+
appConfig.IncrUnreadNotificationsFor(post.CreatedBy);
52+
}
53+
54+
// Notify any User Mentions in Answer
55+
if (!string.IsNullOrEmpty(answer.Body))
56+
{
57+
var cleanBody = answer.Body.StripHtml().Trim();
58+
var userNameMentions = cleanBody.FindUserNameMentions()
59+
.Where(x => x != post.CreatedBy && x != answer.CreatedBy && appConfig.IsHuman(x)).ToList();
60+
if (userNameMentions.Count > 0)
61+
{
62+
var existingUsers = await db.SelectAsync(db.From<ApplicationUser>()
63+
.Where(x => userNameMentions.Contains(x.UserName!)));
64+
65+
foreach (var existingUser in existingUsers)
66+
{
67+
var firstMentionPos = cleanBody.IndexOf(existingUser.UserName!, StringComparison.Ordinal);
68+
if (firstMentionPos < 0) continue;
69+
70+
var startPos = Math.Max(0, firstMentionPos - 50);
71+
if (appConfig.IsHuman(existingUser.UserName))
72+
{
73+
await db.InsertAsync(new Notification
74+
{
75+
UserName = existingUser.UserName!,
76+
Type = NotificationType.AnswerMention,
77+
RefId = $"{postId}",
78+
PostId = postId,
79+
CreatedDate = answer.CreationDate,
80+
Summary = cleanBody.SubstringWithEllipsis(startPos, 100),
81+
RefUserName = answer.CreatedBy,
82+
});
83+
appConfig.IncrUnreadNotificationsFor(existingUser.UserName!);
84+
}
85+
}
86+
}
87+
}
88+
}
89+
90+
if (appConfig.IsHuman(answer.CreatedBy))
91+
{
92+
await db.InsertAsync(new Achievement
93+
{
94+
UserName = answer.CreatedBy,
95+
Type = AchievementType.NewAnswer,
96+
RefId = refId,
97+
PostId = postId,
98+
Score = 1,
99+
CreatedDate = DateTime.UtcNow,
100+
});
101+
appConfig.IncrUnreadAchievementsFor(answer.CreatedBy);
102+
}
103+
}
104+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Data;
2+
using MyApp.Data;
3+
using MyApp.ServiceModel;
4+
using ServiceStack.OrmLite;
5+
6+
namespace MyApp.ServiceInterface.App;
7+
8+
public class CreateNotificationCommand(AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<Notification>
9+
{
10+
public async Task ExecuteAsync(Notification request)
11+
{
12+
await db.InsertAsync(request);
13+
appConfig.IncrUnreadNotificationsFor(request.UserName);
14+
}
15+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System.Data;
2+
using Microsoft.Extensions.Logging;
3+
using MyApp.Data;
4+
using MyApp.ServiceModel;
5+
using ServiceStack;
6+
using ServiceStack.OrmLite;
7+
8+
namespace MyApp.ServiceInterface.App;
9+
10+
public class CreatePostCommand(ILogger log, AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<Post>
11+
{
12+
public async Task ExecuteAsync(Post post)
13+
{
14+
var body = post.Body;
15+
post.Body = null;
16+
post.Id = (int)await db.InsertAsync(post, selectIdentity: true);
17+
var createdBy = post.CreatedBy;
18+
if (createdBy != null && post.PostTypeId == 1)
19+
{
20+
await appConfig.ResetUserQuestionsAsync(db, createdBy);
21+
}
22+
23+
try
24+
{
25+
await db.InsertAsync(new StatTotals
26+
{
27+
Id = $"{post.Id}",
28+
PostId = post.Id,
29+
UpVotes = 0,
30+
DownVotes = 0,
31+
StartingUpVotes = 0,
32+
CreatedBy = post.CreatedBy,
33+
});
34+
}
35+
catch (Exception e)
36+
{
37+
log.LogWarning("Couldn't insert StatTotals for Post {PostId}: '{Message}', updating instead...", post.Id,
38+
e.Message);
39+
await db.UpdateOnlyAsync(() => new StatTotals
40+
{
41+
PostId = post.Id,
42+
CreatedBy = post.CreatedBy,
43+
}, x => x.Id == $"{post.Id}");
44+
}
45+
46+
if (!string.IsNullOrEmpty(body))
47+
{
48+
var cleanBody = body.StripHtml().Trim();
49+
var userNameMentions = cleanBody.FindUserNameMentions()
50+
.Where(x => x != createdBy && appConfig.IsHuman(x)).ToList();
51+
if (userNameMentions.Count > 0)
52+
{
53+
var existingUsers = await db.SelectAsync(db.From<ApplicationUser>()
54+
.Where(x => userNameMentions.Contains(x.UserName!)));
55+
56+
foreach (var existingUser in existingUsers)
57+
{
58+
var firstMentionPos = cleanBody.IndexOf(existingUser.UserName!, StringComparison.Ordinal);
59+
if (firstMentionPos < 0) continue;
60+
61+
var startPos = Math.Max(0, firstMentionPos - 50);
62+
if (appConfig.IsHuman(existingUser.UserName))
63+
{
64+
await db.InsertAsync(new Notification
65+
{
66+
UserName = existingUser.UserName!,
67+
Type = NotificationType.QuestionMention,
68+
RefId = $"{post.Id}",
69+
PostId = post.Id,
70+
CreatedDate = post.CreationDate,
71+
Summary = cleanBody.SubstringWithEllipsis(startPos, 100),
72+
RefUserName = createdBy,
73+
});
74+
appConfig.IncrUnreadNotificationsFor(existingUser.UserName!);
75+
}
76+
}
77+
}
78+
}
79+
80+
if (appConfig.IsHuman(post.CreatedBy))
81+
{
82+
await db.InsertAsync(new Achievement
83+
{
84+
UserName = post.CreatedBy!,
85+
Type = AchievementType.NewQuestion,
86+
RefId = $"{post.Id}",
87+
PostId = post.Id,
88+
Score = 1,
89+
CreatedDate = DateTime.UtcNow,
90+
});
91+
appConfig.IncrUnreadAchievementsFor(post.CreatedBy!);
92+
}
93+
}
94+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Data;
2+
using MyApp.Data;
3+
using ServiceStack.OrmLite;
4+
5+
namespace MyApp.ServiceInterface.App;
6+
7+
public class CreatePostJobsCommand(IDbConnection db, ModelWorkerQueue modelWorkers) : IExecuteCommandAsync<CreatePostJobs>
8+
{
9+
public async Task ExecuteAsync(CreatePostJobs request)
10+
{
11+
await db.SaveAllAsync(request.PostJobs);
12+
request.PostJobs.ForEach(modelWorkers.Enqueue);
13+
}
14+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Data;
2+
using MyApp.Data;
3+
using MyApp.ServiceModel;
4+
using ServiceStack.Messaging;
5+
using ServiceStack.OrmLite;
6+
7+
namespace MyApp.ServiceInterface.App;
8+
9+
public class CreatePostVotesCommand(AppConfig appConfig, IDbConnection db, IMessageProducer mqClient) : IExecuteCommandAsync<Vote>
10+
{
11+
public async Task ExecuteAsync(Vote vote)
12+
{
13+
if (string.IsNullOrEmpty(vote.RefId))
14+
throw new ArgumentNullException(nameof(vote.RefId));
15+
if (string.IsNullOrEmpty(vote.UserName))
16+
throw new ArgumentNullException(nameof(vote.UserName));
17+
18+
var isAnswer = vote.RefId.IndexOf('-') >= 0;
19+
var voteUp = isAnswer ? AchievementType.AnswerUpVote : AchievementType.QuestionUpVote;
20+
var voteDown = isAnswer ? AchievementType.AnswerDownVote : AchievementType.QuestionDownVote;
21+
22+
var rowsDeleted = await db.DeleteAsync<Vote>(new { vote.RefId, vote.UserName });
23+
if (rowsDeleted > 0 && vote.RefUserName != null)
24+
{
25+
// If they rescinded their previous vote, also remove the Ref User's previous achievement for that Q or A
26+
await db.ExecuteNonQueryAsync(
27+
"DELETE FROM Achievement WHERE UserName = @TargetUser AND RefUserName = @VoterUserName AND RefId = @RefId AND Type IN (@voteUp,@voteDown)",
28+
new { TargetUser = vote.RefUserName, VoterUserName = vote.UserName , vote.RefId, voteUp, voteDown });
29+
}
30+
31+
if (vote.Score != 0)
32+
{
33+
await db.InsertAsync(vote);
34+
35+
if (appConfig.IsHuman(vote.RefUserName))
36+
{
37+
await db.InsertAsync(new Achievement
38+
{
39+
UserName = vote.RefUserName!, // User who's Q or A was voted on
40+
RefUserName = vote.UserName, // User who voted
41+
PostId = vote.PostId,
42+
RefId = vote.RefId,
43+
Type = vote.Score > 0 ? voteUp : voteDown,
44+
Score = vote.Score > 0 ? 10 : -1, // 10 points for UpVote, -1 point for DownVote
45+
CreatedDate = DateTime.UtcNow,
46+
});
47+
appConfig.IncrUnreadAchievementsFor(vote.RefUserName!);
48+
}
49+
}
50+
51+
mqClient.Publish(new RenderComponent {
52+
RegenerateMeta = new() { ForPost = vote.PostId },
53+
});
54+
}
55+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Data;
2+
using MyApp.Data;
3+
using MyApp.ServiceModel;
4+
using ServiceStack.OrmLite;
5+
6+
namespace MyApp.ServiceInterface.App;
7+
8+
public class DeleteCommentCommand(AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<DeleteComment>
9+
{
10+
public async Task ExecuteAsync(DeleteComment request)
11+
{
12+
var refId = $"{request.Id}-{request.Created}";
13+
var rowsAffected = await db.DeleteAsync(db.From<Notification>()
14+
.Where(x => x.RefId == refId && x.RefUserName == request.CreatedBy));
15+
if (rowsAffected > 0)
16+
{
17+
appConfig.ResetUsersUnreadNotifications(db);
18+
}
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Data;
2+
using MyApp.Data;
3+
using MyApp.ServiceModel;
4+
using ServiceStack.OrmLite;
5+
6+
namespace MyApp.ServiceInterface.App;
7+
8+
public class DeletePostCommand(AppConfig appConfig, IDbConnection db) : IExecuteCommandAsync<DeletePost>
9+
{
10+
public async Task ExecuteAsync(DeletePost request)
11+
{
12+
foreach (var postId in request.Ids)
13+
{
14+
await db.DeleteAsync<PostJob>(x => x.PostId == postId);
15+
await db.DeleteAsync<Vote>(x => x.PostId == postId);
16+
await db.DeleteByIdAsync<Post>(postId);
17+
appConfig.ResetInitialPostId(db);
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)