示例#1
0
        public static async void NotifyCategoryChange(ServerPlanetCategory category)
        {
            string json = JsonSerializer.Serialize(category);

            // Send update to members
            await Current.Clients.Group($"p-{category.Planet_Id}").SendAsync("CategoryUpdate", json);
        }
示例#2
0
        public async Task <CategoryPermissionsNode> GetCategoryNodeAsync(ServerPlanetCategory category, ValourDB db = null)
        {
            bool createdb = false;

            if (db == null)
            {
                db = new ValourDB(ValourDB.DBOptions);
            }

            var res = await db.CategoryPermissionsNodes.FirstOrDefaultAsync(x => x.Category_Id == category.Id &&
                                                                            x.Role_Id == Id);

            if (createdb)
            {
                await db.DisposeAsync();
            }

            return(res);
        }
示例#3
0
        private static async Task InsertItem(HttpContext ctx, ValourDB db, ulong category_id,
                                             [FromHeader] string authorization)
        {
            AuthToken auth = await ServerAuthToken.TryAuthorize(authorization, db);

            if (auth == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync($"Token is invalid [token: {authorization}]");

                return;
            }

            ServerPlanetCategory category = await db.PlanetCategories.Include(x => x.Planet)
                                            .ThenInclude(x => x.Members.Where(x => x.User_Id == auth.User_Id))
                                            .FirstOrDefaultAsync(x => x.Id == category_id);

            if (category == null)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Category not found [id: {category_id}]");

                return;
            }

            var member = category.Planet.Members.FirstOrDefault();

            if (member == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member not found");

                return;
            }

            if (!await category.HasPermission(member, CategoryPermissions.View, db))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member lacks CategoryPermissions.View");

                return;
            }

            if (!auth.HasScope(UserPermissions.PlanetManagement))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync($"Token lacks UserPermissions.PlanetManagement");

                return;
            }

            if (!await category.HasPermission(member, CategoryPermissions.ManageCategory, db))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member lacks CategoryPermissions.ManageCategory");

                return;
            }

            ChannelListItem in_item = await JsonSerializer.DeserializeAsync <ChannelListItem>(ctx.Request.Body);

            if (in_item == null || in_item.Planet_Id == 0)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync("Include item data.");

                return;
            }

            IServerChannelListItem item = await IServerChannelListItem.FindAsync(in_item.ItemType, in_item.Id, db);

            if (item == null)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Item not found [id: {in_item.Id}]");

                return;
            }

            ServerPlanet item_planet = await db.Planets.FindAsync(item.Planet_Id);

            if (item_planet == null)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Item planet not found [id: {in_item.Planet_Id}]");

                return;
            }

            if (item_planet.Id != category.Planet_Id)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Item belongs to different planet");

                return;
            }

            if (item.Parent_Id == category.Id)
            {
                ctx.Response.StatusCode = 200;
                await ctx.Response.WriteAsync($"No change");

                return;
            }

            // Ensure that if this is a category, it is not going into a category that contains itself!
            if (item.ItemType == ItemType.Category)
            {
                ulong?parent_id = category.Parent_Id;

                while (parent_id != null)
                {
                    // Recursion is a nono
                    if (parent_id == item.Id)
                    {
                        ctx.Response.StatusCode = 400;
                        await ctx.Response.WriteAsync("Operation would result in recursion.");

                        return;
                    }

                    parent_id = (await db.PlanetCategories.FindAsync(parent_id)).Parent_Id;
                }
            }

            item.Parent_Id = category.Id;
            item.Position  = in_item.Position;

            db.Update(item);
            await db.SaveChangesAsync();

            item.NotifyClientsChange();

            ctx.Response.StatusCode = 200;
            await ctx.Response.WriteAsync("Success");

            return;
        }
