Repository navigation
Expand file tree
/
Copy pathUser.cs
More file actions
123 lines (97 loc) · 4.99 KB
/
Copy pathUser.cs
File metadata and controls
123 lines (97 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
using ProblemSource.Services;
using ProblemSourceModule.Models.Aggregates;
using ProblemSourceModule.Services.Storage;
namespace ProblemSourceModule.Models
{
public class User
{
public string Email { get; set; } = string.Empty;
public string HashedPassword { get; set; } = string.Empty;
public string Role { get; set; } = "";
public string PasswordForHashing { set { HashedPassword = HashPassword(NormalizeEmail(Email), value); } }
public bool VerifyPassword(string password)
{
var tmp = new User { Email = Email, PasswordForHashing = password };
return tmp.HashedPassword == HashedPassword;
}
public UserTrainingsCollection Trainings { get; set; } = new();
public static string NormalizeEmail(string email) => email.ToLower().Trim();
private static string HashPassword(string saltBase, string password)
{
//var salt = System.Security.Cryptography.RandomNumberGenerator.GetBytes(128 / 8); // divide by 8 to convert bits to bytes
var salt = System.Text.Encoding.UTF8.GetBytes(saltBase.PadLeft(128 / 8, '0'));
// derive a 256-bit subkey (use HMACSHA256 with 100,000 iterations)
return Convert.ToBase64String(
Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivation.Pbkdf2(
password: password!,
salt: salt,
prf: Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivationPrf.HMACSHA256,
iterationCount: 100000,
numBytesRequested: 256 / 8));
}
}
public class UserTrainingsCollection : Dictionary<string, List<int>>
{
public UserTrainingsCollection()
{
}
public UserTrainingsCollection(string groupName, IEnumerable<int> ids)
{
Add(groupName, ids.ToList());
}
public UserTrainingsCollection(Dictionary<string, IEnumerable<int>> groups)
{
foreach (var kv in groups)
Add(kv.Key, kv.Value.ToList());
}
public UserTrainingsCollection(Dictionary<string, List<int>> groups)
{
foreach (var kv in groups)
Add(kv.Key, kv.Value);
}
public List<int> GetAllIds()
{
return Values.SelectMany(o => o).Distinct().ToList();
}
public async Task<Dictionary<string, List<(int Id, Training Training, TrainingSummary? Summary)>>> GetTrainingsInfo(ITrainingRepository trainingRepo, IStatisticsProvider statisticsProvider)
{
var summaries = await statisticsProvider.GetTrainingSummaries(GetAllIds());
var trainings = await trainingRepo.GetByIds(GetAllIds());
var result = new Dictionary<string, List<(int, Training, TrainingSummary?)>>();
foreach (var kv in this)
result.Add(kv.Key, kv.Value.Select(id => (id, trainings.Single(o => o.Id == id), summaries.SingleOrDefault(o => o?.Id == id))).ToList());
return result;
}
public async Task<Dictionary<string, List<Training>>> RemoveUnusedFromGroups(int numTrainings, string exceptGroup, ITrainingRepository trainingRepo, IStatisticsProvider statisticsProvider)
{
var info = await GetTrainingsInfo(trainingRepo, statisticsProvider);
var summaries = info.SelectMany(o => o.Value).Select(o => o.Item3).OfType<TrainingSummary>();
var startedTrainingIds = summaries.Where(o => o.TrainedDays >= 1).Select(o => o.Id).ToList();
var trainings = info.SelectMany(o => o.Value).Select(o => o.Item2).OfType<Training>();
var justCreatedTrainingIds = trainings.Where(o => o.Created >= DateTimeOffset.UtcNow.AddDays(-1)).Select(o => o.Id).ToList(); ;
var numRemainingToTransfer = numTrainings;
var updated = new Dictionary<string, List<int>>();
var source = new Dictionary<string, List<Training>>();
foreach (var kv in this.Where(o => o.Key != exceptGroup))
{
var available = kv.Value.Except(startedTrainingIds).Except(justCreatedTrainingIds);
var forTransfer = available.Take(numRemainingToTransfer);
if (forTransfer.Any())
{
source[kv.Key] = forTransfer.Select(id => trainings.Single(p => p.Id == id)).ToList();
updated[kv.Key] = kv.Value.Except(forTransfer).ToList();
}
numRemainingToTransfer -= forTransfer.Count();
if (numRemainingToTransfer == 0)
break;
}
if (numRemainingToTransfer > 0)
throw new Exception("Not enough unused trainings");
// Update data only after we have verified enough trainings could be transferred
foreach (var item in updated)
this[item.Key] = item.Value;
return source;
// Note: caller updates user repo
}
}
}