PageRenderTime 35ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/App/StackExchange.DataExplorer/Models/User.cs

https://code.google.com/p/stack-exchange-data-explorer/
C# | 367 lines | 293 code | 63 blank | 11 comment | 49 complexity | 6616eb283453c44ed75f1f4eff9257c9 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data.SqlClient;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using StackExchange.DataExplorer.Helpers;
  8. namespace StackExchange.DataExplorer.Models
  9. {
  10. public partial class User
  11. {
  12. public int Id { get; set; }
  13. public string Login { get; set; }
  14. public string Email { get; set; }
  15. public DateTime? LastLogin { get; set; }
  16. public bool IsAdmin { get; set; }
  17. public string IPAddress { get; set; }
  18. public DateTime? CreationDate { get; set; }
  19. public string AboutMe { get; set; }
  20. public string Website { get; set; }
  21. public string Location { get; set; }
  22. public DateTime? DOB { get; set; }
  23. public DateTime? LastActivityDate { get; set; }
  24. public DateTime? LastSeenDate { get; set; }
  25. public string PreferencesRaw { get; set; }
  26. List<UserOpenId> _userOpenIds;
  27. public List<UserOpenId> UserOpenIds
  28. {
  29. get
  30. {
  31. _userOpenIds = _userOpenIds ?? Current.DB.Query<UserOpenId>("select * from UserOpenIds where UserId = @Id", new { Id }).ToList();
  32. return _userOpenIds;
  33. }
  34. }
  35. private const string emptyLogin = "jon.doe";
  36. /// <summary>
  37. /// Should be set when ControllerBase determines CurrentUser
  38. /// </summary>
  39. public string XSRFFormValue { get; set; }
  40. public bool IsAnonymous { get; set; }
  41. public string SafeAboutMe
  42. {
  43. get { return HtmlUtilities.Safe(HtmlUtilities.RawToCooked(AboutMe ?? "")); }
  44. }
  45. public string Age
  46. {
  47. get
  48. {
  49. if (DOB == null) return "";
  50. DateTime now = DateTime.Today;
  51. int age = now.Year - DOB.Value.Year;
  52. if (DOB.Value > now.AddYears(-age)) age--;
  53. return age.ToString();
  54. }
  55. }
  56. public bool IsValid(ChangeAction action)
  57. {
  58. return (GetBusinessRuleViolations(action).Count == 0);
  59. }
  60. public void OnValidate(ChangeAction action)
  61. {
  62. if (!IsValid(action))
  63. throw new ApplicationException("User object not in a valid state.");
  64. }
  65. public IList<BusinessRuleViolation> GetBusinessRuleViolations(ChangeAction action)
  66. {
  67. var violations = new List<BusinessRuleViolation>();
  68. if (Login.IsNullOrEmpty())
  69. violations.Add(new BusinessRuleViolation("Login name is required.", "Login"));
  70. if ((action == ChangeAction.Insert) || (action == ChangeAction.Update))
  71. {
  72. if (Current.DB.Query<int>("select 1 from Users where Login = @Login and Id <> @Id", new { Login, Id }).Any())
  73. {
  74. violations.Add(new BusinessRuleViolation("Login name must be unique.", "Login"));
  75. }
  76. }
  77. return violations;
  78. }
  79. public static User CreateUser(string login, string email, string openIdClaim)
  80. {
  81. var u = new User();
  82. u.CreationDate = DateTime.UtcNow;
  83. login = CleanLogin(login ?? string.Empty);
  84. if (login.Length == 0)
  85. {
  86. login = emptyLogin;
  87. }
  88. u.Login = login;
  89. u.Email = email;
  90. int retries = 0;
  91. bool success = false;
  92. int maxId = Current.DB.Query<int?>("select max(Id) + 1 from Users").First() ?? 0;
  93. maxId += 1;
  94. while (!success)
  95. {
  96. IList<BusinessRuleViolation> violations = u.GetBusinessRuleViolations(ChangeAction.Insert);
  97. if (violations.Any(v => v.PropertyName == "Login"))
  98. {
  99. u.Login = login + (maxId + retries);
  100. }
  101. else if (violations.Count > 0)
  102. {
  103. throw new NotImplementedException("The User isn't valid, and we can't compensate for it right now.");
  104. }
  105. else
  106. {
  107. success = true;
  108. }
  109. }
  110. u.Id = Current.DB.Users.Insert(new { u.Email, u.Login, u.CreationDate }).Value;
  111. Current.DB.UserOpenIds.Insert(new { OpenIdClaim = openIdClaim, UserId = u.Id });
  112. return u;
  113. }
  114. private static string CleanLogin(string login)
  115. {
  116. login = Regex.Replace(login, "\\s+", ".");
  117. login = Regex.Replace(login, "[^\\.a-zA-Z0-9]", "");
  118. return login;
  119. }
  120. public static string NormalizeOpenId(string openId)
  121. {
  122. openId = (openId ?? "").Trim().ToLowerInvariant();
  123. if (openId.StartsWith("https://"))
  124. openId = "http://" + openId.Substring(8);
  125. if (openId.EndsWith("/"))
  126. openId = openId.Substring(0, openId.Length - 1);
  127. if (canRemoveWww.IsMatch(openId))
  128. openId = openId.ReplaceFirst("www.", "");
  129. return openId;
  130. }
  131. // allow varying of "www." only as the third-level domain
  132. private static Regex canRemoveWww = new Regex(@"^https?://www\.[^./]+\.[^./]+(?:/|$)", RegexOptions.Compiled);
  133. public string Gravatar(int width)
  134. {
  135. return
  136. String.Format(
  137. "<img src=\"http://www.gravatar.com/avatar/{0}?s={1}&amp;d=identicon&amp;r=PG\" height=\"{1}\" width=\"{1}\" class=\"logo\">",
  138. Util.GravatarHash(Email ?? Id.ToString()), width + "px"
  139. );
  140. }
  141. public string UrlTitle
  142. {
  143. get { return HtmlUtilities.URLFriendly(Login); }
  144. }
  145. internal static bool MergeUsers(int masterId, int mergeId, StringBuilder log)
  146. {
  147. var db = Current.DB;
  148. string canMergeMsg = "";
  149. if (!CanMergeUsers(masterId, mergeId, out canMergeMsg))
  150. {
  151. log.AppendLine(canMergeMsg);
  152. return false;
  153. }
  154. var masterUser = db.Users.Get(masterId);
  155. var mergeUser = db.Users.Get(mergeId);
  156. log.AppendLine(string.Format("Beginning merge of {0} into {1}", mergeId, masterId));
  157. // Revision Executions
  158. {
  159. var updates = db.Execute(@"update r set ExecutionCount = r.ExecutionCount + r2.ExecutionCount
  160. from RevisionExecutions r
  161. join RevisionExecutions r2 on r.SiteId = r2.SiteId and r.RevisionId = r2.RevisionId
  162. where r.UserId = @masterId and r2.UserId = @mergeId", new { mergeId, masterId });
  163. var deletes = db.Execute(@"delete r
  164. from RevisionExecutions r
  165. join RevisionExecutions r2 on r.SiteId = r2.SiteId and r.RevisionId = r2.RevisionId
  166. where r.UserId = @mergeId and r2.UserId = @masterId", new { mergeId, masterId });
  167. var remaps = db.Execute(@"update r set UserId = @masterId
  168. from RevisionExecutions r
  169. where UserId = @mergeId", new { mergeId, masterId });
  170. log.AppendLine(string.Format("Moving revision executions over {0} dupes, {1} remapped, {2} deleted", updates, remaps, deletes));
  171. }
  172. // User Open Ids
  173. {
  174. var rempped = db.Execute("update UserOpenIds set UserId = @masterId where UserId = @mergeId", new { mergeId, masterId });
  175. log.AppendLine(string.Format("Remapped {0} user open ids", rempped));
  176. }
  177. // update QuerySets
  178. {
  179. var rempped = db.Execute("update QuerySets set OwnerId = @masterId where OwnerId = @mergeId", new { mergeId, masterId });
  180. log.AppendLine(string.Format("Remapped {0} user query sets", rempped));
  181. }
  182. // revisions
  183. {
  184. var rempped = db.Execute("update Revisions set OwnerId = @masterId where OwnerId = @mergeId", new { mergeId, masterId });
  185. log.AppendLine(string.Format("Remapped {0} revisions", rempped));
  186. }
  187. // votes
  188. {
  189. var dupes = db.Execute(@"delete v
  190. from Votes v
  191. join Votes v2 on v.VoteTypeId = v2.VoteTypeId and v.QuerySetId = v2.QuerySetId
  192. where v.UserId = @mergeId and v2.UserId = @masterId", new { mergeId, masterId });
  193. var rempped = db.Execute("update Votes set UserId = @masterId where UserId = @mergeId", new { mergeId, masterId });
  194. log.AppendLine(string.Format("Remapped {0} votes, deleted {1} dupes", rempped, dupes));
  195. }
  196. // SavedQueries (it is deprecated, but do it just in case)
  197. try
  198. {
  199. var count = db.Execute("update SavedQueries set UserId = @masterId where UserId = @mergeId", new { mergeId, masterId });
  200. log.AppendLine(string.Format("Remapped {0} saved queries", count));
  201. }
  202. catch
  203. {
  204. log.AppendLine("Failed to remap saved queries, perhaps it is time to dump this ... ");
  205. }
  206. // User
  207. log.AppendLine("Moving user properties over");
  208. string savedLogin = null;
  209. if (masterUser.Login.StartsWith(emptyLogin) && !mergeUser.Login.StartsWith(emptyLogin)) savedLogin = mergeUser.Login;
  210. if (masterUser.Email.IsNullOrEmpty() && !mergeUser.Email.IsNullOrEmpty()) masterUser.Email = mergeUser.Email;
  211. if (!masterUser.IsAdmin && mergeUser.IsAdmin) masterUser.IsAdmin = true;
  212. if (masterUser.CreationDate.GetValueOrDefault() > mergeUser.CreationDate.GetValueOrDefault()) masterUser.CreationDate = mergeUser.CreationDate;
  213. if (masterUser.AboutMe.IsNullOrEmpty() && mergeUser.AboutMe.HasValue()) masterUser.AboutMe = mergeUser.AboutMe;
  214. if (masterUser.Website.IsNullOrEmpty() && mergeUser.Website.HasValue()) masterUser.Website = mergeUser.Website;
  215. if (masterUser.Location.IsNullOrEmpty() && mergeUser.Location.HasValue()) masterUser.Location = mergeUser.Location;
  216. if (!masterUser.DOB.HasValue && mergeUser.DOB.HasValue) masterUser.DOB = mergeUser.DOB;
  217. if (masterUser.LastSeenDate.GetValueOrDefault() < mergeUser.LastSeenDate.GetValueOrDefault())
  218. {
  219. masterUser.LastSeenDate = mergeUser.LastSeenDate;
  220. masterUser.IPAddress = mergeUser.IPAddress;
  221. }
  222. var violations = masterUser.GetBusinessRuleViolations(ChangeAction.Update);
  223. if (violations.Count == 0)
  224. {
  225. db.Users.Update(masterUser.Id, new
  226. {
  227. masterUser.Email,
  228. masterUser.IsAdmin,
  229. masterUser.CreationDate,
  230. masterUser.AboutMe,
  231. masterUser.Website,
  232. masterUser.Location,
  233. masterUser.DOB,
  234. masterUser.LastSeenDate,
  235. masterUser.IPAddress
  236. });
  237. log.AppendLine("Updated user record");
  238. }
  239. else
  240. {
  241. log.AppendLine("**UNABLE TO SUBMIT:");
  242. violations.ToList().ForEach(v => log.AppendLine(string.Format("--{0}: {1}", v.PropertyName, v.ErrorMessage)));
  243. return false;
  244. }
  245. db.Users.Delete(mergeUser.Id);
  246. log.AppendLine("Deleted merged user");
  247. if (savedLogin.HasValue())
  248. {
  249. Current.DB.Users.Update(masterUser.Id, new { Login = savedLogin });
  250. log.AppendLine("Replaced username on master since it was a jon.doe");
  251. }
  252. log.AppendLine("That's all folks");
  253. return true;
  254. }
  255. public static bool CanMergeUsers(int masterId, int mergeId, out string canMergeMsg)
  256. {
  257. var masterUser = Current.DB.Users.Get(masterId);
  258. var mergeUser = Current.DB.Users.Get(mergeId);
  259. if (masterId == mergeId)
  260. {
  261. canMergeMsg = "User ids are identical";
  262. return false;
  263. }
  264. else if (masterUser == null)
  265. {
  266. canMergeMsg = string.Format("Master user (id {0}) not found", masterId);
  267. return false;
  268. }
  269. else if (mergeUser == null)
  270. {
  271. canMergeMsg = string.Format("Merge user (id {0}) not found", mergeId);
  272. return false;
  273. }
  274. canMergeMsg = null;
  275. return true;
  276. }
  277. int? _savedQueriesCount;
  278. public int SavedQueriesCount
  279. {
  280. get
  281. {
  282. _savedQueriesCount = _savedQueriesCount ?? Current.DB.Query<int>("SELECT COUNT(*) FROM QuerySets WHERE OwnerId = @userId", new { userId = Id }).FirstOrDefault();
  283. return _savedQueriesCount.Value;
  284. }
  285. set
  286. {
  287. _savedQueriesCount = value;
  288. }
  289. }
  290. int? _queryExecutionsCount;
  291. public int QueryExecutionsCount
  292. {
  293. get
  294. {
  295. _queryExecutionsCount = _queryExecutionsCount ?? Current.DB.Query<int>("select count(*) from RevisionExecutions where UserId = @userId", new { userId = Id }).FirstOrDefault();
  296. return _queryExecutionsCount.Value;
  297. }
  298. set
  299. {
  300. _queryExecutionsCount = value;
  301. }
  302. }
  303. }
  304. }