示例#4
0
        private static async Task SetChildOrder(HttpContext ctx, ValourDB db, ulong category_id,
                                                [FromHeader] string authorization)
        {
            AuthToken auth = await ServerAuthToken.TryAuthorize(authorization, db);

            if (auth == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync($"Token is invalid [token: {authorization}]");

                return;
            }

            ServerPlanetCategory category = await db.PlanetCategories.Include(x => x.Planet)
                                            .ThenInclude(x => x.Members.Where(x => x.User_Id == auth.User_Id))
                                            .FirstOrDefaultAsync(x => x.Id == category_id);

            if (category == null)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Category not found [id: {category_id}]");

                return;
            }

            var member = category.Planet.Members.FirstOrDefault();

            if (member == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member not found");

                return;
            }

            if (!await category.HasPermission(member, CategoryPermissions.View, db))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member lacks CategoryPermissions.View");

                return;
            }

            if (!auth.HasScope(UserPermissions.PlanetManagement))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync($"Token lacks UserPermissions.PlanetManagement");

                return;
            }

            if (!await category.HasPermission(member, CategoryPermissions.ManageCategory, db))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member lacks CategoryPermissions.ManageCategory");

                return;
            }

            string body = await ctx.Request.ReadBodyStringAsync();

            if (string.IsNullOrEmpty(body))
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync("Include order data.");

                return;
            }

            List <CategoryContentData> orderData = JsonSerializer.Deserialize <List <CategoryContentData> >(body);

            if (orderData == null || orderData.Count == 0)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync("Include order data.");

                return;
            }

            List <IServerChannelListItem> changed = new List <IServerChannelListItem>();

            foreach (CategoryContentData order in orderData)
            {
                IServerChannelListItem item = await IServerChannelListItem.FindAsync(order.ItemType, order.Id, db);

                if (item == null)
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync($"Item with id {order.Id} not found");

                    return;
                }

                if (item.Planet_Id != category.Planet_Id)
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync($"Item with id {order.Id} belongs to wrong planet {item.Planet_Id}");

                    return;
                }

                // Only act if there is a difference
                if (item.Parent_Id != category_id || item.Position != order.Position)
                {
                    // Prevent putting an item inside of itself
                    if (item.Id != category_id)
                    {
                        item.Parent_Id = category_id;
                        item.Position  = order.Position;
                        db.Update(item);
                        changed.Add(item);
                    }
                }
            }

            // If all is successful, save and send updates
            foreach (var item in changed)
            {
                // Send update to clients
                item.NotifyClientsChange();
            }

            await db.SaveChangesAsync();

            ctx.Response.StatusCode = 200;
            await ctx.Response.WriteAsync("Success");

            return;
        }
示例#5
0
        private static async Task GetChildren(HttpContext ctx, ValourDB db, ulong category_id,
                                              [FromHeader] string authorization)
        {
            AuthToken auth = await ServerAuthToken.TryAuthorize(authorization, db);

            if (auth == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync($"Token is invalid [token: {authorization}]");

                return;
            }

            ServerPlanetCategory category = await db.PlanetCategories.Include(x => x.Planet)
                                            .ThenInclude(x => x.Members.Where(x => x.User_Id == auth.User_Id))
                                            .Include(x => x.Planet)
                                            .ThenInclude(x => x.ChatChannels)
                                            .Include(x => x.Planet)
                                            .ThenInclude(x => x.Categories)
                                            .FirstOrDefaultAsync(x => x.Id == category_id);

            if (category == null)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Category not found [id: {category_id}]");

                return;
            }

            var member = category.Planet.Members.FirstOrDefault();

            if (member == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member not found");

                return;
            }

            if (!await category.HasPermission(member, CategoryPermissions.View, db))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member lacks CategoryPermissions.View");

                return;
            }

            List <ChannelListItem> children = new List <ChannelListItem>();

            foreach (var channel in category.Planet.ChatChannels)
            {
                if (await channel.HasPermission(member, ChatChannelPermissions.View, db))
                {
                    children.Add(channel);
                }
            }

            foreach (var cat in category.Planet.Categories)
            {
                if (await cat.HasPermission(member, CategoryPermissions.View, db))
                {
                    children.Add(cat);
                }
            }

            ctx.Response.StatusCode = 200;
            await ctx.Response.WriteAsJsonAsync(children);

            return;
        }
