Compare commits
36 Commits
master
...
feature/us
Author | SHA1 | Date |
---|---|---|
Derek | 89bfd8756c | |
Derek | 02ea66c2b1 | |
Derek | 650421695c | |
Derek | 00482e29f2 | |
Derek | 25ca7bb9bd | |
Derek | 1bb3e59a46 | |
Derek | f2f215acda | |
Derek | 7e507748c4 | |
Derek | ce29702f50 | |
Derek | c549e1c942 | |
Derek | 93d6c235f5 | |
Derek | 4d7a81c0c9 | |
Derek | 12f086c650 | |
Derek | 680e52a6ab | |
Derek | d8a9565dc3 | |
Derek | 8add5cefda | |
Derek | 58bd4895ce | |
Derek | e4b0f4cff8 | |
Derek | c001137094 | |
Derek | 838637de55 | |
Derek | aa0921aae6 | |
Derek | 333dbf9faa | |
Derek | d729ab8ef0 | |
Derek | d68be11789 | |
Derek | b793963d8f | |
Derek | 76509445ff | |
Derek | c7a9639a29 | |
Derek | e90f6fb215 | |
Derek | 3e5cb113a7 | |
Derek | cee989e772 | |
Derek | edbf1bff5c | |
Derek | 83b386390f | |
Derek | 5662757981 | |
Derek | 3a32127ba2 | |
Derek | 6a63b32f9b | |
Derek | 555f4f57e9 |
|
@ -1 +1,4 @@
|
|||
__pycache__/
|
||||
*.db
|
||||
config.json
|
||||
static/components/
|
||||
|
|
7
Pipfile
7
Pipfile
|
@ -6,6 +6,13 @@ name = "pypi"
|
|||
[packages]
|
||||
flask = "*"
|
||||
gunicorn = "*"
|
||||
flask-login = "*"
|
||||
flask-sqlalchemy = "*"
|
||||
bcrypt = "*"
|
||||
flask-migrate = "*"
|
||||
click = "*"
|
||||
flask-limiter = "*"
|
||||
eventlet = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "81cb5d5f0b11719d8d9c5ec9cc683fdcf959c652fda256d5552a82d0f459a99c"
|
||||
"sha256": "2e5352c6c0be1150d27c750324f7c132acf21f9bb4c9dcd04c8370c96761400a"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -16,13 +16,113 @@
|
|||
]
|
||||
},
|
||||
"default": {
|
||||
"alembic": {
|
||||
"hashes": [
|
||||
"sha256:52d73b1d750f1414fa90c25a08da47b87de1e4ad883935718a8f36396e19e78e",
|
||||
"sha256:eb7db9b4510562ec37c91d00b00d95fde076c1030d3f661aea882eec532b3565"
|
||||
],
|
||||
"version": "==1.0.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"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.4"
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"version": "==1.11.5"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
|
||||
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.7"
|
||||
},
|
||||
"dnspython": {
|
||||
"hashes": [
|
||||
"sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c",
|
||||
"sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31"
|
||||
],
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"eventlet": {
|
||||
"hashes": [
|
||||
"sha256:c584163e006e613707e224552fafc63e4e0aa31d7de0ab18b481ac0b385254c8",
|
||||
"sha256:d9d31a3c8dbcedbcce5859a919956d934685b17323fc80e1077cb344a2ffa68d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.24.1"
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
|
||||
|
@ -31,13 +131,67 @@
|
|||
"index": "pypi",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"gunicorn": {
|
||||
"flask-limiter": {
|
||||
"hashes": [
|
||||
"sha256:7ef2b828b335ed58e3b64ffa84caceb0a7dd7c5ca12f217241350dec36a1d5dc",
|
||||
"sha256:bc59005979efb6d2dd7d5ba72d99f8a8422862ad17ff3a16e900684630dd2a10"
|
||||
"sha256:8cce98dcf25bf2ddbb824c2b503b4fc8e1a139154240fd2c60d9306bad8a0db8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.8.1"
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"flask-login": {
|
||||
"hashes": [
|
||||
"sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"flask-migrate": {
|
||||
"hashes": [
|
||||
"sha256:83ebc105f87357ddd3968f83510d2b1092f006660b1c6ba07a4efce036ca567d",
|
||||
"sha256:cd1b4e6cb829eeb41c02ad9202d83bef5f4b7a036dd9fad72ce96ad1e22efb07"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.1"
|
||||
},
|
||||
"flask-sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b",
|
||||
"sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.2"
|
||||
},
|
||||
"greenlet": {
|
||||
"hashes": [
|
||||
"sha256:0411b5bf0de5ec11060925fd811ad49073fa19f995bcf408839eb619b59bb9f7",
|
||||
"sha256:131f4ed14f0fd28d2a9fa50f79a57d5ed1c8f742d3ccac3d773fee09ef6fe217",
|
||||
"sha256:13510d32f8db72a0b3e1720dbf8cba5c4eecdf07abc4cb631982f51256c453d1",
|
||||
"sha256:31dc4d77ef04ab0460d024786f51466dbbc274fda7c8aad0885a6df5ff8d642e",
|
||||
"sha256:35021d9fecea53b21e4defec0ff3ad69a8e2b75aca1ceddd444a5ba71216547e",
|
||||
"sha256:426a8ef9e3b97c27e841648241c2862442c13c91ec4a48c4a72b262ccf30add9",
|
||||
"sha256:58217698193fb94f3e6ff57eed0ae20381a8d06c2bc10151f76c06bb449a3a19",
|
||||
"sha256:5f45adbbb69281845981bb4e0a4efb8a405f10f3cd6c349cb4a5db3357c6bf93",
|
||||
"sha256:5fdb524767288f7ad161d2182f7ed6cafc0a283363728dcd04b9485f6411547c",
|
||||
"sha256:71fbee1f7ef3fb42efa3761a8faefc796e7e425f528de536cfb4c9de03bde885",
|
||||
"sha256:80bd314157851d06f7db7ca527082dbb0ee97afefb529cdcd59f7a5950927ba0",
|
||||
"sha256:b843c9ef6aed54a2649887f55959da0031595ccfaf7e7a0ba7aa681ffeaa0aa1",
|
||||
"sha256:c6a05ef8125503d2d282ccf1448e3599b8a6bd805c3cdee79760fa3da0ea090e",
|
||||
"sha256:deeda2769a52db840efe5bf7bdf7cefa0ae17b43a844a3259d39fb9465c8b008",
|
||||
"sha256:e66f8b09eec1afdcab947d3a1d65b87b25fde39e9172ae1bec562488335633b4",
|
||||
"sha256:e8db93045414980dbada8908d49dbbc0aa134277da3ff613b3e548cb275bdd37",
|
||||
"sha256:f1cc268a15ade58d9a0c04569fe6613e19b8b0345b64453064e2c3c6d79051af",
|
||||
"sha256:fe3001b6a4f3f3582a865b9e5081cc548b973ec20320f297f5e2d46860e9c703",
|
||||
"sha256:fe85bf7adb26eb47ad53a1bae5d35a28df16b2b93b89042a3a28746617a4738d"
|
||||
],
|
||||
"version": "==0.4.14"
|
||||
},
|
||||
"gunicorn": {
|
||||
"hashes": [
|
||||
"sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471",
|
||||
"sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.9.0"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
|
@ -52,12 +206,64 @@
|
|||
],
|
||||
"version": "==2.10"
|
||||
},
|
||||
"limits": {
|
||||
"hashes": [
|
||||
"sha256:9df578f4161017d79f5188609f1d65f6b639f8aad2914c3960c9252e56a0ff95",
|
||||
"sha256:a017b8d9e9da6761f4574642149c337f8f540d4edfe573fb91ad2c4001a2bc76"
|
||||
],
|
||||
"version": "==1.3"
|
||||
},
|
||||
"mako": {
|
||||
"hashes": [
|
||||
"sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae"
|
||||
],
|
||||
"version": "==1.0.7"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
|
||||
],
|
||||
"version": "==1.0"
|
||||
},
|
||||
"monotonic": {
|
||||
"hashes": [
|
||||
"sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0",
|
||||
"sha256:552a91f381532e33cbd07c6a2655a21908088962bb8fa7239ecbcc6ad1140cc7"
|
||||
],
|
||||
"version": "==1.5"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226"
|
||||
],
|
||||
"version": "==2.18"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0",
|
||||
"sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8"
|
||||
],
|
||||
"version": "==2.7.3"
|
||||
},
|
||||
"python-editor": {
|
||||
"hashes": [
|
||||
"sha256:a3c066acee22a1c94f63938341d4fb374e3fdd69366ed6603d7b24bed1efc565"
|
||||
],
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:ef6569ad403520ee13e180e1bfd6ed71a0254192a934ec1dbd3dbf48f4aa9524"
|
||||
],
|
||||
"version": "==1.2.11"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Share
|
||||
|
||||
because ftp was too easy
|
||||
|
||||
## Quickstart
|
||||
|
||||
It's easy peasy my dude
|
||||
|
||||
1. Install pip and pipenv
|
||||
```bash
|
||||
apt install pip3
|
||||
pip3 install pipenv
|
||||
apt install npm
|
||||
npm install bower
|
||||
```
|
||||
|
||||
2. Get dependencies
|
||||
```bash
|
||||
pipenv install
|
||||
bower install
|
||||
```
|
||||
|
||||
3. Initialize
|
||||
```bash
|
||||
pipenv run python setup.py serve_dir [--secret SECRET_KEY] [--db CONNECTION_STRING]
|
||||
```
|
||||
|
||||
4. Run it
|
||||
+ For testing
|
||||
```bash
|
||||
pipenv shell
|
||||
FLASK_APP="notpiracyiswear.py" flask run
|
||||
```
|
||||
|
||||
+ For production
|
||||
```bash
|
||||
pipenv run gunicorn notpiracyiswear:app -k 'eventlet'
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "skehsucks-share",
|
||||
"authors": [
|
||||
"Derek Schmidt <skehmatics@gmail.com>"
|
||||
],
|
||||
"description": "ftp but worse",
|
||||
"main": "notpiracyiswear.py",
|
||||
"license": "GPL",
|
||||
"homepage": "",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"webcomponentsjs": "^2.0.1",
|
||||
"vaadin-upload": "^4.1.0",
|
||||
"clipboard-copy": "advanced-rest-client/clipboard-copy#^2.0.1",
|
||||
"paper-tooltip": "PolymerElements/paper-tooltip#^2.1.1",
|
||||
"paper-toggle-button": "PolymerElements/paper-toggle-button#^2.1.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"webcomponentsjs": "^v1.1.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Generic single-database configuration.
|
|
@ -0,0 +1,45 @@
|
|||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
|
@ -0,0 +1,88 @@
|
|||
from __future__ import with_statement
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
from logging.config import fileConfig
|
||||
import logging
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
from flask import current_app
|
||||
config.set_main_option('sqlalchemy.url',
|
||||
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
|
||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(url=url, compare_type=True)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
engine = engine_from_config(config.get_section(config.config_ini_section),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool)
|
||||
|
||||
connection = engine.connect()
|
||||
context.configure(connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
compare_type=True,
|
||||
**current_app.extensions['migrate'].configure_args)
|
||||
|
||||
try:
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
|
@ -0,0 +1,24 @@
|
|||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
|
@ -0,0 +1,43 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 49984f04bc27
|
||||
Revises: ea5307f715d1
|
||||
Create Date: 2018-09-08 10:28:27.031642
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '49984f04bc27'
|
||||
down_revision = 'ea5307f715d1'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('user', sa.Column('can_manage', sa.Boolean(), nullable=False, server_default=sa.false()))
|
||||
op.add_column('user', sa.Column('can_refer', sa.Boolean(), nullable=False, server_default=sa.false()))
|
||||
op.add_column('user', sa.Column('can_upload', sa.Boolean(), nullable=False, server_default=sa.false()))
|
||||
user = sa.sql.table('user',
|
||||
sa.sql.column('username', sa.String),
|
||||
sa.sql.column('can_refer', sa.Boolean()),
|
||||
sa.sql.column('can_upload', sa.Boolean()),
|
||||
sa.sql.column('can_manage', sa.Boolean())
|
||||
)
|
||||
op.execute(
|
||||
user.update().\
|
||||
where(user.c.username==op.inline_literal('admin')).\
|
||||
values({'can_refer':op.inline_literal(True), 'can_upload':op.inline_literal(True), 'can_manage':op.inline_literal(True)})
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('user', 'can_upload')
|
||||
op.drop_column('user', 'can_refer')
|
||||
op.drop_column('user', 'can_manage')
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,38 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 8c5a3947f711
|
||||
Revises:
|
||||
Create Date: 2018-05-31 09:50:58.314665
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8c5a3947f711'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('username', sa.String(length=50), nullable=False),
|
||||
sa.Column('password', sa.String(length=64), nullable=False),
|
||||
sa.Column('active', sa.Boolean(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_user_active'), 'user', ['active'], unique=False)
|
||||
op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_user_username'), table_name='user')
|
||||
op.drop_index(op.f('ix_user_active'), table_name='user')
|
||||
op.drop_table('user')
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,38 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: bb298ef84235
|
||||
Revises: 8c5a3947f711
|
||||
Create Date: 2018-05-31 09:53:34.164700
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'bb298ef84235'
|
||||
down_revision = '8c5a3947f711'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('referal',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.String(length=36), nullable=True),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('message', sa.String(), nullable=True),
|
||||
sa.Column('kwargs', sa.PickleType(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_referal_key'), 'referal', ['key'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_referal_key'), table_name='referal')
|
||||
op.drop_table('referal')
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,34 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: ea5307f715d1
|
||||
Revises: bb298ef84235
|
||||
Create Date: 2018-05-31 15:47:34.645982
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ea5307f715d1'
|
||||
down_revision = 'bb298ef84235'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('referal', 'key',
|
||||
existing_type=sa.String(length=36),
|
||||
type_=sa.String(length=43),
|
||||
existing_nullable=True)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('referal', 'key',
|
||||
existing_type=sa.String(length=43),
|
||||
type_=sa.String(length=36),
|
||||
existing_nullable=True)
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,59 @@
|
|||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import UserMixin
|
||||
import bcrypt
|
||||
import secrets
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(50), unique=True, nullable=False, index=True)
|
||||
password = db.Column(db.String(64), nullable=False)
|
||||
|
||||
active = db.Column(db.Boolean(), nullable=False, index=True)
|
||||
can_refer = db.Column(db.Boolean(), nullable=False)
|
||||
can_upload = db.Column(db.Boolean(), nullable=False)
|
||||
can_manage = db.Column(db.Boolean(), nullable=False)
|
||||
|
||||
referals = db.relationship('Referal', backref='user', lazy=True)
|
||||
|
||||
def __init__(self, username, password, active=True, can_refer=False, can_upload=False, can_manage=False):
|
||||
self.username = username
|
||||
self.set_password(password)
|
||||
self.active = active
|
||||
self.can_refer = can_refer
|
||||
self.can_upload = can_upload
|
||||
self.can_manage = can_manage
|
||||
|
||||
def __repr__(self):
|
||||
return "<User {username} : active = {is_active}>".format(username=self.username, is_active=self.active)
|
||||
|
||||
def set_password(self, password):
|
||||
if len(password) > 72:
|
||||
raise AttributeError("Password to hash is longer than 72 characters (bcrypt truncates after 72)")
|
||||
self.password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
|
||||
|
||||
def check_password(self, password):
|
||||
return bcrypt.checkpw(password.encode(), self.password)
|
||||
|
||||
def is_active(self):
|
||||
return self.active
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
class Referal(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
key = db.Column(db.String(43), index=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
message = db.Column(db.String(), nullable=True)
|
||||
kwargs = db.Column(db.PickleType, nullable=True)
|
||||
|
||||
def __init__(self, user, message=None, **kwargs):
|
||||
self.user_id = user.id
|
||||
self.key = secrets.token_urlsafe()
|
||||
self.message = message
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __repr__(self):
|
||||
return "<Referal {id} : user = {user}>".format(id=self.id, user=self.user.username)
|
|
@ -1,24 +1,197 @@
|
|||
from flask import Flask, render_template, send_from_directory, redirect, url_for
|
||||
from flask import Flask, render_template, flash, send_from_directory, redirect, request, session, url_for, abort
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from flask_login import LoginManager, current_user, login_user, logout_user, login_required, fresh_login_required
|
||||
from flask_limiter import Limiter
|
||||
import flask_limiter.util
|
||||
from operator import itemgetter
|
||||
from werkzeug.utils import secure_filename
|
||||
import os
|
||||
import json
|
||||
|
||||
from models import db, User, Referal
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile('settings.cfg')
|
||||
app.config.from_json('config.json')
|
||||
|
||||
db.app = app
|
||||
db.init_app(app)
|
||||
|
||||
migrate = Migrate(app, db)
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = "login"
|
||||
login_manager.login_message = "slap them creds in to continue"
|
||||
login_manager.needs_refresh_message = "no cookies for you - log in properly to continue"
|
||||
login_manager.needs_refresh_message_category = 'info'
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(id):
|
||||
return User.query.get(id)
|
||||
|
||||
limiter = Limiter(app, key_func=flask_limiter.util.get_ipaddr, headers_enabled=True)
|
||||
|
||||
def is_secure_path(path, servepath=None):
|
||||
realpath = os.path.abspath(path) + os.path.sep
|
||||
if servepath is None:
|
||||
servepath = os.path.abspath(app.config['SERVE_DIR'])
|
||||
else:
|
||||
servepath = os.path.abspath(servepath)
|
||||
|
||||
return realpath.startswith(servepath)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
# TODO: Login / user management here
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('browse'))
|
||||
else:
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
@limiter.limit("8/minute;1/second", exempt_when=lambda : request.method == 'GET')
|
||||
def login():
|
||||
if request.method == 'GET':
|
||||
if current_user.is_authenticated:
|
||||
return redirect(request.args.get('next') or url_for('index'))
|
||||
else:
|
||||
return render_template('login.html')
|
||||
else:
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user is None:
|
||||
flash("username / password all fucked up", 'error')
|
||||
return redirect(request.script_root + request.path)
|
||||
|
||||
if user.check_password(password) == True:
|
||||
success = login_user(user)
|
||||
if success:
|
||||
flash("holy cow you are logged in!", 'info')
|
||||
return redirect(request.args.get('next') or url_for('index'))
|
||||
else:
|
||||
flash("oof ouch u banned my dude", 'error')
|
||||
return redirect(request.script_root + request.path)
|
||||
|
||||
else:
|
||||
flash("username / password all fucked up", 'error')
|
||||
return redirect(request.script_root + request.path)
|
||||
|
||||
@app.route('/signup', methods=['GET', 'POST'])
|
||||
@limiter.limit("5/minute;1/second", exempt_when=lambda : request.method == 'GET')
|
||||
def signup():
|
||||
referal_key = request.args.get('referalkey')
|
||||
if not referal_key:
|
||||
return render_template('genericerror.html', message="I don't trust y'all. Signups are by referal only rn.")
|
||||
|
||||
referal = Referal.query.filter_by(key=referal_key).first()
|
||||
if not referal:
|
||||
return render_template('genericerror.html', message="Someone gave you a bad referal code. What a dumbass. \nGo ask for a new one.")
|
||||
if not referal.user.active == True:
|
||||
return render_template('genericerror.html', message="Looks like {user} got a good 'ol banaroni. Referals from them aren't valid anymore. \nSorry pal.".format(user=referal.user.username))
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template('signup.html', referer_username=referal.user.username, referal_message=referal.message or "oh worm? make an account my dude")
|
||||
else:
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
referal_kwargs = referal.kwargs or {}
|
||||
new_user = User(username, password, **referal_kwargs)
|
||||
|
||||
db.session.add(new_user)
|
||||
db.session.delete(referal)
|
||||
db.session.commit()
|
||||
|
||||
login_success = login_user(new_user)
|
||||
if login_success:
|
||||
return redirect(url_for('browse'))
|
||||
else:
|
||||
flash("couldn't log you in because ?? guess you'll have to do it yourself", 'error')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash("bye binch", 'info')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/upload/', methods=['GET', 'POST'])
|
||||
@app.route('/upload/<path:path>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def upload(path=None):
|
||||
if not current_user.can_upload:
|
||||
abort(403)
|
||||
|
||||
path = path if path is not None else ''
|
||||
if not is_secure_path(os.path.join(app.config['SERVE_DIR'], path)):
|
||||
abort(400)
|
||||
|
||||
if not os.path.exists(os.path.join(app.config['SERVE_DIR'], path)):
|
||||
abort(404)
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template('upload.html')
|
||||
else:
|
||||
if 'file' in request.files and secure_filename(request.files['file'].filename) != '':
|
||||
file = request.files['file']
|
||||
filename = secure_filename(file.filename)
|
||||
fullpath = os.path.join(app.config['SERVE_DIR'], path, filename)
|
||||
if os.path.exists(fullpath):
|
||||
return json.dumps({'status': 'error', 'message': "Filename already exists"}), 409
|
||||
file.save(fullpath)
|
||||
relpath = os.path.join(path, filename)
|
||||
return json.dumps({'status': 'success', 'link': url_for('files', path = relpath, _external = True)}), 201
|
||||
else:
|
||||
return json.dumps({'status': 'error', 'message': "No valid file attached to this request"}), 400
|
||||
|
||||
@app.route('/refer', methods=['GET', 'POST'])
|
||||
@fresh_login_required
|
||||
@limiter.limit("50/hour;2/second", key_func=lambda : current_user, exempt_when=lambda : request.method == 'GET')
|
||||
def refer():
|
||||
if not current_user.can_refer:
|
||||
abort(403)
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template('new_referal.html')
|
||||
else:
|
||||
can_refer = request.form.get('refer', 'off') == 'on'
|
||||
can_upload = request.form.get('upload', 'off') == 'on'
|
||||
can_manage = request.form.get('manage', 'off') == 'on'
|
||||
|
||||
if (not current_user.can_refer and can_refer) or (not current_user.can_upload and can_upload) or (not current_user.can_manage and can_manage):
|
||||
flash('You cant just grant permissions you yourself dont have, dingo', 'error')
|
||||
return redirect(url_for('refer'))
|
||||
|
||||
message = request.form.get('message', None)
|
||||
referal = Referal(current_user, message=message, can_refer=can_refer, can_upload=can_upload, can_manage=can_manage)
|
||||
db.session.add(referal)
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for('show_referal', key=referal.key))
|
||||
|
||||
@app.route('/refer/show', methods=['GET'])
|
||||
@login_required
|
||||
def show_referal():
|
||||
if not current_user.can_refer:
|
||||
abort(403)
|
||||
|
||||
return render_template('show_referal.html', key=request.args.get('key'))
|
||||
|
||||
@app.route('/browse/')
|
||||
@app.route('/browse/<path:path>')
|
||||
@login_required
|
||||
def browse(path=None):
|
||||
# Search for files in path
|
||||
searchpath = os.path.join('files', path) if path is not None else 'files'
|
||||
searchpath = os.path.join(app.config['SERVE_DIR'], path) if path is not None else app.config['SERVE_DIR']
|
||||
|
||||
if not is_secure_path(searchpath):
|
||||
abort(400)
|
||||
|
||||
# 404 if no such path exists
|
||||
if not os.path.exists(searchpath):
|
||||
return render_template('404.html'), 404
|
||||
abort(404)
|
||||
|
||||
if not os.path.isdir(searchpath):
|
||||
return redirect(url_for('files', path=path), code=301)
|
||||
|
@ -37,9 +210,35 @@ def browse(path = None):
|
|||
# Render out the item browser
|
||||
return render_template('items.html', files = file_size_isdir_tuple_list, path = os.path.normpath(path) + '/' if path is not None else '')
|
||||
|
||||
# Expecting lots of traffic? Do these via nginx before you kill your server
|
||||
@app.route('/files/<path:path>')
|
||||
@login_required
|
||||
def files(path):
|
||||
return send_from_directory('files', path)
|
||||
if not is_secure_path(os.path.join(app.config['SERVE_DIR'], path)):
|
||||
abort(400)
|
||||
return send_from_directory(app.config['SERVE_DIR'], path)
|
||||
|
||||
@app.route('/components/<path:path>')
|
||||
def components(path):
|
||||
if not is_secure_path(os.path.join('static', 'components', path), servepath=os.path.join('static', 'components')):
|
||||
abort(400)
|
||||
return send_from_directory(os.path.join('static', 'components'), path)
|
||||
|
||||
@app.route('/custom-components/<path:path>')
|
||||
def custom_components(path):
|
||||
if not is_secure_path(os.path.join('static', 'custom-components', path), servepath=os.path.join('static', 'custom-components')):
|
||||
abort(400)
|
||||
return send_from_directory(os.path.join('static', 'custom-components'), path)
|
||||
|
||||
|
||||
# Boring stuff
|
||||
@app.errorhandler(400)
|
||||
def bad_request(e):
|
||||
return render_template('genericerror.html', message="The request you sent didn't check out."), 400
|
||||
|
||||
@app.errorhandler(403)
|
||||
def forbidden(e):
|
||||
return render_template('403.html'), 403
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
|
@ -49,5 +248,9 @@ def page_not_found(e):
|
|||
def internal_error(e):
|
||||
return render_template('500.html'), 500
|
||||
|
||||
@app.errorhandler(429)
|
||||
def rate_limit(e):
|
||||
return render_template('429.html', back=request.script_root + request.path), 429
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
DEBUG = False
|
|
@ -0,0 +1,30 @@
|
|||
from models import User
|
||||
from alembic import command
|
||||
import json
|
||||
import secrets
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.argument("serve_dir")
|
||||
@click.option('--secret', help="Secret key to use. A random hex string will be generated if not provided")
|
||||
@click.option('--db', 'connection', help="Database connection string. Defaults to \"sqlite:///test.db\"")
|
||||
def main(serve_dir, secret=None, connection=None):
|
||||
config = {"SERVE_DIR": serve_dir, "SECRET_KEY": secret or secrets.token_hex(), "SQLALCHEMY_DATABASE_URI": connection or 'sqlite:///test.db'}
|
||||
with open('config.json', 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
# HACK: this is probably not the best way to make sure the built config is applied before we do anything else
|
||||
from notpiracyiswear import app, db, migrate
|
||||
|
||||
db.create_all()
|
||||
admin = User('admin', app.config['SECRET_KEY'], can_refer=True, can_upload=True)
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
|
||||
config = migrate.get_config()
|
||||
with app.app_context():
|
||||
command.stamp(config, 'head', sql=False, tag=None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,352 @@
|
|||
<!--
|
||||
@license
|
||||
Copyright (c) 2017 Vaadin Ltd.
|
||||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
||||
-->
|
||||
|
||||
<link rel="import" href="../components/polymer/polymer-element.html">
|
||||
<link rel="import" href="../components/vaadin-themable-mixin/vaadin-themable-mixin.html">
|
||||
<link rel="import" href="../components/vaadin-progress-bar/src/vaadin-progress-bar.html">
|
||||
<link rel="import" href="../components/vaadin-upload/src/vaadin-upload-icons.html">
|
||||
<link rel="import" href="../components/clipboard-copy/clipboard-copy.html">
|
||||
<link rel="import" href="../components/paper-tooltip/paper-tooltip.html">
|
||||
<!-- chrome is pretty serious about its scopes, so we have to get this twice. -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<dom-module id="vaadin-permalinked-upload-file">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<style include="lumo-field-button">
|
||||
:host {
|
||||
padding: var(--lumo-space-s) 0;
|
||||
}
|
||||
|
||||
:host(:not(:first-child)) {
|
||||
border-top: 1px solid var(--lumo-contrast-10pct);
|
||||
}
|
||||
|
||||
[part="row"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
[part="status"],
|
||||
[part="error"] {
|
||||
color: var(--lumo-secondary-text-color);
|
||||
font-size: var(--lumo-font-size-s);
|
||||
}
|
||||
|
||||
[part="info"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
[part="meta"] {
|
||||
width: 0.001px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
[part="name"] {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
[part="commands"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
[part="done-icon"],
|
||||
[part="warning-icon"] {
|
||||
margin-right: var(--lumo-space-xs);
|
||||
}
|
||||
|
||||
/* When both icons are hidden, let us keep space for one */
|
||||
[part="done-icon"][hidden] + [part="warning-icon"][hidden] {
|
||||
display: block !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
[part="done-icon"],
|
||||
[part="warning-icon"] {
|
||||
font-size: var(--lumo-icon-size-m);
|
||||
font-family: 'lumo-icons';
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
[part="start-button"],
|
||||
[part="retry-button"],
|
||||
[part="clear-button"],
|
||||
[part="copy-button"] {
|
||||
flex: none;
|
||||
margin-left: var(--lumo-space-xs);
|
||||
}
|
||||
|
||||
[part="done-icon"]::before,
|
||||
[part="warning-icon"]::before,
|
||||
[part="start-button"]::before,
|
||||
[part="retry-button"]::before,
|
||||
[part="clear-button"]::before,
|
||||
[part="copy-button"] i {
|
||||
vertical-align: -.25em;
|
||||
}
|
||||
|
||||
[part="done-icon"]::before {
|
||||
content: var(--lumo-icons-checkmark);
|
||||
color: var(--lumo-primary-text-color);
|
||||
}
|
||||
|
||||
[part="warning-icon"]::before {
|
||||
content: var(--lumo-icons-error);
|
||||
color: var(--lumo-error-text-color);
|
||||
}
|
||||
|
||||
[part="start-button"]::before {
|
||||
content: var(--lumo-icons-play);
|
||||
}
|
||||
|
||||
[part="retry-button"]::before {
|
||||
content: var(--lumo-icons-reload);
|
||||
}
|
||||
|
||||
[part="clear-button"]::before {
|
||||
content: var(--lumo-icons-cross);
|
||||
}
|
||||
|
||||
[part="error"] {
|
||||
color: var(--lumo-error-text-color);
|
||||
}
|
||||
|
||||
[part="progress"] {
|
||||
width: auto;
|
||||
margin-left: calc(var(--lumo-icon-size-m) + var(--lumo-space-xs));
|
||||
margin-right: calc(var(--lumo-icon-size-m) + var(--lumo-space-xs));
|
||||
}
|
||||
|
||||
[part="progress"][complete],
|
||||
[part="progress"][error] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div part="row">
|
||||
<div part="info">
|
||||
<div part="done-icon" hidden$="[[!file.complete]]"></div>
|
||||
<div part="warning-icon" hidden$="[[!file.error]]"></div>
|
||||
<slot name="custom-icon"></slot>
|
||||
|
||||
<div part="meta">
|
||||
<div part="name" id="name">[[file.name]]</div>
|
||||
<div part="status" hidden$="[[!file.url]]" id="status">Avalible at <a href="[[file.url]]">[[file.url]]</a></div>
|
||||
<div part="error" id="error" hidden$="[[!file.error]]">[[_errorOrMessage(file.error, file.message)]]</div>
|
||||
</div>
|
||||
</div>
|
||||
<div part="commands">
|
||||
<div part="start-button" file-event="file-start" on-click="_fireFileEvent" hidden$="[[!file.held]]"></div>
|
||||
<div part="retry-button" file-event="file-retry" on-click="_fireFileEvent" hidden$="[[!file.error]]"></div>
|
||||
<div part="clear-button" file-event="file-abort" on-click="_fireFileEvent" hidden$="[[file.complete]]"></div>
|
||||
<div part="copy-button" on-click="_copyUrl" hidden$="[[!file.complete]]">
|
||||
<clipboard-copy id="clipboardomatic" content="[[file.url]]"></clipboard-copy>
|
||||
<i class="material-icons">file_copy</i>
|
||||
</div>
|
||||
</div>
|
||||
<paper-tooltip id="copied_notif" for="copy-button" position="right">Copied to clipboard</paper-tooltip>
|
||||
</div>
|
||||
|
||||
<vaadin-progress-bar
|
||||
part="progress"
|
||||
id="progress"
|
||||
value$="[[_formatProgressValue(file.progress)]]"
|
||||
error$="[[file.error]]"
|
||||
indeterminate$="[[file.indeterminate]]"
|
||||
uploading$="[[file.uploading]]"
|
||||
complete$="[[file.complete]]"
|
||||
hidden$="[[file.complete]]">
|
||||
</vaadin-progress-bar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* `<vaadin-permalinked-upload-file>` element represents a file in the file list of `<vaadin-upload>`.
|
||||
*
|
||||
* ### Styling
|
||||
*
|
||||
* The following shadow DOM parts are available for styling:
|
||||
*
|
||||
* Part name | Description
|
||||
* ---|---
|
||||
* `row` | File container
|
||||
* `info` | Container for file status icon, file name, status and error messages
|
||||
* `done-icon` | File done status icon
|
||||
* `warning-icon` | File warning status icon
|
||||
* `meta` | Container for file name, status and error messages
|
||||
* `name` | File name
|
||||
* `error` | Error message, shown when error happens
|
||||
* `status` | Status message
|
||||
* `commands` | Container for file command icons
|
||||
* `start-button` | Start file upload button
|
||||
* `retry-button` | Retry file upload button
|
||||
* `clear-button` | Clear file button
|
||||
* `progress`| Progress bar
|
||||
*
|
||||
* The following state attributes are available for styling:
|
||||
*
|
||||
* Attribute | Description | Part name
|
||||
* ---|---|---
|
||||
* `error` | An error has happened during uploading | `:host`
|
||||
* `indeterminate` | Uploading is in progress, but the progress value is unknown | `:host`
|
||||
* `uploading` | Uploading is in progress | `:host`
|
||||
* `complete` | Uploading has finished successfully | `:host`
|
||||
*
|
||||
* See [ThemableMixin – how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki)
|
||||
*
|
||||
* @memberof Vaadin
|
||||
* @mixes Vaadin.ThemableMixin
|
||||
* @demo demo/index.html
|
||||
*/
|
||||
class PermalinkedUploadFileElement extends Vaadin.ThemableMixin(Polymer.Element) {
|
||||
static get is() {
|
||||
return 'vaadin-permalinked-upload-file';
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
file: Object
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_fileAborted(file.abort)',
|
||||
'_toggleHostAttribute(file.error, "error")',
|
||||
'_toggleHostAttribute(file.indeterminate, "indeterminate")',
|
||||
'_toggleHostAttribute(file.uploading, "uploading")',
|
||||
'_toggleHostAttribute(file.complete, "complete")',
|
||||
];
|
||||
}
|
||||
|
||||
_copyUrl(event) {
|
||||
var success = this.$.clipboardomatic.copy();
|
||||
if (success) {
|
||||
var notif = this.$.copied_notif;
|
||||
notif.show();
|
||||
window.setTimeout(function() {
|
||||
notif.hide();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
_errorOrMessage(error, message) {
|
||||
if (message) {
|
||||
return message;
|
||||
} else {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
_fileAborted(abort) {
|
||||
if (abort) {
|
||||
this._remove();
|
||||
}
|
||||
}
|
||||
|
||||
_remove() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('file-remove', {
|
||||
detail: {file: this.file},
|
||||
bubbles: true,
|
||||
composed: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_formatProgressValue(progress) {
|
||||
return progress / 100;
|
||||
}
|
||||
|
||||
_fireFileEvent(e) {
|
||||
e.preventDefault();
|
||||
return this.dispatchEvent(
|
||||
new CustomEvent(e.target.getAttribute('file-event'), {
|
||||
detail: {file: this.file},
|
||||
bubbles: true,
|
||||
composed: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_toggleHostAttribute(value, attributeName) {
|
||||
const shouldHave = Boolean(value);
|
||||
const has = this.hasAttribute(attributeName);
|
||||
if (has !== shouldHave) {
|
||||
if (shouldHave) {
|
||||
this.setAttribute(attributeName, '');
|
||||
} else {
|
||||
this.removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the retry button is pressed. It is listened by `vaadin-upload`
|
||||
* which will start a new upload process of this file.
|
||||
*
|
||||
* @event file-retry
|
||||
* @param {Object} detail
|
||||
* @param {Object} detail.file file to retry upload of
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the start button is pressed. It is listened by `vaadin-upload`
|
||||
* which will start a new upload process of this file.
|
||||
*
|
||||
* @event file-start
|
||||
* @param {Object} detail
|
||||
* @param {Object} detail.file file to start upload of
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when abort button is pressed. It is listened by `vaadin-upload` which
|
||||
* will abort the upload in progress, but will not remove the file from the list
|
||||
* to allow the animation to hide the element to be run.
|
||||
*
|
||||
* @event file-abort
|
||||
* @param {Object} detail
|
||||
* @param {Object} detail.file file to abort upload of
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired after the animation to hide the element has finished. It is listened
|
||||
* by `vaadin-upload` which will actually remove the file from the upload
|
||||
* file list.
|
||||
*
|
||||
* @event file-remove
|
||||
* @param {Object} detail
|
||||
* @param {Object} detail.file file to remove from the upload of
|
||||
*/
|
||||
}
|
||||
|
||||
customElements.define(PermalinkedUploadFileElement.is, PermalinkedUploadFileElement);
|
||||
|
||||
/**
|
||||
* @namespace Vaadin
|
||||
*/
|
||||
window.Vaadin = window.Vaadin || {};
|
||||
Vaadin.PermalinkedUploadFileElement = PermalinkedUploadFileElement;
|
||||
})();
|
||||
</script>
|
||||
</dom-module>
|
|
@ -1,4 +0,0 @@
|
|||
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 209 B |
|
@ -1,4 +0,0 @@
|
|||
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/>
|
||||
<path d="M0-.25h24v24H0z" fill="none"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 214 B |
227
static/style.css
227
static/style.css
|
@ -4,40 +4,44 @@ body {
|
|||
--trinary-background-color: #B0BEC5;
|
||||
--foreground-color: #000000;
|
||||
--secondary-foreground-color: #424242;
|
||||
--foreground-disabled-color: #9e9e9e;
|
||||
|
||||
--info-color: #90caf9;
|
||||
--error-color: #f06292;
|
||||
--error-color-light: #f48fb1;
|
||||
|
||||
|
||||
background-color: var(--background-color);
|
||||
color: var(--foreground-color);
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
p, b, i {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
#mainwrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex: auto;
|
||||
flex-grow: 3;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
#contentarea {
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
max-width: 600px;
|
||||
min-width: 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: auto;
|
||||
padding: 8px 16px;
|
||||
margin: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#contentarea > *:not(:last-child) {
|
||||
margin-top: 0px;
|
||||
|
@ -51,6 +55,116 @@ p, b, i {
|
|||
color: var(--secondary-foreground-color);
|
||||
}
|
||||
|
||||
#login_form {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
input:not([type=file]) {
|
||||
height: 40px;
|
||||
background-color: var(--secondary-background-color);
|
||||
border-style: none;
|
||||
color: inherit;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
height: 40px;
|
||||
/* background-color: var(--secondary-background-color); */
|
||||
border-style: dashed;
|
||||
border-color: var(--trinary-background-color);
|
||||
border-width: 2px;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
|
||||
button {
|
||||
transition: background cubic-bezier(0.4, 0, 0.2, 1) 0.28s;
|
||||
background-color: var(--secondary-background-color);
|
||||
color: inherit;
|
||||
border-style: none;
|
||||
}
|
||||
button:active {
|
||||
transition: background cubic-bezier(0.4, 0, 0.2, 1) 0.05s;
|
||||
background-color: var(--info-color);
|
||||
}
|
||||
|
||||
#login_form > button {
|
||||
height: 38px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
#login_form > *:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
form > div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#custom_message_text {
|
||||
margin: 12px 0px;
|
||||
}
|
||||
|
||||
form > button {
|
||||
margin-top: 32px;
|
||||
padding: 12px 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[disabled] + .checkboxtext {
|
||||
color: var(--foreground-disabled-color);
|
||||
}
|
||||
|
||||
#link_copy {
|
||||
margin-top: 12px;
|
||||
color: var(--secondary-foreground-color);
|
||||
}
|
||||
|
||||
@keyframes fade { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes slide { from { bottom: 0px; } to { bottom: -100%; } }
|
||||
|
||||
#flash_wrapper {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
animation: slide cubic-bezier(0.4, 0, 0.2, 1) 1, fade 1;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 4s, 4.5s;
|
||||
animation-duration: 1s, 0.5s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.flash {
|
||||
width: 100%;
|
||||
padding: 16px 32px;
|
||||
}
|
||||
.flash > p {
|
||||
margin: 0px;
|
||||
}
|
||||
.flash {
|
||||
background-color: var(--info-color);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { background-color: var(--error-color); }
|
||||
20% { background-color: var(--error-color-light); }
|
||||
100% { background-color: var(--error-color); }
|
||||
}
|
||||
.flash.error {
|
||||
background-color: var(--error-color);
|
||||
animation: pulse ease 2;
|
||||
animation-fill-mode: backwards;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
|
||||
.itemwrapper {
|
||||
transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background-color: var(--secondary-background-color);
|
||||
|
@ -65,14 +179,53 @@ p, b, i {
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 4px 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
flex: auto;
|
||||
min-width: 1%;
|
||||
word-wrap: break-word;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.item-size {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.item-icon {
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
#headerbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
margin: 16px;
|
||||
padding: 0px;
|
||||
z-index: 10;
|
||||
}
|
||||
#headerbar > .iconbutton {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#headerbar-text {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin: 0px;
|
||||
margin-right: auto;
|
||||
display: none;
|
||||
}
|
||||
.iconbutton {
|
||||
padding: 4px;
|
||||
}
|
||||
.iconbutton > img {
|
||||
width: 32px;
|
||||
}
|
||||
.iconbutton > i {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
a.nostyle:link {
|
||||
text-decoration: inherit;
|
||||
|
@ -82,27 +235,59 @@ a.nostyle:visited {
|
|||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
#waifuwrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
button.nostyle {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
@media only screen and (max-width: 780px) {
|
||||
#waifu {
|
||||
display: none;
|
||||
}
|
||||
#waifu-lazyflexhack {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
#waifu-lazyflexhack {
|
||||
|
||||
#waifu-spacesaver {
|
||||
width: 100%;
|
||||
max-width: 30vw;
|
||||
max-height: 100vh;
|
||||
visibility: hidden;
|
||||
}
|
||||
#waifu {
|
||||
/* visibility: hidden; */
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
z-index: -1;
|
||||
max-width: 30vw;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 780px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
}
|
||||
#headerbar {
|
||||
background-color: var(--info-color);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||
flex-direction: row-reverse;
|
||||
position: sticky;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
margin: 0px;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
#headerbar > .iconbutton {
|
||||
margin-bottom: 0px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
#headerbar-text {
|
||||
display: inline-block;
|
||||
}
|
||||
.iconbutton > img {
|
||||
width: 24px;
|
||||
}
|
||||
.iconbutton > i {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#waifu {
|
||||
display: none;
|
||||
}
|
||||
#waifu-spacesaver {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'error.html' %}
|
||||
{% block head %}
|
||||
Get out
|
||||
{% endblock %}
|
||||
{% block disc %}
|
||||
You're not welcome here >:c
|
||||
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'error.html' %}
|
||||
{% block head %}
|
||||
sTOP IT
|
||||
{% endblock %}
|
||||
{% block disc %}
|
||||
Whoa buddy calm down, you're going too fast. Go drink some tea and then <a href="{{ back }}">try again.</a>
|
||||
{% endblock %}
|
|
@ -1,39 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
|
||||
|
||||
<title>it's not piracy i swear</title>
|
||||
<meta name="description" content="ftp but worse">
|
||||
|
||||
<link href="/static/style.css" type="text/css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mainwrapper">
|
||||
<div id="contentarea">
|
||||
{% for file, size in files %}
|
||||
<a class="nostyle itemwrapper" href="/files/{{ file }}" download>
|
||||
<div class="item">
|
||||
<p class="item-name">{{ file }}</p>
|
||||
<p class="item-size">{{ size }}</p>
|
||||
</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<p id="nofileshead">
|
||||
It's fucking nothing!
|
||||
</p>
|
||||
<p id="nofilesdisc">
|
||||
No files avalible yet. Come back later, eh?
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="waifuwrapper">
|
||||
<img id="waifu-lazyflexhack" src="/static/obligitorywaifu.png"></img>
|
||||
<!-- <div id="waifu-spacesaver"></div> -->
|
||||
</div>
|
||||
<img id="waifu" src="/static/obligitorywaifu.png"></img>
|
||||
</body>
|
||||
</html>
|
|
@ -9,7 +9,7 @@
|
|||
<div class="item">
|
||||
<p class="item-name">{{ file }}</p>
|
||||
{% if isdir %}
|
||||
<img src="{{ url_for('static', filename='ic_chevron_right_black.svg') }}" />
|
||||
<i class="item-icon material-icons">chevron_right</i>
|
||||
{% else %}
|
||||
<p class="item-size">{{ size|filesizeformat }}</p>
|
||||
{% endif %}
|
||||
|
@ -29,7 +29,7 @@
|
|||
<p class="item-name">
|
||||
Back
|
||||
</p>
|
||||
<img src="{{ url_for('static', filename='ic_chevron_left_black.svg') }}" />
|
||||
<i class="item-icon material-icons">chevron_left</i>
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{% extends 'skel.html' %}
|
||||
{% block body %}
|
||||
<div id="mainwrapper">
|
||||
<div id="contentarea">
|
||||
<form id="login_form" action="" method="post">
|
||||
<input id="username_input" name="username" type="text" placeholder="Username" required></input>
|
||||
<input id="password_input" name="password" type="password" placeholder="Password" required></input>
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -6,8 +6,6 @@
|
|||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="waifuwrapper">
|
||||
<img id="waifu-lazyflexhack" src="{{ url_for('static', filename='obligitorywaifu.png') }}"></img>
|
||||
</div>
|
||||
<div id="waifu-spacesaver"></div>
|
||||
<img id="waifu" src="{{ url_for('static', filename='obligitorywaifu.png') }}"></img>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
{% extends 'skel.html' %}
|
||||
{% block body %}
|
||||
<div id="mainwrapper">
|
||||
<div id="contentarea">
|
||||
<p id="infohead">
|
||||
Sharing is <span style='text-decoration: line-through;'>communism</span> caring
|
||||
</p>
|
||||
<p id="infodisc">
|
||||
Referal links can be used only once to register a new user on this site, giving access to all it's content.
|
||||
Give out with caution.
|
||||
</p>
|
||||
<form id="referal_form" action="" method="post">
|
||||
<div id="refer_container">
|
||||
<input id="refer_permission_checkbox" name="refer" type="checkbox"></input>
|
||||
<p class="checkboxtext">Allow refering other users</p>
|
||||
</div>
|
||||
<div id="upload_container">
|
||||
<input id="upload_permission_checkbox" name="upload" type="checkbox" {{ "disabled" if not current_user.can_upload }}></input>
|
||||
<p class="checkboxtext">Allow uploading new files</p>
|
||||
</div>
|
||||
<div id="admin_container">
|
||||
<input id="admin_permission_checkbox" name="manage" type="checkbox" {{ "disabled" if not current_user.can_manage }}></input>
|
||||
<p class="checkboxtext">Allow administrative actions</p>
|
||||
</div>
|
||||
<input id="custom_message_text" name="message" placeholder="Custom message shown on signup page"></input>
|
||||
<button type="submit">Generate referal link</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,24 @@
|
|||
{% extends 'skel.html' %}
|
||||
{% block body %}
|
||||
<div id="mainwrapper">
|
||||
<div id="contentarea">
|
||||
<p id="infohead">
|
||||
Sharing is <span style='text-decoration: line-through;'>communism</span> caring
|
||||
</p>
|
||||
<p id="infodisc">
|
||||
Here's your shiny new referal link:
|
||||
</p>
|
||||
<input id="link_copy" readonly value="{{ url_for('signup', referalkey=key, _external=True) }}"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelector('#link_copy').onclick = function(event) {
|
||||
var sel, range;
|
||||
sel = window.getSelection();
|
||||
if(sel.toString() == '') {
|
||||
event.target.select();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
|||
{% extends 'skel.html' %}
|
||||
{% block body %}
|
||||
<div id="mainwrapper">
|
||||
<div id="contentarea">
|
||||
<p id="infohead">
|
||||
{{ referal_message }}
|
||||
</p>
|
||||
<p id="infodisc">
|
||||
- {{ referer_username }}
|
||||
</p>
|
||||
<form id="login_form" action="" method="post" style="padding-top: 32px;">
|
||||
<input id="username_input" name="username" type="text" placeholder="Username" required></input>
|
||||
<input id="password_input" name="password" type="password" placeholder="Password" required></input>
|
||||
<input id="password_confirm_input" name="password" type="password" placeholder="Confirm password" required></input>
|
||||
<button type="submit">Sign up</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var password = document.getElementById("password_input"), confirm_password = document.getElementById("password_confirm_input");
|
||||
|
||||
function validatePassword(){
|
||||
if(password.value != confirm_password.value) {
|
||||
confirm_password.setCustomValidity("Passwords don't match");
|
||||
} else {
|
||||
confirm_password.setCustomValidity('');
|
||||
}
|
||||
}
|
||||
|
||||
password.onchange = validatePassword;
|
||||
confirm_password.onkeyup = validatePassword;
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -7,13 +7,46 @@
|
|||
<title>it's not piracy i swear</title>
|
||||
<meta name="description" content="ftp but worse">
|
||||
|
||||
<link href="{{ url_for('static', filename='style.css') }}" type="text/css" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='style.css') }}" type="text/css" rel="stylesheet" />
|
||||
{% block imports %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% if current_user.is_authenticated %}
|
||||
<div id="headerbar">
|
||||
<a class="nostyle iconbutton" href="{{ url_for('logout') }}" title="Logout">
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
</a>
|
||||
{% if current_user.can_refer %}
|
||||
<a class="nostyle iconbutton" href="{{ url_for('refer') }}" title="Add new user">
|
||||
<i class="material-icons">person_add</i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if current_user.can_upload and path is defined %}
|
||||
<a class="nostyle iconbutton" href="{{ url_for('upload', path=path) }}" title="Upload to current directory">
|
||||
<i class="material-icons">cloud_upload</i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<p id="headerbar-text">it's not piracy, it's digiorno</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block body %}
|
||||
{% endblock %}
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div id="flash_wrapper">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash {{ category }}">
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
{% extends 'skel.html' %}
|
||||
{% block imports %}
|
||||
<script src="{{ url_for('components', path='webcomponentsjs/webcomponents-loader.js') }}"></script>
|
||||
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-bind.html') }}">
|
||||
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-repeat.html') }}">
|
||||
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-if.html') }}">
|
||||
<link rel="import" href="{{ url_for('components', path='vaadin-upload/vaadin-upload.html') }}">
|
||||
<link rel="import" href="{{ url_for('custom_components', path='vaadin-permalinked-upload-file.html') }}">
|
||||
<link rel="import" href="{{ url_for('components', path='paper-toggle-button/paper-toggle-button.html') }}">
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div id="mainwrapper">
|
||||
<div id="contentarea">
|
||||
<p id="infohead">
|
||||
Files for the file gods
|
||||
</p>
|
||||
<p id="infodisc">
|
||||
Get your upload on my dude.
|
||||
</p>
|
||||
<form class="noscript" action="" method="POST" enctype="multipart/form-data">
|
||||
<input id="upload_input" name="files" type="file" accept="image/*"></input>
|
||||
<button id="upload_button" type="submit">Upload</button>
|
||||
</form>
|
||||
<dom-bind>
|
||||
<template>
|
||||
<vaadin-upload id="file_upload" files="{%raw%}{{files}}{%endraw%}" target="" method="POST" form-data-name="file" nodrop="[[nodrop]]">
|
||||
<style is="custom-style">
|
||||
[nodrop] [part="upload-button"] {
|
||||
width: 100%;
|
||||
}
|
||||
[nodrop] #addFiles {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<div slot="drop-label-icon"></div>
|
||||
<span slot="drop-label" class="font-headline">or drag a file here</span>
|
||||
<div slot="file-list">
|
||||
<template is="dom-repeat" items="[[files]]" as="file">
|
||||
<vaadin-permalinked-upload-file file="[[file]]"></vaadin-permalinked-upload-file>
|
||||
</template>
|
||||
</div>
|
||||
</vaadin-upload>
|
||||
</template>
|
||||
</dom-bind>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function isES6()
|
||||
{
|
||||
try { Function("() => {};"); return true; }
|
||||
catch(exception) { return false; }
|
||||
}
|
||||
|
||||
window.addEventListener('WebComponentsReady', function() {
|
||||
// Remove noscript / dinosaur componenets if both webcomponents and es6 avalible
|
||||
if (isES6()){
|
||||
var noscripts = document.querySelectorAll('.noscript');
|
||||
for (var i = noscripts.length-1; i >= 0; i--){
|
||||
noscripts[i].parentNode.removeChild(noscripts[i]);
|
||||
}
|
||||
} else {
|
||||
// TODO: Abort polymer loading to fix IE become ing responsive for a bit
|
||||
}
|
||||
|
||||
var upload = document.querySelector('vaadin-upload#file_upload');
|
||||
var binder = document.querySelector('dom-bind');
|
||||
|
||||
upload.addEventListener('upload-response', function(event) {
|
||||
response = JSON.parse(event.detail.xhr.response);
|
||||
console.log(response);
|
||||
if (response.status == 'success'){
|
||||
event.detail.file.url = response.link;
|
||||
} else {
|
||||
event.detail.file.message = response.message;
|
||||
}
|
||||
});
|
||||
|
||||
binder.files = [];
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
Reference in New Issue