using Microsoft.AspNetCore.Mvc; using sodoff.Attributes; using sodoff.Model; using sodoff.Schema; using sodoff.Util; namespace sodoff.Controllers.Common; public class GroupController : Controller { private static readonly List RolePermissions; static GroupController() { RolePermissions = new List(); for (GroupType type = GroupType.Public; type <= GroupType.Private; type++) { // Anything commented out is not implemented. RolePermissions.Add(new RolePermission { GroupType = type, Role = UserRole.Member, Permissions = [ "Delete Own Msg", //"Post Message" ] }); RolePermissions.Add(new RolePermission { GroupType = type, Role = UserRole.Elder, Permissions = [ //"Invite", "Approve Join Request", //"Post News", //"Delete Own Msg", //"Delete Any Msg", //"Delete News", //"Post Message", "Remove Member" ] }); RolePermissions.Add(new RolePermission { GroupType = type, Role = UserRole.Leader, Permissions = [ //"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" ] }); } } public static readonly Schema.Group EMD_Dragons = new Schema.Group { GroupID = "8e68214a-c801-4759-8461-d01f28484134", Name = "Dragons", Color = "234,57,23", Logo = "RS_DATA/Content/PlayerData/EMD/IcoEMDTeamDragons.png" }; public static readonly Schema.Group EMD_Scorpions = new Schema.Group { GroupID = "db0aa225-2f0e-424c-83a7-73783fe63fef", Name = "Scorpions", Color = "120,183,53", Logo = "RS_DATA/Content/PlayerData/EMD/IcoEMDTeamScorpions.png" }; private readonly DBContext ctx; public GroupController(DBContext ctx) { this.ctx = ctx; bool changed = false; Guid DragonString = new Guid(EMD_Dragons.GroupID); Guid ScorpionString = new Guid(EMD_Scorpions.GroupID); if (!ctx.Groups.Any(g => g.GroupID == DragonString)) { ctx.Groups.Add(new Model.Group { GroupID = DragonString, Name = EMD_Dragons.Name, Color = EMD_Dragons.Color, Logo = EMD_Dragons.Logo, Type = GroupType.System, GameID = ClientVersion.EMD, MaxMemberLimit = int.MaxValue }); changed = true; } if (!ctx.Groups.Any(g => g.GroupID == ScorpionString)) { ctx.Groups.Add(new Model.Group { GroupID = ScorpionString, Name = EMD_Scorpions.Name, Color = EMD_Scorpions.Color, Logo = EMD_Scorpions.Logo, Type = GroupType.System, GameID = ClientVersion.EMD, MaxMemberLimit = int.MaxValue }); changed = true; } if (changed) ctx.SaveChanges(); } [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.GroupRoles.Any(g => g.GameID == gameId)) { 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 (viking.GroupRoles.Any(g => g.Name.Equals(request.Name, StringComparison.InvariantCultureIgnoreCase))) 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.Now, GameID = gameId, MaxMemberLimit = 50, GroupID = Guid.NewGuid(), Vikings = new List() }; ctx.Groups.Add(group); group.Vikings.Add(new GroupViking { 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(); GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(gv => gv.Group.GroupID.ToString() == request.GroupID); if (vikingRole == null) { return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupNotFound }); } else 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 ((request.Name?.Length ?? 0) == 0) request.Name = vikingRole.Name; if ((request.Description?.Length ?? 0) == 0) request.Description = vikingRole.Description; if (request.Name != vikingRole.Name && viking.GroupRoles.Any(g => g.Name.Equals(request.Name, StringComparison.InvariantCultureIgnoreCase))) 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 ((request.Color?.Length ?? 0) > 0) vikingRole.Group.Color = request.Color; if ((request.Logo?.Length ?? 0) > 0) 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) { uint gameId = ClientVersion.GetGameID(apiKey); // Check for loyalty. if (viking.GroupRoles.Any(g => g.GameID == gameId)) { return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.SELF_BLOCKED }); } Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID.ToString().Equals(groupID, StringComparison.OrdinalIgnoreCase)); 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 GroupViking { 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); Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID.ToString() == request.GroupID.ToUpper()); if (group != null) { if (group.Type >= GroupType.Private) { return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.GroupTypeIsNotPublic }); } GroupViking? existing = viking.GroupRoles.FirstOrDefault(g => g.GameID == gameId); 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 // For future implemention, once moderation is possible. }); ctx.SaveChanges(); return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.JoinRequestPending }); } if (group.Vikings.Count < group.MaxMemberLimit) { GroupViking joinee = new GroupViking { Viking = viking, Group = group, UserRole = UserRole.Member, JoinDate = DateTime.Now }; group.Vikings.Add(joinee); group.LastActiveTime = joinee.JoinDate; ctx.SaveChanges(); return Ok(new GroupJoinResult { Success = true, Status = JoinGroupStatus.Success }); } else { return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.GroupIsFull }); } } return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.Error }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/LeaveGroup")] [VikingSession] public IActionResult LeaveGroup(Viking viking, [FromForm] string groupLeaveRequest) { LeaveGroupRequest request = XmlUtil.DeserializeXml(groupLeaveRequest); GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); if (vikingRole != null) { GroupViking? targetRole = null; if (viking.Uid.ToString().Equals(request.UserID, StringComparison.CurrentCultureIgnoreCase)) { targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking == viking); } else if (vikingRole.UserRole >= UserRole.Elder) { targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid.ToString() == request.UserID); } else { return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.Error }); } if (targetRole != null) { 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 }); } return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.UserNotAMemberOfTheGroup }); } return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.Error }); } [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); IEnumerable groups = ctx.Groups; if (request.ForUserID != null) { Viking? target = ctx.Vikings.FirstOrDefault(v => request.ForUserID.ToUpper() == v.Uid.ToString()); if (target == null) return Ok(new GetGroupsResult { Success = false }); groups = target.GroupRoles.Select(gv => gv.Group); } else { groups = groups.Where(g => g.Type == GroupType.Public || g.Type == GroupType.MembersOnly); } if (request.Name != null) { groups = groups.Where(g => g.Name?.Contains(request.Name, StringComparison.InvariantCultureIgnoreCase) == true); } int skip = 0; if (request.PageSize != null) { if ((request.PageNo ?? 0) > 1) skip = (int)((request.PageNo! - 1) * request.PageSize); if (skip > 0) groups = groups.Skip(skip); groups = groups.Take((int) request.PageSize); } groups = groups.Where(g => g.GameID == gameId).OrderByDescending(g => g.Points); 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); GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); if (vikingRole != null) { if (vikingRole.UserRole < UserRole.Elder) { return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.UserHasNoPermission }); } GroupViking? targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid.ToString() == request.RemoveUserID); if (targetRole != null) { 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 }); } return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.UserNotAMemberOfTheGroup }); } return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.Error }); } [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); GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); if (vikingRole != null) { if (vikingRole.UserRole < UserRole.Elder) { return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.ApproverHasNoPermission }); } Viking? target = ctx.Vikings.FirstOrDefault(v => v.Uid.ToString() == request.UserID); if (target != null) { GroupViking? existing = target.GroupRoles.FirstOrDefault(g => g.GameID == gameId); if (existing != null) { if (existing.Group == vikingRole.Group) { return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.UserAlreadyMemberOfTheGroup }); } else { return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.UserHasNoJoinRequest }); } } if (vikingRole.Group.Vikings.Count < vikingRole.Group.MaxMemberLimit) { if (request.Approved) { GroupViking joinee = new GroupViking { Viking = target, Group = vikingRole.Group, UserRole = UserRole.Member, JoinDate = DateTime.Now }; 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 }); } else { return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.GroupIsFull }); } } else { return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.Error }); } } return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.ApproverNotInThisGroup }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/AssignRole")] [VikingSession] public IActionResult AssignRole(Viking viking, [FromForm] string assignRoleRequest) { AssignRoleRequest request = XmlUtil.DeserializeXml(assignRoleRequest); GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); if (vikingRole != null) { if (vikingRole.UserRole < UserRole.Elder) { return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission }); } GroupViking? targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid.ToString() == request.MemberID); if (targetRole != null) { 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 }); } else { return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.MemberNotPartOfTheGroup }); } } return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverNotMemberOfTheGroup }); } [HttpPost] [Produces("application/xml")] [Route("V2/GroupWebService.asmx/GetPendingJoinRequest")] [VikingSession] public IActionResult GetPendingJoinRequests(Viking viking, [FromForm] string getPendingJoinRequest) { GetPendingJoinRequest request = XmlUtil.DeserializeXml(getPendingJoinRequest); GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); if (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 Crew!" // Defualt from Math Blaster btw }; 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] string userId) { uint gameId = ClientVersion.GetGameID(apiKey); Viking? viking = ctx.Vikings.FirstOrDefault(v => v.Uid.ToString() == userId); if (viking == null) return []; return viking.GroupRoles.Where(gv => gv.GameID == gameId).Select(gv => new Schema.Group { GroupID = gv.Group.GroupID.ToString(), Name = gv.Name, Color = gv.Color, Logo = gv.Logo, MemberLimit = gv.Group.MaxMemberLimit }).ToArray(); } [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 GroupMember[] GetMembersByGroupID([FromForm] string groupID) { groupID = groupID.ToUpper(); Model.Group? group = ctx.Groups.FirstOrDefault( g => g.GroupID.ToString() == groupID ); if (group == null) return []; return group.Vikings.Select(v => new GroupMember{ DisplayName = 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(); } }