示例#6
0
        private static async Task Category(HttpContext ctx, ValourDB db, ulong category_id,
                                           [FromHeader] string authorization)
        {
            AuthToken auth = await ServerAuthToken.TryAuthorize(authorization, db);

            if (auth == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync($"Token is invalid [token: {authorization}]");

                return;
            }

            ServerPlanetCategory category = await db.PlanetCategories.Include(x => x.Planet)
                                            .ThenInclude(x => x.Members.Where(x => x.User_Id == auth.User_Id))
                                            .FirstOrDefaultAsync(x => x.Id == category_id);

            if (category == null)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Category not found [id: {category_id}]");

                return;
            }

            var member = category.Planet.Members.FirstOrDefault();

            if (member == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member not found");

                return;
            }

            if (!await category.HasPermission(member, CategoryPermissions.View, db))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member lacks CategoryPermissions.View");

                return;
            }

            switch (ctx.Request.Method)
            {
            case "GET":
            {
                ctx.Response.StatusCode = 200;
                await ctx.Response.WriteAsJsonAsync((PlanetCategory)category);

                return;
            }

            case "DELETE":
            {
                if (!auth.HasScope(UserPermissions.PlanetManagement))
                {
                    ctx.Response.StatusCode = 401;
                    await ctx.Response.WriteAsync($"Token lacks UserPermissions.PlanetManagement");

                    return;
                }

                if (!await category.HasPermission(member, CategoryPermissions.ManageCategory, db))
                {
                    ctx.Response.StatusCode = 401;
                    await ctx.Response.WriteAsync("Member lacks CategoryPermissions.ManageCategory");

                    return;
                }

                TaskResult result = await category.TryDeleteAsync(db);

                if (!result.Success)
                {
                    ctx.Response.StatusCode = 400;
                }
                else
                {
                    ctx.Response.StatusCode = 200;
                }

                await ctx.Response.WriteAsync(result.Message);

                return;
            }
            }
        }
示例#7
0
        private static async Task ParentId(HttpContext ctx, ValourDB db, ulong category_id, int?position,
                                           [FromHeader] string authorization)
        {
            if (position == null)
            {
                position = -1;
            }

            AuthToken auth = await ServerAuthToken.TryAuthorize(authorization, db);

            if (auth == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync($"Token is invalid [token: {authorization}]");

                return;
            }

            ServerPlanetCategory category = await db.PlanetCategories.Include(x => x.Planet)
                                            .ThenInclude(x => x.Members.Where(x => x.User_Id == auth.User_Id))
                                            .FirstOrDefaultAsync(x => x.Id == category_id);

            if (category == null)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Category not found [id: {category_id}]");

                return;
            }

            var member = category.Planet.Members.FirstOrDefault();

            if (member == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member not found");

                return;
            }

            if (!await category.HasPermission(member, CategoryPermissions.View, db))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member lacks CategoryPermissions.View");

                return;
            }

            switch (ctx.Request.Method)
            {
            case "GET":
            {
                ctx.Response.StatusCode = 200;
                await ctx.Response.WriteAsJsonAsync(category.Parent_Id);

                return;
            }

            case "PUT":
            {
                if (!auth.HasScope(UserPermissions.PlanetManagement))
                {
                    ctx.Response.StatusCode = 401;
                    await ctx.Response.WriteAsync($"Token lacks UserPermissions.PlanetManagement");

                    return;
                }

                string body = await ctx.Request.ReadBodyStringAsync();

                ulong?parent_id;

                if (body == "null" || body == "0" || body == "none" || string.IsNullOrWhiteSpace(body))
                {
                    parent_id = null;
                }
                else
                {
                    ulong parsed_ul;
                    bool  parsed = ulong.TryParse(body, out parsed_ul);

                    parent_id = parsed_ul;

                    if (!parsed)
                    {
                        ctx.Response.StatusCode = 400;
                        await ctx.Response.WriteAsync("Given value is invalid");

                        return;
                    }
                }

                TaskResult <int> result = await category.TrySetParentAsync(member, parent_id, (int)position, db);

                ctx.Response.StatusCode = result.Data;
                await ctx.Response.WriteAsync(result.Message);

                return;
            }
            }
        }
