diff --git a/Pipfile b/Pipfile index 77ef605..4380a05 100644 --- a/Pipfile +++ b/Pipfile @@ -4,11 +4,13 @@ verify_ssl = true name = "pypi" [packages] -Flask = "==1.0.2" -"argon2-cffi" = "==18.3.0" -bcrypt = "==3.1.4" +"argon2-cffi" = "==20.1.0" +bcrypt = "==3.1.7" pony = "==0.7.6" flup = "==1.0.3" +flask = "==1.1.2" +flask-cors = "*" +python-urlify = {editable = true,git = "git://github.com/dreikanter/python-urlify"} [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index d14d1a2..837b39c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f233a44ad456d1f5c386c7913543df8cf372efca58c4a316fbddbae667278c7f" + "sha256": "737118abc77181ae5528b3db3038e7973b0e4c1c929cdd71223da07cf44ca961" }, "pipfile-spec": 6, "requires": { @@ -18,119 +18,105 @@ "default": { "argon2-cffi": { "hashes": [ - "sha256:003f588de43a817af6ecc1c06103fa0801de63849db3cb0f37576bb2da29043d", - "sha256:04528ebbcc5d77eb49e7c2560fcf9d489cdc3b14f89fdd975c72c0a12934025a", - "sha256:04ead34244af38d79742cc46a212fec94daf99b49add66878f5d4b22da72d4aa", - "sha256:0529aeb71b50e068d300c992f850387c2456f2d3d4083d17d18e75710d057682", - "sha256:0d18a3dcb4ca7f3717155994a4f131a43072e47b708e57c4be16f60253337dfd", - "sha256:0ecbd2346da3e5af84427fd8df3ece484c903a9dafd9470571def47df54f2780", - "sha256:193de795483b00d752d16ec5df11d119a3a2c43f5464edfaf919a2ca9cc5b991", - "sha256:22a99f90da7176ee86fbdfb0a95411bc807b9d795b89495ee88c2e0468a496e8", - "sha256:2829d648dfa4d42ce33ec0f36e863d1068fd729b38ef6f830262b43e04f9ba1c", - "sha256:3b61a4ef1eb785d41f190520db716aa598d15f147419cbbdc9061dc232126f09", - "sha256:3ddcdde047cd4dba2bcce7d890dcefd6723548b849fa82ba87e04a468079b9b1", - "sha256:457c5db9bb99f2ffb7ce9ebf923b523898e75464dd019fbebdd1c6096ddcf044", - "sha256:51d78eedbba1f9e45a1c3fb1470ad6d1faafc6ec42eabb969df29c2aa848b645", - "sha256:af0d3dbc8f32d95be480eedd5d77fe8714f5441a28b9abcfa687ecf5301a1abd", - "sha256:ca65f736d2129687008178e3d9956264fd2be2f69429edf0d755c2f97cd003f1", - "sha256:d371fcd42e01c78c76397120d07c67f6e16f5fef97d327ad372c8debe38f9f56", - "sha256:ec12248d4c1e045a736beebf55daf1430c45a29ab8d773d8540c224555784275" + "sha256:05a8ac07c7026542377e38389638a8a1e9b78f1cd8439cd7493b39f08dd75fbf", + "sha256:0bf066bc049332489bb2d75f69216416329d9dc65deee127152caeb16e5ce7d5", + "sha256:18dee20e25e4be86680b178b35ccfc5d495ebd5792cd00781548d50880fee5c5", + "sha256:392c3c2ef91d12da510cfb6f9bae52512a4552573a9e27600bdb800e05905d2b", + "sha256:57358570592c46c420300ec94f2ff3b32cbccd10d38bdc12dc6979c4a8484fbc", + "sha256:6678bb047373f52bcff02db8afab0d2a77d83bde61cfecea7c5c62e2335cb203", + "sha256:6ea92c980586931a816d61e4faf6c192b4abce89aa767ff6581e6ddc985ed003", + "sha256:77e909cc756ef81d6abb60524d259d959bab384832f0c651ed7dcb6e5ccdbb78", + "sha256:7d455c802727710e9dfa69b74ccaab04568386ca17b0ad36350b622cd34606fe", + "sha256:9bee3212ba4f560af397b6d7146848c32a800652301843df06b9e8f68f0f7361", + "sha256:9dfd5197852530294ecb5795c97a823839258dfd5eb9420233c7cfedec2058f2", + "sha256:b160416adc0f012fb1f12588a5e6954889510f82f698e23ed4f4fa57f12a0647", + "sha256:ba7209b608945b889457f949cc04c8e762bed4fe3fec88ae9a6b7765ae82e496", + "sha256:cc0e028b209a5483b6846053d5fd7165f460a1f14774d79e632e75e7ae64b82b", + "sha256:d8029b2d3e4b4cea770e9e5a0104dd8fa185c1724a0f01528ae4826a6d25f97d", + "sha256:da7f0445b71db6d3a72462e04f36544b0de871289b0bc8a7cc87c0f5ec7079fa" ], "index": "pypi", - "version": "==18.3.0" + "version": "==20.1.0" }, "bcrypt": { "hashes": [ - "sha256:01477981abf74e306e8ee31629a940a5e9138de000c6b0898f7f850461c4a0a5", - "sha256:054d6e0acaea429e6da3613fcd12d05ee29a531794d96f6ab959f29a39f33391", - "sha256:0872eeecdf9a429c1420158500eedb323a132bc5bf3339475151c52414729e70", - "sha256:09a3b8c258b815eadb611bad04ca15ec77d86aa9ce56070e1af0d5932f17642a", - "sha256:0f317e4ffbdd15c3c0f8ab5fbd86aa9aabc7bea18b5cc5951b456fe39e9f738c", - "sha256:2788c32673a2ad0062bea850ab73cffc0dba874db10d7a3682b6f2f280553f20", - "sha256:321d4d48be25b8d77594d8324c0585c80ae91ac214f62db9098734e5e7fb280f", - "sha256:346d6f84ff0b493dbc90c6b77136df83e81f903f0b95525ee80e5e6d5e4eef84", - "sha256:34dd60b90b0f6de94a89e71fcd19913a30e83091c8468d0923a93a0cccbfbbff", - "sha256:3b4c23300c4eded8895442c003ae9b14328ae69309ac5867e7530de8bdd7875d", - "sha256:43d1960e7db14042319c46925892d5fa99b08ff21d57482e6f5328a1aca03588", - "sha256:49e96267cd9be55a349fd74f9852eb9ae2c427cd7f6455d0f1765d7332292832", - "sha256:63e06ffdaf4054a89757a3a1ab07f1b922daf911743114a54f7c561b9e1baa58", - "sha256:67ed1a374c9155ec0840214ce804616de49c3df9c5bc66740687c1c9b1cd9e8d", - "sha256:6b662a5669186439f4f583636c8d6ea77cf92f7cfe6aae8d22edf16c36840574", - "sha256:6efd9ca20aefbaf2e7e6817a2c6ed4a50ff6900fafdea1bcb1d0e9471743b144", - "sha256:8569844a5d8e1fdde4d7712a05ab2e6061343ac34af6e7e3d7935b2bd1907bfd", - "sha256:8629ea6a8a59f865add1d6a87464c3c676e60101b8d16ef404d0a031424a8491", - "sha256:988cac675e25133d01a78f2286189c1f01974470817a33eaf4cfee573cfb72a5", - "sha256:9a6fedda73aba1568962f7543a1f586051c54febbc74e87769bad6a4b8587c39", - "sha256:9eced8962ce3b7124fe20fd358cf8c7470706437fa064b9874f849ad4c5866fc", - "sha256:a005ed6163490988711ff732386b08effcbf8df62ae93dd1e5bda0714fad8afb", - "sha256:ae35dbcb6b011af6c840893b32399252d81ff57d52c13e12422e16b5fea1d0fb", - "sha256:b1e8491c6740f21b37cca77bc64677696a3fb9f32360794d57fa8477b7329eda", - "sha256:c906bdb482162e9ef48eea9f8c0d967acceb5c84f2d25574c7d2a58d04861df1", - "sha256:cb18ffdc861dbb244f14be32c47ab69604d0aca415bee53485fcea4f8e93d5ef", - "sha256:cc2f24dc1c6c88c56248e93f28d439ee4018338567b0bbb490ea26a381a29b1e", - "sha256:d860c7fff18d49e20339fc6dffc2d485635e36d4b2cccf58f45db815b64100b4", - "sha256:d86da365dda59010ba0d1ac45aa78390f56bf7f992e65f70b3b081d5e5257b09", - "sha256:e22f0997622e1ceec834fd25947dc2ee2962c2133ea693d61805bc867abaf7ea", - "sha256:f2fe545d27a619a552396533cddf70d83cecd880a611cdfdbb87ca6aec52f66b", - "sha256:f425e925485b3be48051f913dbe17e08e8c48588fdf44a26b8b14067041c0da6", - "sha256:f7fd3ed3745fe6e81e28dc3b3d76cce31525a91f32a387e1febd6b982caf8cdb", - "sha256:f9210820ee4818d84658ed7df16a7f30c9fba7d8b139959950acef91745cc0f7" + "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89", + "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42", + "sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294", + "sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161", + "sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752", + "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31", + "sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5", + "sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c", + "sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0", + "sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de", + "sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e", + "sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052", + "sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09", + "sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105", + "sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133", + "sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1", + "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7", + "sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc" ], "index": "pypi", - "version": "==3.1.4" + "version": "==3.1.7" }, "cffi": { "hashes": [ - "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", - "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", - "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", - "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", - "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", - "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", - "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", - "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", - "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", - "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", - "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", - "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", - "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", - "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", - "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", - "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", - "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", - "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", - "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", - "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", - "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", - "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", - "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", - "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", - "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", - "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", - "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", - "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", - "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", - "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", - "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", - "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", + "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", + "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", + "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", + "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", + "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", + "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", + "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", + "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", + "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", + "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", + "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", + "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", + "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", + "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", + "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", + "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", + "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", + "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", + "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", + "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", + "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", + "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", + "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", + "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", + "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", + "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", + "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" ], - "version": "==1.11.5" + "version": "==1.14.0" }, "click": { "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", - "version": "==7.0" + "version": "==7.1.2" }, "flask": { "hashes": [ - "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", - "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" + "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", + "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" ], "index": "pypi", - "version": "==1.0.2" + "version": "==1.1.2" + }, + "flask-cors": { + "hashes": [ + "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", + "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a" + ], + "index": "pypi", + "version": "==3.0.8" }, "flup": { "hashes": [ @@ -145,49 +131,52 @@ "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", "version": "==1.1.0" }, "jinja2": { "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", + "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" ], - "version": "==2.10" + "version": "==2.11.2" }, "markupsafe": { "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", - "version": "==1.1.0" + "version": "==1.1.1" }, "pony": { "hashes": [ @@ -200,25 +189,24 @@ }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", - "version": "==2.19" + "version": "==2.20" }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6'", - "version": "==1.12.0" + "version": "==1.15.0" }, "werkzeug": { "hashes": [ - "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", - "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" + "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", + "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" ], - "version": "==0.14.1" + "version": "==1.0.1" } }, "develop": {} diff --git a/app/controllers/auth.py b/app/controllers/auth.py index 3cd96ad..79844bd 100644 --- a/app/controllers/auth.py +++ b/app/controllers/auth.py @@ -1,30 +1,25 @@ from pony.orm import db_session from app.models.users import User from app.controllers.users import UsersController -from core.exceptions import InvalidCreds +from core.exceptions import InvalidCredentials from core.utils.passwords import ArgonHasher, BcryptHasher class AuthController: @staticmethod @db_session - def create_session(email, password): - for user in User.select(lambda u: u.email == email): - if user.password_type == 'bcrypt': - if BcryptHasher.are_password_same(user.password, password): - return { - 'user': UsersController.get_one_by_token(user.token), - 'token': user.token - } - else: - raise InvalidCreds + def create_session(email: str, password: str): + user = User.get(email=email) + if user is None: + raise InvalidCredentials + + if user.password_type == 'bcrypt': + if BcryptHasher.are_password_same(user.password, password): + return UsersController.get_one_by_token(user.token), user.token else: - if ArgonHasher.are_password_same(user.password, password): - return { - 'user': UsersController.get_one_by_token(user.token), - 'token': user.token - } - else: - raise InvalidCreds - - raise InvalidCreds + raise InvalidCredentials + else: + if ArgonHasher.are_password_same(user.password, password): + return UsersController.get_one_by_token(user.token), user.token + else: + raise InvalidCredentials diff --git a/app/controllers/blog_posts.py b/app/controllers/blog_posts.py index fdfc364..f93a27d 100644 --- a/app/controllers/blog_posts.py +++ b/app/controllers/blog_posts.py @@ -1,43 +1,51 @@ import time - from pony.orm import * -from werkzeug.exceptions import NotFound +from core.utils.ids import generate_url from app.controllers.users import UsersController from app.models.blog_posts import BlogPost class BlogPostsController: @staticmethod - def fill_informations(post: BlogPost, without_content: bool = False): - if without_content: - to_exclude = 'content' - else: - to_exclude = None - p = post.to_dict(exclude=to_exclude) - p['author'] = UsersController.get_one(p['author']) - return p + def fill_information(post: BlogPost, include_content: bool = False): + to_exclude = None if include_content else 'content' + post = post.to_dict(exclude=to_exclude) + post['author'] = UsersController.get_one(post['author']) + return post @staticmethod @db_session - def get_all(): - posts = list(BlogPost.select().order_by(desc(BlogPost.timestamp))) + def multi_fill_information(posts: [BlogPost], include_content: bool = False): + posts = list(posts) for post in posts: - posts[posts.index(post)] = BlogPostsController.fill_informations(post, True) + posts[posts.index(post)] = BlogPostsController.fill_information(post, include_content) return posts + @staticmethod + @db_session + def fetch_all(): + return BlogPost.select().sort_by(desc(BlogPost.timestamp)) + + @staticmethod + @db_session + def filter_by_category(posts, category): + return posts.where(category=category) + + @staticmethod + @db_session + def filter_by_type(posts, type): + return posts.where(type=type) + @staticmethod @db_session def get_last(): - posts = list(BlogPost.select().order_by(desc(BlogPost.timestamp))) - return BlogPostsController.fill_informations(posts[0]) + posts = BlogPostsController.fetch_all() + return BlogPostsController.fill_information(posts.first(), include_content=True) @staticmethod @db_session def get_one(url): - try: - return BlogPostsController.fill_informations(BlogPost[url]) - except core.ObjectNotFound: - raise NotFound + return BlogPostsController.fill_information(BlogPost[url], include_content=True) @staticmethod @db_session @@ -45,7 +53,7 @@ def create_one(params, optional_data): timestamp = int(time.time()) # Required fields - post = BlogPost(url=params['url'], + post = BlogPost(url=generate_url(params['title']), title=params['title'], timestamp=timestamp, author=params['author_username'], @@ -65,20 +73,14 @@ def create_one(params, optional_data): @staticmethod @db_session def update_one(url, params, optional_data): - try: - post = BlogPost[url] - for field in optional_data: - if field in params: - setattr(post, field, params[field]) - commit() - except core.ObjectNotFound: - raise NotFound + post = BlogPost[url] + for field in optional_data: + if field in params: + setattr(post, field, params[field]) + commit() @staticmethod @db_session def delete_one(url): - try: - BlogPost[url].delete() - return True - except core.ObjectNotFound: - raise NotFound + BlogPost[url].delete() + return True diff --git a/app/controllers/posts.py b/app/controllers/posts.py index fd28c34..22fe934 100644 --- a/app/controllers/posts.py +++ b/app/controllers/posts.py @@ -1,75 +1,68 @@ import time - from pony.orm import * -from werkzeug.exceptions import NotFound -from core.exceptions import DataError - +from core.utils.ids import generate_url from app.controllers.users import UsersController from app.models.posts import Post -from core.utils import ids class PostsController: @staticmethod - def fill_informations(post: Post, additional_fields: list = []): - fields = ['title', 'url', 'category', 'author', 'timestamp', 'banner'] + additional_fields - post = post.to_dict(only=fields) + @db_session + def fill_information(post: Post, include_content: bool = False): + to_exclude = None if include_content else 'content' + post = post.to_dict(exclude=to_exclude) post['author'] = UsersController.get_one(post['author']) return post @staticmethod @db_session - def get_all(): - posts = list(Post.select().order_by(desc(Post.timestamp))) + def multi_fill_information(posts: [Post], include_content: bool = False): + posts = list(posts) for post in posts: - posts[posts.index(post)] = PostsController.fill_informations(post) + posts[posts.index(post)] = PostsController.fill_information(post, include_content) return posts + @staticmethod + @db_session + def fetch_all(): + return Post.select().sort_by(desc(Post.timestamp)) + @staticmethod @db_session def get_last(): - posts = list(Post.select().order_by(desc(Post.timestamp))) - return PostsController.fill_informations(posts[0]) + posts = PostsController.fetch_all() + return PostsController.fill_information(posts.first(), True) @staticmethod @db_session def get_one(url): - try: - return PostsController.fill_informations(Post[url], ['content']) - except core.ObjectNotFound: - raise NotFound + return PostsController.fill_information(Post[url], True) @staticmethod @db_session - def create_one(title, url, category, content, banner, author_username): + def create_one(params): timestamp = int(time.time()) - post = Post(title=title, - url=url, - content=content, - category=category, - banner=banner, + post = Post(title=params['title'], + url=generate_url(params['title']), + content=params['content'], + category=params['category'], + banner=params['banner'], timestamp=timestamp, - author=author_username) + author=params['author_username']) commit() return True @staticmethod @db_session def update_one(url, params, optional_data): - try: - post = Post[url] - for field in optional_data: - if field in params: - setattr(post, field, params[field]) - commit() - except core.ObjectNotFound: - raise NotFound + post = Post[url] + for field in optional_data: + if field in params: + setattr(post, field, params[field]) + commit() @staticmethod @db_session def delete_one(url): - try: - Post[url].delete() - return True - except core.ObjectNotFound: - raise NotFound + Post[url].delete() + return True diff --git a/app/controllers/users.py b/app/controllers/users.py index 1366607..43c7b3f 100644 --- a/app/controllers/users.py +++ b/app/controllers/users.py @@ -2,19 +2,21 @@ from werkzeug.exceptions import NotFound from pony.orm import * -from core.exceptions import DataError from app.models.users import User -from core.utils import ids, tokens -from core.utils.passwords import ArgonHasher, BcryptHasher +from core.utils import tokens +from core.utils.passwords import ArgonHasher class UsersController: @staticmethod - def fill_informations(user: User, additional_fields: list = []): - fields = ['username', 'displayname', 'timestamp', 'picture', 'description', 'biography', 'location', 'socials', 'is_email_public'] + additional_fields + def fill_information(user: User, include_permissions: bool = False): + fields = ['username', 'displayname', 'timestamp', 'picture', 'description', 'biography', 'location', 'socials', + 'is_email_public'] if user.is_email_public: fields.append('email') + if include_permissions: + fields.append('permissions') return user.to_dict(only=fields) @staticmethod @@ -22,44 +24,31 @@ def fill_informations(user: User, additional_fields: list = []): def get_all(): users = list(User.select()) for user in users: - users[users.index(user)] = UsersController.fill_informations(user) + users[users.index(user)] = UsersController.fill_information(user) return users @staticmethod @db_session def get_one(username): - try: - user = User[username] - if user.is_activated is False or user.is_verified is False: - raise NotFound - return UsersController.fill_informations(user) - except core.ObjectNotFound: - raise NotFound + user = User[username] + UsersController.check_active(user) + return UsersController.fill_information(user) @staticmethod @db_session def get_one_by_token(token): - try: - user = User.get(token=token) - if user is None: - raise NotFound - if user.is_activated is False or user.is_verified is False: - raise NotFound - return UsersController.fill_informations(user, additional_fields=['permissions']) - except core.ObjectNotFound: + user = User.get(token=token) + if user is None: raise NotFound + UsersController.check_active(user) + return UsersController.fill_information(user, include_permissions=True) @staticmethod @db_session def get_user_permissions(username): - try: - user = User[username] - if user.is_activated is False or user.is_verified is False: - raise NotFound - return user.permissions - except core.ObjectNotFound: - raise NotFound - + user = User[username] + UsersController.check_active(user) + return user.permissions @staticmethod @db_session @@ -80,28 +69,27 @@ def create_one(email, username, password): @staticmethod @db_session def update_profile(token, params, optional_data): - try: - user = User.get(token=token) - for field in optional_data: - if field in params: - setattr(user, field, params[field]) - commit() - return True - except core.ObjectNotFound: - raise NotFound + user = User.get(token=token) + for field in optional_data: + if field in params: + setattr(user, field, params[field]) + commit() + return True @staticmethod @db_session def update_permissions(username, permissions): - try: - user = User[username] - user.permissions = permissions - commit() - return True - except core.ObjectNotFound: - raise NotFound + user = User[username] + user.permissions = permissions + commit() + return True @staticmethod @db_session def delete_one(username, admin_token): pass + + @staticmethod + def check_active(user): + if user.is_activated is False or user.is_verified is False: + raise NotFound diff --git a/app/middlewares/body.py b/app/middlewares/body.py index 7970b15..c017791 100644 --- a/app/middlewares/body.py +++ b/app/middlewares/body.py @@ -3,7 +3,12 @@ class CheckBody: @staticmethod - def call(request, required_data, optional_data): + def call(request, required_data=None, optional_data=None): + if required_data is None: + required_data = {} + if optional_data is None: + optional_data = {} + request_data = request.json parsed_data = { 'optional': {} @@ -12,11 +17,10 @@ def call(request, required_data, optional_data): if field in request_data: parsed_data[field] = request_data[field] else: - raise DataError + raise DataError(required_data, optional_data) for field in optional_data: if field in request_data: parsed_data['optional'][field] = request_data[field] - return parsed_data diff --git a/app/middlewares/permissions.py b/app/middlewares/permissions.py index 55edd63..17d44bb 100644 --- a/app/middlewares/permissions.py +++ b/app/middlewares/permissions.py @@ -8,9 +8,7 @@ class CheckPermissions: def __init__(self, request, permissions): CheckAuth(request) - user = UsersController.get_one_by_token(request.headers.get("Authorization")) + user = UsersController.get_one_by_token(request.headers.get('Authorization')) for permission in permissions: - if permission in user["permissions"]: - pass - else: + if permission not in user['permissions']: raise Forbidden diff --git a/app/models/blog_posts.py b/app/models/blog_posts.py index bc1678a..bf1d624 100644 --- a/app/models/blog_posts.py +++ b/app/models/blog_posts.py @@ -15,4 +15,5 @@ class BlogPost(db.Entity): content = Required(str) locale = Required(str, default='fr') article_language = Required(str, default='md') + _table_ = 'articles' diff --git a/app/models/posts.py b/app/models/posts.py index 951fcdb..df336fb 100644 --- a/app/models/posts.py +++ b/app/models/posts.py @@ -6,7 +6,7 @@ class Post(db.Entity): url = PrimaryKey(str, max_len=64) title = Required(str, max_len=64) category = Required(str, max_len=20) - banner = Optional(str, default="") + banner = Optional(str, default='') content = Required(LongStr) author = Required(str) timestamp = Required(int, unique=True) diff --git a/app/models/users.py b/app/models/users.py index 4c9bffc..c3dabfe 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -11,7 +11,7 @@ class User(db.Entity): permissions = Required(Json, default=[]) token = Required(str) timestamp = Required(int) - picture = Required(str, default='https://cdn.becauseofprog.fr/pictures/new_member.png') + picture = Required(str, default='https://cdn.becauseofprog.fr/v2/sites/becauseofprog.fr/pictures/new_member.png') description = Optional(str) biography = Optional(str) location = Optional(str) diff --git a/app/views/auth.py b/app/views/auth.py index e5209b7..cdb2154 100644 --- a/app/views/auth.py +++ b/app/views/auth.py @@ -1,10 +1,10 @@ from flask import request -from werkzeug.exceptions import NotFound +from app.controllers.users import UsersController from app.controllers.auth import AuthController +from app.middlewares.auth import CheckAuth from app.middlewares.body import CheckBody from core import responses -from core.exceptions import InvalidCreds, DataError from main import app @@ -18,15 +18,15 @@ def create_session(): 'type': 'string' } } - try: - data = CheckBody.call(request, required_data=required_data, optional_data={}) - user_data = AuthController.create_session(email=data['email'], password=data['password']) - response = { - 'code': 1, - 'data': user_data - } - return responses.response(response) - except DataError: - return responses.data_error(required_data) - except InvalidCreds: - return responses.invalid_username_or_password() + data = CheckBody.call(request, required_data=required_data) + user, token = AuthController.create_session(data['email'], data['password']) + return responses.success({ + 'user': user, + 'token': token + }) + + +@app.route('/v1/auth/data', methods=['GET']) +def get_information(): + CheckAuth(request) + return responses.success(UsersController.get_one_by_token(request.headers.get('Authorization'))) diff --git a/app/views/blog_posts.py b/app/views/blog_posts.py index 6589885..eac34a7 100644 --- a/app/views/blog_posts.py +++ b/app/views/blog_posts.py @@ -5,35 +5,36 @@ from app.middlewares.body import CheckBody from app.middlewares.permissions import CheckPermissions from core import responses -from core.exceptions import DataError +from core.utils.pagination import paginate from main import app @app.route('/v1/blog-posts', methods=['GET']) def get_all_blog_posts(): - response = { - 'code': 1, - 'data': BlogPostsController.get_all() - } - return responses.response(response) + posts = BlogPostsController.fetch_all() + + category = request.args.get('category', None) + if category is not None: + posts = BlogPostsController.filter_by_category(posts, category) + + type = request.args.get('type', None) + if type is not None: + posts = BlogPostsController.filter_by_type(posts, type) + + posts, pages = paginate(request, posts) + posts = BlogPostsController.multi_fill_information(posts) + + return responses.success(posts, pages=pages) + @app.route('/v1/blog-posts/last', methods=['GET']) def get_last_blog_post(): - response = { - 'code': 1, - 'data': BlogPostsController.get_last() - } - return responses.response(response) + return responses.success(BlogPostsController.get_last()) @app.route('/v1/blog-posts', methods=['POST']) def create_blog_post(): required_data = { - 'url': { - 'type': 'string', - 'min_length': 5, - 'max_length': 64 - }, 'title': { 'type': 'string', 'min_length': 5, @@ -67,25 +68,20 @@ def create_blog_post(): 'default': 'fr' } } - try: - data = CheckBody.call(request, required_data=required_data, optional_data=optional_data) - CheckPermissions(request, permissions=['BLOG_WRITE']) - author = UsersController.get_one_by_token(request.headers.get('Authorization')) - data['author_username'] = author['username'] - BlogPostsController.create_one(params=data, - optional_data=optional_data - ) - return responses.response({'code': 1}, 201) - except DataError: - return responses.data_error(required_data, optional_data) + data = CheckBody.call(request, required_data=required_data, optional_data=optional_data) + CheckPermissions(request, permissions=['BLOG_WRITE']) + author = UsersController.get_one_by_token(request.headers.get('Authorization')) + data['author_username'] = author['username'] + BlogPostsController.create_one(params=data, + optional_data=optional_data + ) + return responses.created() + @app.route('/v1/blog-posts/', methods=['GET']) def get_one_blog_post(url): - response = { - 'code': 1, - 'data': BlogPostsController.get_one(url) - } - return responses.response(response) + return responses.success(BlogPostsController.get_one(url)) + @app.route('/v1/blog-posts/', methods=['PATCH']) def edit_blog_post(url): @@ -117,16 +113,14 @@ def edit_blog_post(url): 'min_length': 20 } } - try: - post = BlogPostsController.get_one(url) - data = CheckBody.call(request, required_data={}, optional_data=optional_data) - CheckPermissions(request, permissions=['BLOG_WRITE']) - BlogPostsController.update_one(url=url, - params=data['optional'], - optional_data=optional_data) - return responses.no_content() - except DataError: - return responses.data_error({}, optional_data) + post = BlogPostsController.get_one(url) + data = CheckBody.call(request, optional_data=optional_data) + CheckPermissions(request, permissions=['BLOG_WRITE']) + BlogPostsController.update_one(url=url, + params=data['optional'], + optional_data=optional_data) + return responses.no_content() + @app.route('/v1/blog-posts/', methods=['DELETE']) def delete_blog_post(url): diff --git a/app/views/errors.py b/app/views/errors.py index c1a679c..28cbfb4 100644 --- a/app/views/errors.py +++ b/app/views/errors.py @@ -1,57 +1,62 @@ -import json +from pony.orm import * from main import app +from core import responses +from core.exceptions import * @app.errorhandler(400) def client_error(_): - response = json.dumps({ - 'code': 0, - 'message': 'Bad request' - }) - return response, 400, {'Content-Type': 'application/json'} + return responses.fail('Bad request') @app.errorhandler(401) def unauthorized(_): - response = { - 'code': 0, - 'message': 'Unauthorized' - } - return json.dumps(response), 401, {'Content-Type': 'application/json'} + return responses.fail('Unauthorized', code=401) @app.errorhandler(403) def forbidden(_): - response = { - 'code': 0, - 'message': 'Forbidden' - } - return json.dumps(response), 403, {'Content-Type': 'application/json'} + return responses.fail('Forbidden', code=403) +@app.errorhandler(core.ObjectNotFound) @app.errorhandler(404) def page_not_found(_): - response = { - 'code': 0, - 'message': 'Not found' - } - return json.dumps(response), 404, {'Content-Type': 'application/json'} + return responses.fail('Not found', code=404) @app.errorhandler(405) def method_not_allowed(_): - response = { - 'code': 0, - 'message': 'Method not allowed' - } - return json.dumps(response), 405, {'Content-Type': 'application/json'} + return responses.fail('Method not allowed', code=405) + + +@app.errorhandler(PaginationError) +def pagination_error(_): + return responses.fail('Invalid page number. Required type : integer greater than 0') + + +@app.errorhandler(InvalidCredentials) +def invalid_credentials(_): + return responses.fail('Invalid email address and/or password') + + +@app.errorhandler(DataError) +def data_error(error): + return responses.fail( + 'Error on the passed data. Try looking at the API documentation for required and optional data : ' + 'https://github.com/BecauseOfProg/api-docs', + additional={ + 'required_data': error.required_data, + 'optional_data': error.optional_data + }) + + +@app.errorhandler(NotUnique) +def not_unique(_): + return responses.fail('The resource already exists. Try changing the identifier (URL or username)') @app.errorhandler(500) def internal_server_error(_): - response = { - 'code': 0, - 'message': 'Internal server error' - } - return json.dumps(response), 500, {'Content-Type': 'application/json'} + return responses.fail('Internal server error', code=500) diff --git a/app/views/posts.py b/app/views/posts.py index bbe902d..e3d88e7 100644 --- a/app/views/posts.py +++ b/app/views/posts.py @@ -5,33 +5,25 @@ from app.middlewares.body import CheckBody from app.middlewares.permissions import CheckPermissions from core import responses -from core.exceptions import DataError +from core.utils.pagination import paginate from main import app @app.route('/v1/posts', methods=['GET']) def get_all_posts(): - response = { - 'code': 1, - 'data': PostsController.get_all() - } - return responses.response(response) + posts = PostsController.fetch_all() + posts, pages = paginate(request, posts) + return responses.success(PostsController.multi_fill_information(posts), pages=pages) + @app.route('/v1/posts/last', methods=['GET']) def get_last_post(): - response = { - 'code': 1, - 'data': PostsController.get_last() - } - return responses.response(response) + return responses.success(PostsController.get_last()) + @app.route('/v1/posts/', methods=['GET']) def get_one_post(url): - response = { - 'code': 1, - 'data': PostsController.get_one(url) - } - return responses.response(response) + return responses.success(PostsController.get_one(url)) @app.route('/v1/posts', methods=['POST']) @@ -42,11 +34,6 @@ def create_post(): 'min_length': 5, 'max_length': 64 }, - 'url': { - 'type': 'string', - 'min_length': 5, - 'max_length': 64 - }, 'category': { 'type': 'string', 'max_length': 20 @@ -59,19 +46,13 @@ def create_post(): 'type': 'string' } } - try: - data = CheckBody.call(request, required_data=required_data, optional_data={}) - CheckPermissions(request, permissions=['POST_WRITE']) - author = UsersController.get_one_by_token(request.headers.get('Authorization')) - PostsController.create_one(title=data['title'], - url=data['url'], - category=data['category'], - content=data['content'], - author_username=author['username'], - banner=data['banner']) - return responses.response({'code': 1}, 201) - except DataError: - return responses.data_error(required_data) + data = CheckBody.call(request, required_data=required_data) + CheckPermissions(request, permissions=['POST_WRITE']) + author = UsersController.get_one_by_token(request.headers.get('Authorization')) + data['author_username'] = author['username'] + PostsController.create_one(data) + return responses.created() + @app.route('/v1/posts/', methods=['PATCH']) def edit_post(url): @@ -94,13 +75,14 @@ def edit_post(url): } } PostsController.get_one(url) - data = CheckBody.call(request, required_data={}, optional_data=optional_data) + data = CheckBody.call(request, optional_data=optional_data) CheckPermissions(request, permissions=['POST_WRITE']) PostsController.update_one(url=url, params=data['optional'], optional_data=optional_data) return responses.no_content() + @app.route('/v1/posts/', methods=['DELETE']) def delete_post(url): CheckPermissions(request, permissions=['POST_WRITE']) diff --git a/app/views/users.py b/app/views/users.py index b5b5c25..a2756b2 100644 --- a/app/views/users.py +++ b/app/views/users.py @@ -6,35 +6,27 @@ from app.middlewares.auth import CheckAuth from app.middlewares.permissions import CheckPermissions from core import responses -from core.exceptions import DataError from main import app + @app.route('/v1/users', methods=['GET']) def get_all_users(): - CheckPermissions(request, permissions=['USER_WRITE']) - response = { - 'code': 1, - 'data': UsersController.get_all() - } - return responses.response(response) + CheckPermissions(request, ['USER_WRITE']) + return responses.success(UsersController.get_all()) + @app.route('/v1/users/', methods=['GET']) def get_one_user(username): - response = { - 'code': 1, - 'data': UsersController.get_one(username) - } - return responses.response(response) + return responses.success(UsersController.get_one(username)) + @app.route('/v1/users//permissions', methods=['GET']) def get_user_permissions(username): - CheckPermissions(request, permissions=['USER_WRITE']) - response = { - 'code': 1, - 'data': UsersController.get_user_permissions(username) - } - return responses.response(response) + CheckPermissions(request, ['USER_WRITE']) + return responses.success(UsersController.get_user_permissions(username)) + +""" @app.route('/v1/users', methods=['POST']) def create_user(): required_data = { @@ -52,14 +44,13 @@ def create_user(): 'min_length': 8 } } - try: - data = CheckBody.call(request, required_data=required_data, optional_data={}) - UsersController.create_one(email=data['email'], - username=data['username'], - password=data['password']) - return responses.response({'code': 1}, 201) - except DataError: - return responses.data_error(required_data) + data = CheckBody.call(request, required_data=required_data) + UsersController.create_one(email=data['email'], + username=data['username'], + password=data['password']) + return responses.created() +""" + @app.route('/v1/users/', methods=['PATCH']) def update_profile(username): @@ -85,7 +76,7 @@ def update_profile(username): 'type': 'list' } } - data = CheckBody.call(request, required_data={}, optional_data=optional_data) + data = CheckBody.call(request, optional_data=optional_data) CheckAuth(request) token = request.headers.get('Authorization') if UsersController.get_one_by_token(token)['username'] != username: @@ -95,6 +86,7 @@ def update_profile(username): optional_data=optional_data) return responses.no_content() + @app.route('/v1/users//email', methods=['PATCH']) def update_email(username): required_data = { @@ -104,6 +96,7 @@ def update_email(username): } } + @app.route('/v1/users//password', methods=['PATCH']) def update_password(username): required_data = { @@ -117,6 +110,7 @@ def update_password(username): } } + @app.route('/v1/users//permissions', methods=['PATCH']) def update_permissions(username): required_data = { @@ -124,11 +118,7 @@ def update_permissions(username): 'type': 'list' } } - try: - CheckPermissions(request, permissions=['USER_WRITE']) - request_data = request.json - UsersController.update_permissions(username, request_data['permissions']) - return responses.no_content() - except DataError: - return responses.data_error(required_data) - + CheckPermissions(request, permissions=['USER_WRITE']) + request_data = request.json + UsersController.update_permissions(username, request_data['permissions']) + return responses.no_content() diff --git a/core/config.py b/core/config.py index 79bd5ae..54058a6 100644 --- a/core/config.py +++ b/core/config.py @@ -2,7 +2,6 @@ class Config: - def __init__(self): self.config_path = 'config.json' self.config = json.loads(open(self.config_path, 'r').read()) diff --git a/core/database.py b/core/database.py index 9248756..050c78e 100644 --- a/core/database.py +++ b/core/database.py @@ -4,18 +4,15 @@ class ApiDatabase: - @staticmethod def create(): config = Config() - db = config.get('db', 'db') - host = config.get('db', 'host') - username = config.get('db', 'username') - password = config.get('db', 'password') database = Database() - database.bind(provider='mysql', - host=host, - user=username, - passwd=password, - db=db) + database.bind( + provider='mysql', + host=config.get('db', 'host'), + user=config.get('db', 'username'), + passwd=config.get('db', 'password'), + db=config.get('db', 'db') + ) return database diff --git a/core/exceptions.py b/core/exceptions.py index 4b8bc54..f427ab3 100644 --- a/core/exceptions.py +++ b/core/exceptions.py @@ -1,6 +1,16 @@ class DataError(Exception): + def __init__(self, required_data, optional_data): + self.required_data = required_data + self.optional_data = optional_data + + +class NotUnique(Exception): + pass + + +class InvalidCredentials(Exception): pass -class InvalidCreds(Exception): +class PaginationError(Exception): pass diff --git a/core/responses.py b/core/responses.py index a581bc9..83a0eb0 100644 --- a/core/responses.py +++ b/core/responses.py @@ -1,32 +1,35 @@ -import json +def response(data: dict, code: int): + return data, code -def response(data, code = 200): - return json.dumps(data), code, {'Content-Type': 'application/json'} +def success(data: dict, additional=None, pages: int = 0, code: int = 200): + if additional is None: + additional = {} + returning = { + 'code': 1, + 'data': data, + **additional + } + if pages != 0: + returning['pages'] = pages -def no_content(): - return response('', 204) + return response(returning, code) -def invalid_username_or_password(): - return json.dumps({ +def fail(message: str, additional=None, code: int = 400): + if additional is None: + additional = {} + return response({ 'code': 0, - 'message': 'Invalid email address and/or password' - }), 400, {'Content-Type': 'application/json'} + 'message': message, + **additional + }, code) -def data_error(required_data, optional_data = {}): - return json.dumps({ - 'code': 0, - 'message': 'Data error', - 'required_data': required_data, - 'optional_data': optional_data - }), 400, {'Content-Type': 'application/json'} +def created(): + return success({}, code=201) -def not_unique(): - return json.dumps({ - 'code': 0, - 'message': 'Already exists' - }), 400, {'Content-Type': 'application/json'} \ No newline at end of file +def no_content(): + return success({}, code=204) diff --git a/core/utils/ids.py b/core/utils/ids.py index cb182da..d551d1e 100644 --- a/core/utils/ids.py +++ b/core/utils/ids.py @@ -1,8 +1,13 @@ from random import randint from time import time +from urlify import urlify def generate_id(): timestamp = str(int(time())) - random_number = str(randint(0, 999)) + random_number = str(randint(0, 9999)) return int(timestamp + random_number) + + +def generate_url(url): + return "%s-%s" % (urlify(url), str(int(time()))[6:]) diff --git a/core/utils/pagination.py b/core/utils/pagination.py new file mode 100644 index 0000000..f3988d0 --- /dev/null +++ b/core/utils/pagination.py @@ -0,0 +1,19 @@ +import math + +from pony.orm import db_session + +from core.exceptions import PaginationError + + +@db_session +def paginate(request, posts): + try: + page = request.args.get('page', '1') + page = int(page) + if page < 1: + raise ValueError + except ValueError: + raise PaginationError + pages = math.ceil(len(posts) / 10) + posts = posts.page(page) + return posts, pages diff --git a/main.py b/main.py index 5053481..d87f061 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,9 @@ from flask import Flask +from flask_cors import CORS from core.database import ApiDatabase app = Flask(__name__) +cors = CORS(app, resources={r"/*": {"origins": "*"}}) db = ApiDatabase.create() from app.views import errors, posts, auth, users, blog_posts