diff --git a/.gitignore b/.gitignore index 2ba771e..c9e9eda 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,5 @@ attachments/* .DS_Store .vscode +.idea +*.iml diff --git a/Pipfile.lock b/Pipfile.lock index 787e283..a389bd0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -208,10 +208,10 @@ }, "hypercorn": { "hashes": [ - "sha256:9ab2d6b686e5a9a455433144a269d3004ad0aa079d15ed291554858a9b8dbd38", - "sha256:9afb874f8332645a00b77311d1975ac6690fe37e3c268d9d65f5250a3134ee29" + "sha256:0562810a44f3e5aa0b28d42adacbbf7185f2975bb305733e6de71d6a927c8d7b", + "sha256:3e9ee14deb34215907364adb62694b36abc471b8eaaf22d812eb85757e5479ad" ], - "version": "==0.6.0" + "version": "==0.7.0" }, "hyperframe": { "hashes": [ @@ -318,35 +318,52 @@ }, "pillow": { "hashes": [ - "sha256:15c056bfa284c30a7f265a41ac4cbbc93bdbfc0dfe0613b9cb8a8581b51a9e55", - "sha256:1a4e06ba4f74494ea0c58c24de2bb752818e9d504474ec95b0aa94f6b0a7e479", - "sha256:1c3c707c76be43c9e99cb7e3d5f1bee1c8e5be8b8a2a5eeee665efbf8ddde91a", - "sha256:1fd0b290203e3b0882d9605d807b03c0f47e3440f97824586c173eca0aadd99d", - "sha256:24114e4a6e1870c5a24b1da8f60d0ba77a0b4027907860188ea82bd3508c80eb", - "sha256:258d886a49b6b058cd7abb0ab4b2b85ce78669a857398e83e8b8e28b317b5abb", - "sha256:33c79b6dd6bc7f65079ab9ca5bebffb5f5d1141c689c9c6a7855776d1b09b7e8", - "sha256:367385fc797b2c31564c427430c7a8630db1a00bd040555dfc1d5c52e39fcd72", - "sha256:3c1884ff078fb8bf5f63d7d86921838b82ed4a7d0c027add773c2f38b3168754", - "sha256:44e5240e8f4f8861d748f2a58b3f04daadab5e22bfec896bf5434745f788f33f", - "sha256:46aa988e15f3ea72dddd81afe3839437b755fffddb5e173886f11460be909dce", - "sha256:74d90d499c9c736d52dd6d9b7221af5665b9c04f1767e35f5dd8694324bd4601", - "sha256:809c0a2ce9032cbcd7b5313f71af4bdc5c8c771cb86eb7559afd954cab82ebb5", - "sha256:85d1ef2cdafd5507c4221d201aaf62fc9276f8b0f71bd3933363e62a33abc734", - "sha256:8c3889c7681af77ecfa4431cd42a2885d093ecb811e81fbe5e203abc07e0995b", - "sha256:9218d81b9fca98d2c47d35d688a0cea0c42fd473159dfd5612dcb0483c63e40b", - "sha256:9aa4f3827992288edd37c9df345783a69ef58bd20cc02e64b36e44bcd157bbf1", - "sha256:9d80f44137a70b6f84c750d11019a3419f409c944526a95219bea0ac31f4dd91", - "sha256:b7ebd36128a2fe93991293f997e44be9286503c7530ace6a55b938b20be288d8", - "sha256:c4c78e2c71c257c136cdd43869fd3d5e34fc2162dc22e4a5406b0ebe86958239", - "sha256:c6a842537f887be1fe115d8abb5daa9bc8cc124e455ff995830cc785624a97af", - "sha256:cf0a2e040fdf5a6d95f4c286c6ef1df6b36c218b528c8a9158ec2452a804b9b8", - "sha256:cfd28aad6fc61f7a5d4ee556a997dc6e5555d9381d1390c00ecaf984d57e4232", - "sha256:dca5660e25932771460d4688ccbb515677caaf8595f3f3240ec16c117deff89a", - "sha256:de7aedc85918c2f887886442e50f52c1b93545606317956d65f342bd81cb4fc3", - "sha256:e6c0bbf8e277b74196e3140c35f9a1ae3eafd818f7f2d3a15819c49135d6c062" + "sha256:0804f77cb1e9b6dbd37601cee11283bba39a8d44b9ddb053400c58e0c0d7d9de", + "sha256:0ab7c5b5d04691bcbd570658667dd1e21ca311c62dcfd315ad2255b1cd37f64f", + "sha256:0b3e6cf3ea1f8cecd625f1420b931c83ce74f00c29a0ff1ce4385f99900ac7c4", + "sha256:0c6ce6ae03a50b0306a683696234b8bc88c5b292d4181ae365b89bd90250ab08", + "sha256:1454ee7297a81c8308ad61d74c849486efa1badc543453c4b90db0bf99decc1c", + "sha256:23efd7f83f2ad6036e2b9ef27a46df7e333de1ad9087d341d87e12225d0142b2", + "sha256:365c06a45712cd723ec16fa4ceb32ce46ad201eb7bbf6d3c16b063c72b61a3ed", + "sha256:38301fbc0af865baa4752ddae1bb3cbb24b3d8f221bf2850aad96b243306fa03", + "sha256:3aef1af1a91798536bbab35d70d35750bd2884f0832c88aeb2499aa2d1ed4992", + "sha256:3c86051d41d1c8b28b9dde08ac93e73aa842991995b12771b0af28da49086bbf", + "sha256:3fe0ab49537d9330c9bba7f16a5f8b02da615b5c809cdf7124f356a0f182eccd", + "sha256:406c856e0f6fc330322a319457d9ff6162834050cda2cf1eaaaea4b771d01914", + "sha256:45a619d5c1915957449264c81c008934452e3fd3604e36809212300b2a4dab68", + "sha256:49f90f147883a0c3778fd29d3eb169d56416f25758d0f66775db9184debc8010", + "sha256:504f5334bfd974490a86fef3e3b494cd3c332a8a680d2f258ca03388b40ae230", + "sha256:51fe9cfcd32c849c6f36ca293648f279fc5097ca8dd6e518b10df3a6a9a13431", + "sha256:571b5a758baf1cb6a04233fb23d6cf1ca60b31f9f641b1700bfaab1194020555", + "sha256:5ac381e8b1259925287ccc5a87d9cf6322a2dc88ae28a97fe3e196385288413f", + "sha256:6052a9e9af4a9a2cc01da4bbee81d42d33feca2bde247c4916d8274b12bb31a4", + "sha256:6153db744a743c0c8c91b8e3b9d40e0b13a5d31dbf8a12748c6d9bfd3ddc01ad", + "sha256:6fd63afd14a16f5d6b408f623cc2142917a1f92855f0df997e09a49f0341be8a", + "sha256:70acbcaba2a638923c2d337e0edea210505708d7859b87c2bd81e8f9902ae826", + "sha256:70b1594d56ed32d56ed21a7fbb2a5c6fd7446cdb7b21e749c9791eac3a64d9e4", + "sha256:76638865c83b1bb33bcac2a61ce4d13c17dba2204969dedb9ab60ef62bede686", + "sha256:7b2ec162c87fc496aa568258ac88631a2ce0acfe681a9af40842fc55deaedc99", + "sha256:7b403ea842b70c4fa0a4969a5d8d86e932c941095b7cda077ea68f7b98ead30b", + "sha256:7be698a28175eae5354da94f5f3dc787d5efae6aca7ad1f286a781afde6a27dd", + "sha256:7cee2cef07c8d76894ebefc54e4bb707dfc7f258ad155bd61d87f6cd487a70ff", + "sha256:7d16d4498f8b374fc625c4037742fbdd7f9ac383fd50b06f4df00c81ef60e829", + "sha256:82840783842b27933cc6388800cb547f31caf436f7e23384d456bdf5fc8dfe49", + "sha256:8755e600b33f4e8c76a590b42acc35d24f4dc801a5868519ce569b9462d77598", + "sha256:9159285ab4030c6f85e001468cb5886de05e6bd9304e9e7d46b983f7d2fad0cc", + "sha256:b50bc1780681b127e28f0075dfb81d6135c3a293e0c1d0211133c75e2179b6c0", + "sha256:b5aa19f1da16b4f5e47b6930053f08cba77ceccaed68748061b0ec24860e510c", + "sha256:bd0582f831ad5bcad6ca001deba4568573a4675437db17c4031939156ff339fa", + "sha256:cdd53acd3afb9878a2289a1b55807871f9877c81174ae0d3763e52f907131d25", + "sha256:cfd40d8a4b59f7567620410f966bb1f32dc555b2b19f82a91b147fac296f645c", + "sha256:e150c5aed6e67321edc6893faa6701581ca2d393472f39142a00e551bcd249a5", + "sha256:e3ae410089de680e8f84c68b755b42bc42c0ceb8c03dbea88a5099747091d38e", + "sha256:e403b37c6a253ebca5d0f2e5624643997aaae529dc96299162418ef54e29eb70", + "sha256:e9046e559c299b395b39ac7dbf16005308821c2f24a63cae2ab173bd6aa11616", + "sha256:ef6be704ae2bc8ad0ebc5cb850ee9139493b0fc4e81abcc240fb392a63ebc808", + "sha256:f8dc19d92896558f9c4317ee365729ead9d7bbcf2052a9a19a3ef17abbb8ac5b" ], "index": "pypi", - "version": "==6.0.0" + "version": "==6.1.0" }, "pycparser": { "hashes": [ @@ -354,12 +371,6 @@ ], "version": "==2.19" }, - "pytoml": { - "hashes": [ - "sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013" - ], - "version": "==0.1.20" - }, "quart": { "hashes": [ "sha256:06ad6c71f12e6f64625bebd4e591903fa653740df4b0e50de2e8722a6370011e", @@ -382,13 +393,21 @@ ], "version": "==2.1.0" }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", + "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3" + ], + "version": "==0.10.0" + }, "typing-extensions": { "hashes": [ - "sha256:07b2c978670896022a43c4b915df8958bec4a6b84add7f2c87b2b728bda3ba64", - "sha256:f3f0e67e1d42de47b5c67c32c9b26641642e9170fe7e292991793705cd5fef7c", - "sha256:fb2cd053238d33a8ec939190f30cfd736c00653a85a2919415cecf7dc3d9da71" + "sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", + "sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", + "sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed" ], - "version": "==3.7.2" + "version": "==3.7.4" }, "websockets": { "hashes": [ @@ -491,35 +510,35 @@ }, "importlib-metadata": { "hashes": [ - "sha256:a9f185022cfa69e9ca5f7eabfd5a58b689894cb78a11e3c8c89398a8ccbb8e7f", - "sha256:df1403cd3aebeb2b1dcd3515ca062eecb5bd3ea7611f18cba81130c68707e879" + "sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7", + "sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db" ], - "version": "==0.17" + "version": "==0.18" }, "more-itertools": { "hashes": [ - "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", - "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" + "sha256:3ad685ff8512bf6dc5a8b82ebf73543999b657eded8c11803d9ba6b648986f4d", + "sha256:8bb43d1f51ecef60d81854af61a3a880555a14643691cc4b64a6ee269c78f09a" ], "markers": "python_version > '2.7'", - "version": "==7.0.0" + "version": "==7.1.0" }, "mypy": { "hashes": [ - "sha256:2afe51527b1f6cdc4a5f34fc90473109b22bf7f21086ba3e9451857cf11489e6", - "sha256:56a16df3e0abb145d8accd5dbb70eba6c4bd26e2f89042b491faa78c9635d1e2", - "sha256:5764f10d27b2e93c84f70af5778941b8f4aa1379b2430f85c827e0f5464e8714", - "sha256:5bbc86374f04a3aa817622f98e40375ccb28c4836f36b66706cf3c6ccce86eda", - "sha256:6a9343089f6377e71e20ca734cd8e7ac25d36478a9df580efabfe9059819bf82", - "sha256:6c9851bc4a23dc1d854d3f5dfd5f20a016f8da86bcdbb42687879bb5f86434b0", - "sha256:b8e85956af3fcf043d6f87c91cbe8705073fc67029ba6e22d3468bfee42c4823", - "sha256:b9a0af8fae490306bc112229000aa0c2ccc837b49d29a5c42e088c132a2334dd", - "sha256:bbf643528e2a55df2c1587008d6e3bda5c0445f1240dfa85129af22ae16d7a9a", - "sha256:c46ab3438bd21511db0f2c612d89d8344154c0c9494afc7fbc932de514cf8d15", - "sha256:f7a83d6bd805855ef83ec605eb01ab4fa42bcef254b13631e451cbb44914a9b0" + "sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", + "sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", + "sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", + "sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", + "sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", + "sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", + "sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", + "sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", + "sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", + "sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", + "sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91" ], "index": "pypi", - "version": "==0.701" + "version": "==0.720" }, "mypy-extensions": { "hashes": [ @@ -575,34 +594,38 @@ }, "typed-ast": { "hashes": [ - "sha256:132eae51d6ef3ff4a8c47c393a4ef5ebf0d1aecc96880eb5d6c8ceab7017cc9b", - "sha256:18141c1484ab8784006c839be8b985cfc82a2e9725837b0ecfa0203f71c4e39d", - "sha256:2baf617f5bbbfe73fd8846463f5aeafc912b5ee247f410700245d68525ec584a", - "sha256:3d90063f2cbbe39177e9b4d888e45777012652d6110156845b828908c51ae462", - "sha256:4304b2218b842d610aa1a1d87e1dc9559597969acc62ce717ee4dfeaa44d7eee", - "sha256:4983ede548ffc3541bae49a82675996497348e55bafd1554dc4e4a5d6eda541a", - "sha256:5315f4509c1476718a4825f45a203b82d7fdf2a6f5f0c8f166435975b1c9f7d4", - "sha256:6cdfb1b49d5345f7c2b90d638822d16ba62dc82f7616e9b4caa10b72f3f16649", - "sha256:7b325f12635598c604690efd7a0197d0b94b7d7778498e76e0710cd582fd1c7a", - "sha256:8d3b0e3b8626615826f9a626548057c5275a9733512b137984a68ba1598d3d2f", - "sha256:8f8631160c79f53081bd23446525db0bc4c5616f78d04021e6e434b286493fd7", - "sha256:912de10965f3dc89da23936f1cc4ed60764f712e5fa603a09dd904f88c996760", - "sha256:b010c07b975fe853c65d7bbe9d4ac62f1c69086750a574f6292597763781ba18", - "sha256:c908c10505904c48081a5415a1e295d8403e353e0c14c42b6d67f8f97fae6616", - "sha256:c94dd3807c0c0610f7c76f078119f4ea48235a953512752b9175f9f98f5ae2bd", - "sha256:ce65dee7594a84c466e79d7fb7d3303e7295d16a83c22c7c4037071b059e2c21", - "sha256:eaa9cfcb221a8a4c2889be6f93da141ac777eb8819f077e1d09fb12d00a09a93", - "sha256:f3376bc31bad66d46d44b4e6522c5c21976bf9bca4ef5987bb2bf727f4506cbb", - "sha256:f9202fa138544e13a4ec1a6792c35834250a85958fde1251b6a22e07d1260ae7" + "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", + "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", + "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", + "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", + "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", + "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", + "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", + "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", + "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", + "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", + "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", + "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", + "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", + "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", + "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" ], - "version": "==1.3.5" + "version": "==1.4.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", + "sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", + "sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed" + ], + "version": "==3.7.4" }, "zipp": { "hashes": [ - "sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d", - "sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3" + "sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", + "sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec" ], - "version": "==0.5.1" + "version": "==0.5.2" } } } diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index 771fbfd..389ec85 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -182,8 +182,7 @@ async def close_channel(channel_id): main_tbl = { ChannelType.GUILD_TEXT: 'guild_text_channels', ChannelType.GUILD_VOICE: 'guild_voice_channels', - - # TODO: categories? + ChannelType.GUILD_CATEGORY: None, }[ctype] await _update_func(guild_id, channel_id) @@ -195,10 +194,17 @@ async def close_channel(channel_id): await delete_messages(channel_id) await guild_cleanup(channel_id) - await app.db.execute(f""" - DELETE FROM {main_tbl} - WHERE id = $1 - """, channel_id) + if main_tbl is not None: + await app.db.execute(f""" + DELETE FROM {main_tbl} + WHERE id = $1 + """, channel_id) + + if ctype == ChannelType.GUILD_CATEGORY: + await app.db.execute(""" + UPDATE guild_channels SET parent_id = NULL + WHERE parent_id = $1 + """, channel_id) await app.db.execute(""" DELETE FROM guild_channels diff --git a/litecord/blueprints/guild/channels.py b/litecord/blueprints/guild/channels.py index 2b0467b..ef45640 100644 --- a/litecord/blueprints/guild/channels.py +++ b/litecord/blueprints/guild/channels.py @@ -20,18 +20,16 @@ along with this program. If not, see . from quart import Blueprint, request, current_app as app, jsonify from litecord.blueprints.auth import token_check -from litecord.snowflake import get_snowflake -from litecord.errors import BadRequest -from litecord.enums import ChannelType -from litecord.blueprints.guild.roles import gen_pairs - -from litecord.schemas import ( - validate, ROLE_UPDATE_POSITION, CHAN_CREATE -) from litecord.blueprints.checks import ( guild_check, guild_owner_check, guild_perm_check ) - +from litecord.blueprints.guild.roles import gen_pairs +from litecord.enums import ChannelType +from litecord.errors import BadRequest +from litecord.schemas import ( + validate, CHANNEL_UPDATE_POSITION, CHAN_CREATE +) +from litecord.snowflake import get_snowflake bp = Blueprint('guild_channels', __name__) @@ -52,6 +50,8 @@ async def _specific_chan_create(channel_id, ctype, **kwargs): kwargs.get('bitrate', 64), kwargs.get('user_limit', 0) ) + elif ctype == ChannelType.GUILD_CATEGORY and 'parent_id' in kwargs and kwargs['parent_id'] is not None: + raise BadRequest('Guild category cannot have a parent!') async def create_guild_channel(guild_id: int, channel_id: int, @@ -72,11 +72,13 @@ async def create_guild_channel(guild_id: int, channel_id: int, # account for the first channel in a guild too max_pos = max_pos or 0 + parent_id = kwargs['parent_id'] if 'parent_id' in kwargs else None + # all channels go to guild_channels await app.db.execute(""" - INSERT INTO guild_channels (id, guild_id, name, position) - VALUES ($1, $2, $3, $4) - """, channel_id, guild_id, kwargs['name'], max_pos + 1) + INSERT INTO guild_channels (id, guild_id, parent_id, name, position) + VALUES ($1, $2, $3, $4, $5) + """, channel_id, guild_id, parent_id, kwargs['name'], max_pos + 1) # the rest of sql magic is dependant on the channel # we're creating (a text or voice or category), @@ -107,7 +109,8 @@ async def create_channel(guild_id): channel_type = ChannelType(channel_type) if channel_type not in (ChannelType.GUILD_TEXT, - ChannelType.GUILD_VOICE): + ChannelType.GUILD_VOICE, + ChannelType.GUILD_CATEGORY): raise BadRequest('Invalid channel type') new_channel_id = get_snowflake() @@ -138,39 +141,37 @@ async def _chan_update_dispatch(guild_id: int, channel_id: int): await app.dispatcher.dispatch_guild(guild_id, 'CHANNEL_UPDATE', chan) -async def _do_single_swap(guild_id: int, pair: tuple): - """Do a single channel swap, dispatching - the CHANNEL_UPDATE events for after the swap""" - pair1, pair2 = pair - channel_1, new_pos_1 = pair1 - channel_2, new_pos_2 = pair2 - - # do the swap in a transaction. - conn = await app.db.acquire() - - async with conn.transaction(): - await conn.executemany(""" - UPDATE guild_channels - SET position = $1 - WHERE id = $2 AND guild_id = $3 - """, [ - (new_pos_1, channel_1, guild_id), - (new_pos_2, channel_2, guild_id)]) - - await app.db.release(conn) - - await _chan_update_dispatch(guild_id, channel_1) - await _chan_update_dispatch(guild_id, channel_2) - - -async def _do_channel_swaps(guild_id: int, swap_pairs: list): - """Swap channel pairs' positions, given the list - of pairs to do. +async def _do_channel_updates(guild_id: int, updates: list): + """Update channel positions, given the list of pairs to do. Dispatches CHANNEL_UPDATEs to the guild. """ - for pair in swap_pairs: - await _do_single_swap(guild_id, pair) + updated = [] + + conn = await app.db.acquire() + for pair in updates: + _id, pos = pair + + async with conn.transaction(): + await conn.execute(""" + UPDATE guild_channels + SET position = $1 + WHERE id = $2 AND guild_id = $3 + """, pos, _id, guild_id) + updated.append(_id) + + await app.db.release(conn) + + for _id in updated: + await _chan_update_dispatch(guild_id, _id) + + +def _group_channel(chan): + if ChannelType(chan['type']) == ChannelType.GUILD_CATEGORY: + return 'c' + elif chan['parent_id'] is None: + return 'n' + return chan['parent_id'] @bp.route('//channels', methods=['PATCH']) @@ -181,22 +182,42 @@ async def modify_channel_pos(guild_id): await guild_owner_check(user_id, guild_id) await guild_perm_check(user_id, guild_id, 'manage_channels') - # same thing as guild.roles, so we use - # the same schema and all. raw_j = await request.get_json() - j = validate({'roles': raw_j}, ROLE_UPDATE_POSITION) - j = j['roles'] + j = validate({'channels': raw_j}, CHANNEL_UPDATE_POSITION) + j = j['channels'] - channels = await app.storage.get_channel_data(guild_id) + channels = {int(chan['id']): chan for chan in await app.storage.get_channel_data(guild_id)} + channel_tree = {} - channel_positions = {chan['position']: int(chan['id']) - for chan in channels} + for chan in j: + conn = await app.db.acquire() + _id = int(chan['id']) + if _id in channels and 'parent_id' in chan and (chan['parent_id'] is None or chan['parent_id'] in channels): + channels[_id]['parent_id'] = chan['parent_id'] + await conn.execute(""" + UPDATE guild_channels + SET parent_id = $1 + WHERE id = $2 AND guild_id = $3 + """, chan['parent_id'], chan['id'], guild_id) - swap_pairs = gen_pairs( - j, - channel_positions - ) + await _chan_update_dispatch(guild_id, chan['id']) + await app.db.release(conn) - await _do_channel_swaps(guild_id, swap_pairs) + for chan in channels.values(): + channel_tree.setdefault(_group_channel(chan), []).append(chan) + + for _key in channel_tree: + _channels = channel_tree[_key] + _channel_ids = list(map(lambda chan: int(chan['id']), _channels)) + print(_key, _channel_ids) + _channel_positions = {chan['position']: int(chan['id']) + for chan in _channels} + _change_list = list(filter(lambda chan: 'position' in chan and int(chan['id']) in _channel_ids, j)) + _swap_pairs = gen_pairs( + _change_list, + _channel_positions + ) + + await _do_channel_updates(guild_id, _swap_pairs) return '', 204 diff --git a/litecord/blueprints/guild/roles.py b/litecord/blueprints/guild/roles.py index 5dfcbe7..7d98f87 100644 --- a/litecord/blueprints/guild/roles.py +++ b/litecord/blueprints/guild/roles.py @@ -19,21 +19,19 @@ along with this program. If not, see . from typing import List, Dict, Tuple -from quart import Blueprint, request, current_app as app, jsonify from logbook import Logger +from quart import Blueprint, request, current_app as app, jsonify from litecord.auth import token_check - from litecord.blueprints.checks import ( guild_check, guild_perm_check ) +from litecord.permissions import get_role_perms from litecord.schemas import ( validate, ROLE_CREATE, ROLE_UPDATE, ROLE_UPDATE_POSITION ) - from litecord.snowflake import get_snowflake from litecord.utils import dict_get -from litecord.permissions import get_role_perms DEFAULT_EVERYONE_PERMS = 104324161 log = Logger(__name__) @@ -152,39 +150,33 @@ async def _role_update_dispatch(role_id: int, guild_id: int): async def _role_pairs_update(guild_id: int, pairs: list): - """Update the roles' positions. + """Update the role positions. Dispatches GUILD_ROLE_UPDATE for all roles being updated. """ - for pair in pairs: - pair_1, pair_2 = pair + updated = [] + conn = await app.db.acquire() - role_1, new_pos_1 = pair_1 - role_2, new_pos_2 = pair_2 + async with conn.transaction(): + for pair in pairs: + _id, pos = pair - conn = await app.db.acquire() - async with conn.transaction(): # update happens in a transaction # so we don't fuck it up await conn.execute(""" UPDATE roles SET position = $1 WHERE roles.id = $2 - """, new_pos_1, role_1) + """, pos, _id) + updated.append(_id) - await conn.execute(""" - UPDATE roles - SET position = $1 - WHERE roles.id = $2 - """, new_pos_2, role_2) + await app.db.release(conn) - await app.db.release(conn) + for _id in updated: + await _role_update_dispatch(_id, guild_id) - # the route fires multiple Guild Role Update. - await _role_update_dispatch(role_1, guild_id) - await _role_update_dispatch(role_2, guild_id) +PairList = List[Tuple[int, int]] -PairList = List[Tuple[Tuple[int, int], Tuple[int, int]]] def gen_pairs(list_of_changes: List[Dict[str, int]], current_state: Dict[int, int], @@ -192,23 +184,6 @@ def gen_pairs(list_of_changes: List[Dict[str, int]], """Generate a list of pairs that, when applied to the database, will generate the desired state given in list_of_changes. - We must check if the given list_of_changes isn't overwriting an - element's (such as a role or a channel) position to an existing one, - without there having an already existing change for the other one. - - Here's a pratical explanation with roles: - - R1 (in position RP1) wants to be in the same position - as R2 (currently in position RP2). - - So, if we did the simpler approach, list_of_changes - would just contain the preferred change: (R1, RP2). - - With gen_pairs, there MUST be a (R2, RP1) in list_of_changes, - if there is, the given result in gen_pairs will be a pair - ((R1, RP2), (R2, RP1)) which is then used to actually - update the roles' positions in a transaction. - Parameters ---------- list_of_changes: @@ -224,52 +199,37 @@ def gen_pairs(list_of_changes: List[Dict[str, int]], Returns ------- list - List of swaps to do to achieve the preferred + List of changes to do to achieve the preferred state given by ``list_of_changes``. """ pairs = [] blacklist = blacklist or [] - preferred_state = {element['id']: element['position'] - for element in list_of_changes} + preferred_state = [] + for chan in current_state: + preferred_state.insert(chan, current_state[chan]) for blacklisted_id in blacklist: - preferred_state.pop(blacklisted_id) + if blacklisted_id in preferred_state: + preferred_state.remove(blacklisted_id) + + current_state = preferred_state.copy() # for each change, we must find a matching change # in the same list, so we can make a swap pair for change in list_of_changes: - element_1, new_pos_1 = change['id'], change['position'] - - # check current pairs - # so we don't repeat an element - flag = False - - for pair in pairs: - if (element_1, new_pos_1) in pair: - flag = True - - # skip if found - if flag: + _id, pos = change['id'], change['position'] + if _id not in preferred_state: continue - # search if there is a role/channel in the - # position we want to change to - element_2 = current_state.get(new_pos_1) + preferred_state.remove(_id) + preferred_state.insert(pos, _id) - if element_2 is None: - continue + assert len(current_state) == len(preferred_state) - # if there is, is that existing channel being - # swapped to another position? - new_pos_2 = preferred_state.get(element_2) - - # if its being swapped to leave space, add it - # to the pairs list - if new_pos_2 is not None: - pairs.append( - ((element_1, new_pos_1), (element_2, new_pos_2)) - ) + for i in range(len(current_state)): + if current_state[i] != preferred_state[i]: + pairs.append((preferred_state[i], i)) return pairs diff --git a/litecord/schemas.py b/litecord/schemas.py index 47ea60f..37454cb 100644 --- a/litecord/schemas.py +++ b/litecord/schemas.py @@ -58,7 +58,6 @@ def _in_enum(enum, value) -> bool: except ValueError: return False - class LitecordValidator(Validator): """Main validator class for Litecord, containing custom types.""" def _validate_type_username(self, value: str) -> bool: @@ -413,6 +412,22 @@ ROLE_UPDATE_POSITION = { } +CHANNEL_UPDATE_POSITION = { + 'channels': { + 'type': 'list', + 'schema': { + 'type': 'dict', + 'schema': { + 'id': {'coerce': int}, + 'position': {'coerce': int}, + 'parent_id': {'coerce': int, 'required': False, 'nullable': True}, + 'lock_permissions': {'type': 'boolean', 'required': False}, + }, + } + } +} + + MEMBER_UPDATE = { 'nick': { 'type': 'nickname', 'required': False}, diff --git a/litecord/storage.py b/litecord/storage.py index 79d07dd..2ae969f 100644 --- a/litecord/storage.py +++ b/litecord/storage.py @@ -381,6 +381,9 @@ class Storage: return {**row, **dict(vrow)} + if chan_type == ChannelType.GUILD_CATEGORY: + return row + log.warning('unknown channel type: {}', chan_type) return row @@ -478,6 +481,8 @@ class Storage: res['permission_overwrites'] = await self.chan_overwrites( channel_id) + if (res['parent_id']) is not None: + res['parent_id'] = str(res['parent_id']) res['id'] = str(res['id']) return res elif ctype == ChannelType.DM: @@ -561,6 +566,8 @@ class Storage: # Making sure. res['id'] = str(res['id']) + if (res['parent_id']) is not None: + res['parent_id'] = str(res['parent_id']) channels.append(res) return channels