示例#8
0
        private static async Task CreateCategory(HttpContext ctx, ValourDB db,
                                                 [FromHeader] string authorization)
        {
            ServerPlanetCategory category_data = await JsonSerializer.DeserializeAsync <ServerPlanetCategory>(ctx.Request.Body);

            if (category_data == null)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync("Please include category in body");

                return;
            }

            if (string.IsNullOrWhiteSpace(category_data.Name))
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync("Please include a category name");

                return;
            }

            // Request parameter validation //

            TaskResult name_valid = ServerPlanetCategory.ValidateName(category_data.Name);

            if (!name_valid.Success)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync($"Name is not valid [name: {category_data.Name}]");

                return;
            }

            // Request authorization //

            AuthToken auth = await ServerAuthToken.TryAuthorize(authorization, db);

            if (!auth.HasScope(UserPermissions.PlanetManagement))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Token lacks UserPermissions.PlanetManagement scope");

                return;
            }

            ServerPlanet planet = await db.Planets.Include(x => x.Members.Where(x => x.User_Id == auth.User_Id))
                                  .FirstOrDefaultAsync(x => x.Id == category_data.Planet_Id);

            var member = planet.Members.FirstOrDefault();

            if (!await planet.HasPermissionAsync(member, PlanetPermissions.ManageChannels, db))
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync("Member lacks PlanetPermissions.ManageChannels node");

                return;
            }

            // Ensure parent category exists

            ulong?parent_id = null;

            ServerPlanetCategory parent = await db.PlanetCategories.FindAsync(category_data.Parent_Id);

            ushort child_count = 0;

            if (parent != null)
            {
                parent_id = parent.Id;

                if (parent.Planet_Id != planet.Id)
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync("Parent id does not match planet");

                    return;
                }

                child_count += (ushort)await db.PlanetChatChannels.CountAsync(x => x.Parent_Id == parent_id);

                child_count += (ushort)await db.PlanetCategories.CountAsync(x => x.Parent_Id == parent_id);
            }

            // Request action //

            // Creates the category

            ServerPlanetCategory category = new ServerPlanetCategory()
            {
                Id          = IdManager.Generate(),
                Name        = category_data.Name,
                Planet_Id   = category_data.Planet_Id,
                Parent_Id   = category_data.Parent_Id,
                Description = category_data.Description,
                Position    = child_count
            };

            // Add channel to database
            await db.PlanetCategories.AddAsync(category);

            // Save changes to DB
            await db.SaveChangesAsync();

            // Send channel refresh
            PlanetHub.NotifyCategoryChange(category);

            ctx.Response.StatusCode = 201;
            await ctx.Response.WriteAsync(category.Id.ToString());
        }
