using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using sodoff.Attributes; using sodoff.Model; using sodoff.Schema; using sodoff.Util; using GroupMember = sodoff.Model.GroupMember; namespace sodoff.Controllers.Common; public class GroupController : Controller { // Any permission that is commented out is not implemented. private static readonly List PermissionsMember = [ //"Delete Own Msg", //"Post Message" ]; private static readonly List PermissionsElder = [ //"Invite", "Approve Join Request", //"Post News", //"Delete Own Msg", //"Delete Any Msg", //"Delete News", //"Post Message", "Remove Member" ]; private static readonly List PermissionsLeader = [ //"Invite", "Approve Join Request", "Assign Leader", "Assign Elder", "Demote Elder", "Edit Group", //"Post News", //"Delete Own Msg", //"Delete Any Msg", //"Delete News", //"Post Message", "Remove Member" ]; private static readonly List RolePermissions = [ new RolePermission { GroupType = GroupType.Public, Role = UserRole.Member, Permissions = PermissionsMember }, new RolePermission { GroupType = GroupType.Public, Role = UserRole.Elder, Permissions = PermissionsElder }, new RolePermission { GroupType = GroupType.Public, Role = UserRole.Leader, Permissions = PermissionsLeader }, new RolePermission { GroupType = GroupType.MembersOnly, Role = UserRole.Member, Permissions = PermissionsMember }, new RolePermission { GroupType = GroupType.MembersOnly, Role = UserRole.Elder, Permissions = PermissionsElder }, new RolePermission { GroupType = GroupType.MembersOnly, Role = UserRole.Leader, Permissions = PermissionsLeader }, new RolePermission { GroupType = GroupType.Private, Role = UserRole.Member, Permissions = PermissionsMember }, new RolePermission { GroupType = GroupType.Private, Role = UserRole.Elder, Permissions = PermissionsElder }, new RolePermission { GroupType = GroupType.Private, Role = UserRole.Leader, Permissions = PermissionsLeader } ]; private readonly DBContext ctx; public GroupController(DBContext ctx) { this.ctx = ctx; } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/CreateGroup")] [VikingSession] public IActionResult CreateGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupCreateRequest) { uint gameId = ClientVersion.GetGameID(apiKey); if (viking.GroupMembership != null) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.CreatorIsNotApproved }); CreateGroupRequest request = XmlUtil.DeserializeXml(groupCreateRequest); request.Name = request.Name.Trim(); // Cue the gauntlet of validity checks. if (request.Type <= GroupType.System) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupTypeIsInvalid }); //if (request.MaxMemberLimit < 4) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupMaxMemberLimitInvalid }); // Not actually used by the game. if (request.Name.Length == 0) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupNameIsEmpty }); if (request.Description.Length == 0) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupDescriptionIsEmpty }); if (ctx.Groups.Any(g => g.Name.ToLower() == request.Name.ToLower())) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupNameIsDuplicate }); Model.Group group = new Model.Group { Name = request.Name, Description = request.Description, Logo = request.Logo, Color = request.Color, Type = request.Type, CreateDate = DateTime.UtcNow, GameID = gameId, MaxMemberLimit = 50, GroupID = Guid.NewGuid(), Vikings = new List() }; ctx.Groups.Add(group); group.Vikings.Add(new GroupMember { Viking = viking, Group = group, UserRole = UserRole.Leader, JoinDate = group.CreateDate }); group.LastActiveTime = group.CreateDate; ctx.SaveChanges(); return Ok(new CreateGroupResult { Success = true, Status = CreateGroupStatus.Success, Group = new Schema.Group { Name = group.Name, Description = group.Description, Logo = group.Logo, Color = group.Color, Type = group.Type, OwnerID = viking.Uid.ToString(), Points = 0,//group.Points, Active = false,//group.Vikings.Count >= 4, MemberLimit = group.MaxMemberLimit, GroupID = group.GroupID.ToString() } // TODO: Delete after 15 days of less than 4 members. }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/EditGroup")] [VikingSession] public IActionResult EditGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupEditRequest) { EditGroupRequest request = XmlUtil.DeserializeXml(groupEditRequest); request.Name = request.Name.Trim(); GroupMember? vikingRole = viking.GroupMembership; if (vikingRole == null || vikingRole.Group.GroupID.ToString() != request.GroupID) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupNotFound }); if (vikingRole.UserRole < UserRole.Elder) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.PermissionDenied }); // Cue the gauntlet of validity checks. if (request.Type != null && request.Type <= GroupType.System) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupTypeIsInvalid }); //if (request.MaxMemberLimit < 4) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupMaxMemberLimitInvalid }); // Not actually used by the game. if (string.IsNullOrEmpty(request.Name)) request.Name = vikingRole.Group.Name; if (string.IsNullOrEmpty(request.Description)) request.Description = vikingRole.Group.Description; if (request.Name != vikingRole.Group.Name && ctx.Groups.Any(g => g.Name.ToLower() == request.Name.ToLower())) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupNameIsDuplicate }); vikingRole.Group.Name = request.Name; vikingRole.Group.Description = request.Description; if (request.Type != null) vikingRole.Group.Type = (GroupType)request.Type; if (!string.IsNullOrEmpty(request.Color)) vikingRole.Group.Color = request.Color; if (!string.IsNullOrEmpty(request.Logo)) vikingRole.Group.Logo = request.Logo; ctx.SaveChanges(); return Ok(new EditGroupResult { Success = true, Status = EditGroupStatus.Success, NewRolePermissions = RolePermissions }); } [HttpPost] [Produces("application/xml")] [Route("GroupWebService.asmx/JoinGroup")] [VikingSession] public IActionResult JoinGroupV1(Viking viking, [FromForm] string apiKey, [FromForm] string groupID) { Guid parsedGroupID = Guid.Parse(groupID); uint gameId = ClientVersion.GetGameID(apiKey); // Check for loyalty. if (viking.GroupMembership != null) return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.SELF_BLOCKED }); Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID == parsedGroupID); if (group != null) { // This check is only on this side to prevent people from attempting to circumvent the join limit. if (group.Type <= GroupType.System || group.Vikings.Count < group.MaxMemberLimit) { group.Vikings.Add(new GroupMember { Viking = viking, Group = group, UserRole = UserRole.Member }); ctx.SaveChanges(); return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.APPROVED }); } } return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.REJECTED }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/JoinGroup")] [VikingSession] public IActionResult JoinGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupJoinRequest) { uint gameId = ClientVersion.GetGameID(apiKey); JoinGroupRequest request = XmlUtil.DeserializeXml(groupJoinRequest); Guid parsedGroupID = Guid.Parse(request.GroupID); Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID == parsedGroupID); if (group == null) return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.Error }); if (group.Type >= GroupType.Private) { return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.GroupTypeIsNotPublic }); } GroupMember? existing = viking.GroupMembership; if (existing != null) { if (existing.Group == group) return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.UserAlreadyMemberOfTheGroup }); existing.Group.Vikings.Remove(existing); if (!existing.Group.Vikings.Any()) ctx.Groups.Remove(existing.Group); } if (group.Type == GroupType.MembersOnly) { if (!group.JoinRequests.Any(r => r.Viking == viking)) group.JoinRequests.Add(new GroupJoinRequest { Group = group, Viking = viking, Message = request.Message }); ctx.SaveChanges(); return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.JoinRequestPending }); } if (group.Vikings.Count >= group.MaxMemberLimit) return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.GroupIsFull }); GroupMember joinee = new GroupMember { Viking = viking, Group = group, UserRole = UserRole.Member, JoinDate = DateTime.UtcNow }; group.Vikings.Add(joinee); group.LastActiveTime = joinee.JoinDate; ctx.SaveChanges(); return Ok(new GroupJoinResult { Success = true, Status = JoinGroupStatus.Success }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/LeaveGroup")] [VikingSession] public IActionResult LeaveGroup(Viking viking, [FromForm] string groupLeaveRequest) { LeaveGroupRequest request = XmlUtil.DeserializeXml(groupLeaveRequest); GroupMember? vikingRole = viking.GroupMembership; if (vikingRole == null || vikingRole.Group.GroupID.ToString() != request.GroupID) return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.Error }); GroupMember? targetRole; if (viking.Uid == Guid.Parse(request.UserID)) { targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking == viking); } else if (vikingRole.UserRole >= UserRole.Elder) { targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid == Guid.Parse(request.UserID)); } else return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.Error }); if (targetRole == null) return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.UserNotAMemberOfTheGroup }); vikingRole.Group.Vikings.Remove(targetRole); if (!vikingRole.Group.Vikings.Any()) ctx.Groups.Remove(vikingRole.Group); ctx.SaveChanges(); return Ok(new LeaveGroupResult { Success = true, Status = LeaveGroupStatus.Success }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/GetGroups")] public IActionResult GetGroups([FromForm] string apiKey, [FromForm] string getGroupsRequest) { uint gameId = ClientVersion.GetGameID(apiKey); GetGroupsRequest request = XmlUtil.DeserializeXml(getGroupsRequest); IQueryable groupsQuery = ctx.Groups.Where(g => g.GameID == gameId); if (request.ForUserID != null) { Viking? target = ctx.Vikings.FirstOrDefault(v => Guid.Parse(request.ForUserID) == v.Uid); if (target == null) return Ok(new GetGroupsResult { Success = false }); if (target.GroupMembership?.Group == null) return Ok(new GetGroupsResult { Success = true }); groupsQuery = groupsQuery.Where(g => g.GroupID == target.GroupMembership.Group.GroupID); } else { groupsQuery = groupsQuery.Where(g => g.Type == GroupType.Public || g.Type == GroupType.MembersOnly); } if (request.Name != null) groupsQuery = groupsQuery.Where(g => g.Name.ToLower().Contains(request.Name.ToLower())); groupsQuery = groupsQuery.OrderByDescending(g => g.Points); int skip = 0; if (request.PageSize != null) { if ((request.PageNo ?? 0) > 1) skip = (int)((request.PageNo! - 1) * request.PageSize); if (skip > 0) groupsQuery = groupsQuery.Skip(skip); groupsQuery = groupsQuery.Take((int) request.PageSize); } if (request.IncludeMemberCount) groupsQuery = groupsQuery.Include(g => g.Vikings); var groups = groupsQuery.ToList(); return Ok(new GetGroupsResult { Success = true, Groups = groups.Select((g, i) => { Schema.Group group = new Schema.Group { Name = g.Name, Description = g.Description, GroupID = g.GroupID.ToString(), OwnerID = (g.Vikings.FirstOrDefault(v => v.UserRole == UserRole.Leader)?.Viking?.Uid ?? Guid.Empty).ToString(), Color = g.Color, Logo = g.Logo, Type = g.Type, Rank = i+skip+1, MemberLimit = g.MaxMemberLimit }; if (request.IncludeMemberCount) group.TotalMemberCount = g.Vikings.Count; return group; }).ToArray(), RolePermissions = RolePermissions }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/RemoveMember")] [VikingSession] public IActionResult RemoveMember(Viking viking, [FromForm] string removeMemberRequest) { RemoveMemberRequest request = XmlUtil.DeserializeXml(removeMemberRequest); GroupMember? vikingRole = viking.GroupMembership; if (vikingRole == null || vikingRole.Group.GroupID != Guid.Parse(request.GroupID)) return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.Error }); if (vikingRole.UserRole < UserRole.Elder) return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.UserHasNoPermission }); GroupMember? targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid == Guid.Parse(request.RemoveUserID)); if (targetRole == null) return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.UserNotAMemberOfTheGroup }); if (targetRole.UserRole >= vikingRole.UserRole) return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.InvalidParameters }); vikingRole.Group.Vikings.Remove(targetRole); if (!vikingRole.Group.Vikings.Any()) ctx.Groups.Remove(vikingRole.Group); ctx.SaveChanges(); return Ok(new RemoveMemberResult { Success = true, Status = RemoveMemberStatus.Success }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/AuthorizeJoinRequest")] [VikingSession] public IActionResult AuthorizeJoinRequest(Viking viking, [FromForm] string apiKey, [FromForm] string authorizeJoinRequest) { uint gameId = ClientVersion.GetGameID(apiKey); AuthorizeJoinRequest request = XmlUtil.DeserializeXml(authorizeJoinRequest); GroupMember? vikingRole = viking.GroupMembership; if (vikingRole == null || vikingRole.Group.GroupID != Guid.Parse(request.GroupID)) return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.ApproverNotInThisGroup }); if (vikingRole.UserRole < UserRole.Elder) return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.ApproverHasNoPermission }); Viking? target = ctx.Vikings.FirstOrDefault(v => v.Uid == Guid.Parse(request.JoineeID)); if (target == null) return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.Error }); GroupMember? existing = target.GroupMembership; if (existing != null) { return Ok(new AuthorizeJoinResult { Success = false, Status = existing.Group == vikingRole.Group ? AuthorizeJoinStatus.UserAlreadyMemberOfTheGroup : AuthorizeJoinStatus.UserHasNoJoinRequest }); } if (vikingRole.Group.Vikings.Count >= vikingRole.Group.MaxMemberLimit) return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.GroupIsFull }); if (request.Approved) { GroupMember joinee = new GroupMember { Viking = target, Group = vikingRole.Group, UserRole = UserRole.Member, JoinDate = DateTime.UtcNow }; vikingRole.Group.Vikings.Add(joinee); vikingRole.Group.LastActiveTime = joinee.JoinDate; } GroupJoinRequest? joinRequest = ctx.GroupJoinRequests.Find(target.Id, vikingRole.GroupID); if (joinRequest != null) ctx.GroupJoinRequests.Remove(joinRequest); ctx.SaveChanges(); return Ok(new AuthorizeJoinResult { Success = true, Status = AuthorizeJoinStatus.Success }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/AssignRole")] [VikingSession] public IActionResult AssignRole(Viking viking, [FromForm] string assignRoleRequest) { AssignRoleRequest request = XmlUtil.DeserializeXml(assignRoleRequest); GroupMember? vikingRole = viking.GroupMembership; if (vikingRole == null || vikingRole.Group.GroupID != Guid.Parse(request.GroupID)) return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverNotMemberOfTheGroup }); if (vikingRole.UserRole < UserRole.Elder) return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission }); GroupMember? targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid == Guid.Parse(request.MemberID)); if (targetRole == null) return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.MemberNotPartOfTheGroup }); if (targetRole.UserRole == request.NewRole) return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.MemberAlreadyInTheRole }); if (vikingRole.UserRole == UserRole.Leader) { // Disallow leader from simply demoting themself. if (viking == targetRole.Viking) return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission }); } else if (viking != targetRole.Viking || request.NewRole > vikingRole.UserRole) { // Disallow Elders from promoting themselves to leader, or promoting anyone else to elder, but allow them to demote themselves. return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission }); } targetRole.UserRole = request.NewRole; if (request.NewRole == UserRole.Leader) vikingRole.UserRole = UserRole.Elder; // This is the only way a leader can demote themself. ctx.SaveChanges(); return Ok(new AssignRoleResult { Success = true, Status = AssignRoleStatus.Success }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/GetPendingJoinRequest")] [VikingSession] public IActionResult GetPendingJoinRequests(Viking viking, [FromForm] string getPendingJoinRequest) { GetPendingJoinRequest request = XmlUtil.DeserializeXml(getPendingJoinRequest); GroupMember? vikingRole = viking.GroupMembership; if (vikingRole?.Group.GroupID == Guid.Parse(request.GroupID) && vikingRole?.UserRole >= UserRole.Elder) { return Ok(new GetPendingJoinResult { Success = true, Requests = vikingRole.Group.JoinRequests .Select(r => { PendingJoinRequest req = new PendingJoinRequest { UserID = r.Viking.Uid.ToString(), GroupID = vikingRole.Group.GroupID.ToString(), StatusID = GroupJoinRequestStatus.Pending, Message = r.Message ?? "Hello! Please invite me to your Clan!" }; req.FromUserID = req.UserID; return req; }).ToArray() }); } return Ok(new GetPendingJoinResult { Success = false }); } [HttpPost] [Produces("application/xml")] [Route("GroupWebService.asmx/GetGroupsByUserID")] public Schema.Group[] GetGroupsByUserID([FromForm] string apiKey, [FromForm] Guid userId) { uint gameId = ClientVersion.GetGameID(apiKey); Viking? viking = ctx.Vikings.FirstOrDefault(v => v.Uid == userId); if (viking == null) return []; Model.Group? group = viking.GroupMembership.Group; return [ new Schema.Group { GroupID = group.GroupID.ToString(), Name = group.Name, Color = group.Color, Logo = group.Logo, MemberLimit = group.MaxMemberLimit } ]; } [HttpPost] [Produces("application/xml")] [Route("GroupWebService.asmx/GetGroupsByGroupType")] public Schema.Group[] GetGroupsByGroupType([FromForm] string apiKey, [FromForm] string groupType) { uint gameId = ClientVersion.GetGameID(apiKey); List groups = new List(); foreach (Model.Group group in ctx.Groups) { if (group.GameID == gameId && group.Type.ToString() == groupType) groups.Add(new Schema.Group { GroupID = group.GroupID.ToString(), Name = group.Name, Color = group.Color, Logo = group.Logo, Type = group.Type, MemberLimit = group.MaxMemberLimit }); } return groups.ToArray(); } [HttpPost] [Produces("application/xml")] [Route("GroupWebService.asmx/GetMembersByGroupID")] public Schema.GroupMember[] GetMembersByGroupID([FromForm] string groupID) { Guid parsedGroupID = Guid.Parse(groupID); Model.Group? group = ctx.Groups.FirstOrDefault( g => g.GroupID == parsedGroupID ); if (group == null) return []; return group.Vikings.Select(v => new Schema.GroupMember { DisplayName = group.GameID == ClientVersion.MB && v.Viking.AvatarSerialized != null ? XmlUtil.DeserializeXml(v.Viking.AvatarSerialized).DisplayName : v.Viking.Name, UserID = v.Viking.Uid.ToString(), JoinDate = v.JoinDate, RoleID = (int) v.UserRole, Online = false, // There's no way to check this. Rank = 0, GroupID = group.GroupID.ToString(), Points = group.Points }).ToArray(); } }