public async Task <ActionResult <PKMember> > PostMember([FromBody] PKMember newMember) { var system = _auth.CurrentSystem; if (newMember.Name == null) { return(BadRequest("Member name cannot be null.")); } // Explicit bounds checks if (newMember.Name != null && newMember.Name.Length > Limits.MaxMemberNameLength) { return(BadRequest($"Member name too long ({newMember.Name.Length} > {Limits.MaxMemberNameLength}.")); } if (newMember.Pronouns != null && newMember.Pronouns.Length > Limits.MaxPronounsLength) { return(BadRequest($"Member pronouns too long ({newMember.Pronouns.Length} > {Limits.MaxPronounsLength}.")); } if (newMember.Description != null && newMember.Description.Length > Limits.MaxDescriptionLength) { return(BadRequest($"Member descriptions too long ({newMember.Description.Length} > {Limits.MaxDescriptionLength}.")); } // Sanity bounds checks if (newMember.AvatarUrl != null && newMember.AvatarUrl.Length > 1000) { return(BadRequest()); } if (newMember.Prefix != null && newMember.Prefix.Length > 1000) { return(BadRequest()); } if (newMember.Suffix != null && newMember.Suffix.Length > 1000) { return(BadRequest()); } var member = await _members.Create(system, newMember.Name); member.Name = newMember.Name; member.Color = newMember.Color; member.AvatarUrl = newMember.AvatarUrl; member.Birthday = newMember.Birthday; member.Pronouns = newMember.Pronouns; member.Description = newMember.Description; member.Prefix = newMember.Prefix; member.Suffix = newMember.Suffix; await _members.Save(member); return(Ok(member)); }
public async Task <ImportResult> ImportSystem(DataFileSystem data, PKSystem system, ulong accountId) { // TODO: make atomic, somehow - we'd need to obtain one IDbConnection and reuse it // which probably means refactoring SystemStore.Save and friends etc var result = new ImportResult { AddedNames = new List <string>(), ModifiedNames = new List <string>() }; // If we don't already have a system to save to, create one if (system == null) { system = await _systems.Create(data.Name); } // Apply system info system.Name = data.Name; if (data.Description != null) { system.Description = data.Description; } if (data.Tag != null) { system.Tag = data.Tag; } if (data.AvatarUrl != null) { system.AvatarUrl = data.AvatarUrl; } if (data.TimeZone != null) { system.UiTz = data.TimeZone ?? "UTC"; } await _systems.Save(system); // Make sure to link the sender account, too await _systems.Link(system, accountId); // Apply members // TODO: parallelize? foreach (var dataMember in data.Members) { // If member's given an ID, we try to look up the member with the given ID PKMember member = null; if (dataMember.Id != null) { member = await _members.GetByHid(dataMember.Id); // ...but if it's a different system's member, we just make a new one anyway if (member != null && member.System != system.Id) { member = null; } } // Try to look up by name, too if (member == null) { member = await _members.GetByName(system, dataMember.Name); } // And if all else fails (eg. fresh import from Tupperbox, etc) we just make a member lol if (member == null) { member = await _members.Create(system, dataMember.Name); result.AddedNames.Add(dataMember.Name); } else { result.ModifiedNames.Add(dataMember.Name); } // Apply member info member.Name = dataMember.Name; if (dataMember.Description != null) { member.Description = dataMember.Description; } if (dataMember.Color != null) { member.Color = dataMember.Color; } if (dataMember.AvatarUrl != null) { member.AvatarUrl = dataMember.AvatarUrl; } if (dataMember.Prefix != null || dataMember.Suffix != null) { member.Prefix = dataMember.Prefix; member.Suffix = dataMember.Suffix; } if (dataMember.Birthday != null) { var birthdayParse = Formats.DateExportFormat.Parse(dataMember.Birthday); member.Birthday = birthdayParse.Success ? (LocalDate?)birthdayParse.Value : null; } await _members.Save(member); } _logger.Information("Imported system {System}", system.Id); // TODO: import switches, too? result.System = system; return(result); }
public async Task RenameMember(Context ctx, PKMember target) { // TODO: this method is pretty much a 1:1 copy/paste of the above creation method, find a way to clean? if (ctx.System == null) { throw Errors.NoSystemError; } if (target.System != ctx.System.Id) { throw Errors.NotOwnMemberError; } var newName = ctx.RemainderOrNull(); // Hard name length cap if (newName.Length > Limits.MaxMemberNameLength) { throw Errors.MemberNameTooLongError(newName.Length); } // Warn if member name will be unproxyable (with/without tag), only if member doesn't have a display name if (target.DisplayName == null && newName.Length > ctx.System.MaxMemberNameLength) { var msg = await ctx.Reply($"{Emojis.Warn} New member name too long ({newName.Length} > {ctx.System.MaxMemberNameLength} characters), this member will be unproxyable. Do you want to change it anyway? (You can set a member display name instead)"); if (!await ctx.PromptYesNo(msg)) { throw new PKError("Member renaming cancelled."); } } // Warn if there's already a member by this name var existingMember = await _members.GetByName(ctx.System, newName); if (existingMember != null) { var msg = await ctx.Reply($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.SanitizeMentions()}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?"); if (!await ctx.PromptYesNo(msg)) { throw new PKError("Member renaming cancelled."); } } // Rename the member target.Name = newName; await _members.Save(target); await ctx.Reply($"{Emojis.Success} Member renamed."); if (newName.Contains(" ")) { await ctx.Reply($"{Emojis.Note} Note that this member's name now contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it."); } if (target.DisplayName != null) { await ctx.Reply($"{Emojis.Note} Note that this member has a display name set ({target.DisplayName.SanitizeMentions()}), and will be proxied using that name instead."); } await _proxyCache.InvalidateResultsForSystem(ctx.System); }
public async Task <ImportResult> ImportSystem(DataFileSystem data, PKSystem system, ulong accountId) { // TODO: make atomic, somehow - we'd need to obtain one IDbConnection and reuse it // which probably means refactoring SystemStore.Save and friends etc var result = new ImportResult { AddedNames = new List <string>(), ModifiedNames = new List <string>(), Success = true // Assume success unless indicated otherwise }; var dataFileToMemberMapping = new Dictionary <string, PKMember>(); var unmappedMembers = new List <DataFileMember>(); // If we don't already have a system to save to, create one if (system == null) { system = await _systems.Create(data.Name); } result.System = system; // Apply system info system.Name = data.Name; if (data.Description != null) { system.Description = data.Description; } if (data.Tag != null) { system.Tag = data.Tag; } if (data.AvatarUrl != null) { system.AvatarUrl = data.AvatarUrl; } if (data.TimeZone != null) { system.UiTz = data.TimeZone ?? "UTC"; } await _systems.Save(system); // Make sure to link the sender account, too await _systems.Link(system, accountId); // Determine which members already exist and which ones need to be created var existingMembers = await _members.GetBySystem(system); foreach (var d in data.Members) { // Try to look up the member with the given ID var match = existingMembers.FirstOrDefault(m => m.Hid.Equals(d.Id)); if (match == null) { match = existingMembers.FirstOrDefault(m => m.Name.Equals(d.Name)); // Try with the name instead } if (match != null) { dataFileToMemberMapping.Add(d.Id, match); // Relate the data file ID to the PKMember for importing switches result.ModifiedNames.Add(d.Name); } else { unmappedMembers.Add(d); // Track members that weren't found so we can create them all result.AddedNames.Add(d.Name); } } // If creating the unmatched members would put us over the member limit, abort before creating any members // new total: # in the system + (# in the file - # in the file that already exist) if (data.Members.Count - dataFileToMemberMapping.Count + existingMembers.Count() > Limits.MaxMemberCount) { result.Success = false; result.Message = $"Import would exceed the maximum number of members ({Limits.MaxMemberCount})."; result.AddedNames.Clear(); result.ModifiedNames.Clear(); return(result); } // Create all unmapped members in one transaction // These consist of members from another PluralKit system or another framework (e.g. Tupperbox) var membersToCreate = new Dictionary <string, string>(); unmappedMembers.ForEach(x => membersToCreate.Add(x.Id, x.Name)); var newMembers = await _members.CreateMultiple(system, membersToCreate); foreach (var member in newMembers) { dataFileToMemberMapping.Add(member.Key, member.Value); } // Update members with data file properties // TODO: parallelize? foreach (var dataMember in data.Members) { dataFileToMemberMapping.TryGetValue(dataMember.Id, out PKMember member); if (member == null) { continue; } // Apply member info member.Name = dataMember.Name; if (dataMember.DisplayName != null) { member.DisplayName = dataMember.DisplayName; } if (dataMember.Description != null) { member.Description = dataMember.Description; } if (dataMember.Color != null) { member.Color = dataMember.Color; } if (dataMember.AvatarUrl != null) { member.AvatarUrl = dataMember.AvatarUrl; } if (dataMember.Prefix != null || dataMember.Suffix != null) { member.Prefix = dataMember.Prefix; member.Suffix = dataMember.Suffix; } if (dataMember.Birthday != null) { var birthdayParse = Formats.DateExportFormat.Parse(dataMember.Birthday); member.Birthday = birthdayParse.Success ? (LocalDate?)birthdayParse.Value : null; } await _members.Save(member); } // Re-map the switch members in the likely case IDs have changed var mappedSwitches = new List <Tuple <Instant, ICollection <PKMember> > >(); foreach (var sw in data.Switches) { var timestamp = InstantPattern.ExtendedIso.Parse(sw.Timestamp).Value; var swMembers = new List <PKMember>(); swMembers.AddRange(sw.Members.Select(x => dataFileToMemberMapping.FirstOrDefault(y => y.Key.Equals(x)).Value)); var mapped = new Tuple <Instant, ICollection <PKMember> >(timestamp, swMembers); mappedSwitches.Add(mapped); } // Import switches if (mappedSwitches.Any()) { await _switches.BulkImportSwitches(system, mappedSwitches); } _logger.Information("Imported system {System}", system.Hid); return(result); }