示例#9
0
        private static async Task Create(HttpContext ctx, ValourDB db,
                                         [FromHeader] string authorization, [Required] string name,
                                         [Required] string image_url)
        {
            ServerAuthToken auth = await ServerAuthToken.TryAuthorize(authorization, db);

            if (auth == null)
            {
                ctx.Response.StatusCode = 401;
                await ctx.Response.WriteAsync($"Token is invalid [token: {authorization}]");

                return;
            }

            TaskResult nameValid = ServerPlanet.ValidateName(name);

            if (!nameValid.Success)
            {
                ctx.Response.StatusCode = 400;
                await ctx.Response.WriteAsync(nameValid.Message);

                return;
            }

            ServerUser user = await db.Users.FindAsync(auth.User_Id);

            if (!user.Valour_Staff)
            {
                var owned_planets = await db.Planets.CountAsync(x => x.Owner_Id == user.Id);

                if (owned_planets > MAX_OWNED_PLANETS)
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync("Max owned planets reached");

                    return;
                }
            }

            // Image handling via proxy
            ProxyResponse proxyResponse = await MPSManager.GetProxy(image_url);

            bool is_media = MPSManager.Media_Types.Contains(proxyResponse.Item.Mime_Type);

            if (proxyResponse.Item == null || !is_media)
            {
                image_url = "https://valour.gg/image.png";
            }
            else
            {
                image_url = proxyResponse.Item.Url;
            }

            ulong planet_id = IdManager.Generate();

            // Create general category
            ServerPlanetCategory category = new ServerPlanetCategory()
            {
                Id          = IdManager.Generate(),
                Name        = "General",
                Parent_Id   = null,
                Planet_Id   = planet_id,
                Description = "General category",
                Position    = 0
            };

            // Create general channel
            ServerPlanetChatChannel channel = new ServerPlanetChatChannel()
            {
                Id            = IdManager.Generate(),
                Planet_Id     = planet_id,
                Name          = "General",
                Message_Count = 0,
                Description   = "General chat channel",
                Parent_Id     = category.Id
            };

            // Create default role
            ServerPlanetRole defaultRole = new ServerPlanetRole()
            {
                Id          = IdManager.Generate(),
                Planet_Id   = planet_id,
                Position    = uint.MaxValue,
                Color_Blue  = 255,
                Color_Green = 255,
                Color_Red   = 255,
                Name        = "@everyone"
            };

            ServerPlanet planet = new ServerPlanet()
            {
                Id              = planet_id,
                Name            = name,
                Member_Count    = 1,
                Description     = "A Valour server.",
                Image_Url       = image_url,
                Public          = true,
                Owner_Id        = user.Id,
                Default_Role_Id = defaultRole.Id,
                Main_Channel_Id = channel.Id
            };

            // Add planet to database
            await db.Planets.AddAsync(planet);

            await db.SaveChangesAsync(); // We must do this first to prevent foreign key errors

            // Add category to database
            await db.PlanetCategories.AddAsync(category);

            // Add channel to database
            await db.PlanetChatChannels.AddAsync(channel);

            // Add default role to database
            await db.PlanetRoles.AddAsync(defaultRole);

            // Save changes
            await db.SaveChangesAsync();

            // Add owner to planet
            await planet.AddMemberAsync(user, db);

            ctx.Response.StatusCode = 200;
            await ctx.Response.WriteAsJsonAsync(planet.Id);
        }
示例#10
0
        private static async Task Channel(HttpContext ctx, ValourDB db, ulong channel_id,
                                          [FromHeader] string authorization)
        {
            AuthToken auth = await ServerAuthToken.TryAuthorize(authorization, db);

            if (auth == null)
            {
                await TokenInvalid(ctx); return;
            }

            switch (ctx.Request.Method)
            {
            case "GET":
            {
                ServerPlanetChatChannel channel = await db.PlanetChatChannels.Include(x => x.Planet)
                                                  .ThenInclude(x => x.Members.Where(x => x.User_Id == auth.User_Id))
                                                  .FirstOrDefaultAsync(x => x.Id == channel_id);

                if (channel == null)
                {
                    await NotFound("Channel not found", ctx); return;
                }

                var member = channel.Planet.Members.FirstOrDefault();

                if (member == null)
                {
                    await Unauthorized("Member not found", ctx); return;
                }

                if (!await channel.HasPermission(member, ChatChannelPermissions.View, db))
                {
                    await Unauthorized("Member lacks ChatChannelPermissions.View", ctx); return;
                }

                ctx.Response.StatusCode = 200;
                await ctx.Response.WriteAsJsonAsync((PlanetChatChannel)channel);

                return;
            }

            case "DELETE":
            {
                ServerPlanetChatChannel channel = await db.PlanetChatChannels.Include(x => x.Planet)
                                                  .ThenInclude(x => x.Members.Where(x => x.User_Id == auth.User_Id))
                                                  .FirstOrDefaultAsync(x => x.Id == channel_id);

                if (channel == null)
                {
                    await NotFound("Channel not found", ctx); return;
                }

                var member = channel.Planet.Members.FirstOrDefault();

                if (!auth.HasScope(UserPermissions.PlanetManagement))
                {
                    await Unauthorized("Token lacks UserPermissions.PlanetManagement", ctx); return;
                }

                TaskResult <int> result = await channel.TryDeleteAsync(member, db);

                ctx.Response.StatusCode = result.Data;
                await ctx.Response.WriteAsync(result.Message);

                return;
            }

            case "POST":
            {
                ServerPlanetChatChannel channel_data =
                    JsonSerializer.Deserialize <ServerPlanetChatChannel>(ctx.Request.Body);

                if (channel_data == null)
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync("Please include channel in body");

                    return;
                }

                if (string.IsNullOrWhiteSpace(channel_data.Name))
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync("Please include a channel name");

                    return;
                }

                // Request parameter validation //

                TaskResult name_valid = ServerPlanetChatChannel.ValidateName(channel_data.Name);

                if (!name_valid.Success)
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync($"Name is not valid [name: {channel_data.Name}]");

                    return;
                }

                // Request authorization //

                if (!auth.HasScope(UserPermissions.PlanetManagement))
                {
                    ctx.Response.StatusCode = 401;
                    await ctx.Response.WriteAsync("Token lacks UserPermissions.PlanetManagement scope");

                    return;
                }

                ServerPlanetMember member = await db.PlanetMembers
                                            .Include(x => x.Planet)
                                            .FirstOrDefaultAsync(x => x.Planet_Id == channel_data.Planet_Id && x.User_Id == auth.User_Id);

                if (!await member.HasPermissionAsync(PlanetPermissions.ManageChannels, db))
                {
                    ctx.Response.StatusCode = 401;
                    await ctx.Response.WriteAsync("Member lacks PlanetPermissions.ManageChannels node");

                    return;
                }

                // Ensure parent category exists

                ServerPlanetCategory parent = await db.PlanetCategories.FindAsync(channel_data.Parent_Id);

                if (parent == null)
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync("Could not find parent");

                    return;
                }

                if (parent.Planet_Id != member.Planet.Id)
                {
                    ctx.Response.StatusCode = 400;
                    await ctx.Response.WriteAsync("Parent id does not match planet");

                    return;
                }

                // Request action //

                // Creates the channel

                ushort child_count = 0;

                child_count += (ushort)await db.PlanetChatChannels.CountAsync(x => x.Parent_Id == channel_data.Parent_Id);

                child_count += (ushort)await db.PlanetCategories.CountAsync(x => x.Parent_Id == channel_data.Parent_Id);

                ServerPlanetChatChannel channel = new ServerPlanetChatChannel()
                {
                    Id            = IdManager.Generate(),
                    Name          = channel_data.Name,
                    Planet_Id     = channel_data.Planet_Id,
                    Parent_Id     = channel_data.Parent_Id,
                    Message_Count = 0,
                    Description   = channel_data.Description,
                    Position      = child_count
                };

                // Add channel to database
                await db.PlanetChatChannels.AddAsync(channel);

                // Save changes to DB
                await db.SaveChangesAsync();

                // Send channel refresh
                PlanetHub.NotifyChatChannelChange(channel);

                ctx.Response.StatusCode = 201;
                await ctx.Response.WriteAsync(channel.Id.ToString());

                return;
            }
            }
        }