├── .envexample ├── .flaskenv ├── .gitignore ├── .vscode ├── settings 2.json └── settings.json ├── Pipfile ├── Pipfile.lock ├── Procfile ├── README.md ├── app ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38 2.pyc │ ├── __init__.cpython-38.pyc │ ├── config.cpython-38 2.pyc │ ├── config.cpython-38.pyc │ ├── models.cpython-38 2.pyc │ ├── models.cpython-38.pyc │ ├── schema.cpython-38 2.pyc │ └── schema.cpython-38.pyc ├── auth.py ├── config.py ├── filters.py ├── models.py ├── schema.py └── types.py ├── customCraftsApi.py ├── migrations ├── README ├── alembic.ini ├── env.py ├── script.py.mako └── versions │ ├── 5842dab8d19b_add_vote_counts_to_review_model.py │ ├── 82e82c504529_.py │ ├── c783ac6f8c44_add_total_sold_field_to_ship_and_.py │ ├── cbc821e170fa_init_migration.py │ └── f61ec522d535_add_color_to_order_item.py └── seed.py /.envexample: -------------------------------------------------------------------------------- 1 | FLASK_ENV=development 2 | SECRET_KEY=secrety_key 3 | DATABASE_URL=sample_database_url -------------------------------------------------------------------------------- /.flaskenv: -------------------------------------------------------------------------------- 1 | FLASK_APP=customCraftsApi.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .venv -------------------------------------------------------------------------------- /.vscode/settings 2.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/Users/sarahhenry/Desktop/appAcademy/independentProjects/Custom-Crafts-api/.venv/bin/python3.8" 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/Users/sarahhenry/Desktop/appAcademy/independentProjects/Custom-Crafts-api/.venv/bin/python3.8" 3 | } -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | flask = "*" 10 | flask-sqlalchemy = "*" 11 | psycopg2-binary = "*" 12 | alembic = "*" 13 | flask-migrate = "*" 14 | python-dotenv = "*" 15 | python-jose = "*" 16 | flask-cors = "*" 17 | six = "*" 18 | sqlalchemy = "*" 19 | graphene = "*" 20 | graphene-sqlalchemy = "*" 21 | flask-graphql = "*" 22 | graphene-sqlalchemy-filter = "*" 23 | gunicorn = "*" 24 | 25 | [requires] 26 | python_version = "3.8" 27 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "f678158e1856f49a78d0a41176943bb6a1bc0fc0c7f2928075b64f4d6f6d4082" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "alembic": { 20 | "hashes": [ 21 | "sha256:035ab00497217628bf5d0be82d664d8713ab13d37b630084da8e1f98facf4dbf" 22 | ], 23 | "index": "pypi", 24 | "version": "==1.4.2" 25 | }, 26 | "aniso8601": { 27 | "hashes": [ 28 | "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e", 29 | "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b" 30 | ], 31 | "version": "==7.0.0" 32 | }, 33 | "click": { 34 | "hashes": [ 35 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 36 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 37 | ], 38 | "version": "==7.1.2" 39 | }, 40 | "ecdsa": { 41 | "hashes": [ 42 | "sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061", 43 | "sha256:8f12ac317f8a1318efa75757ef0a651abe12e51fc1af8838fb91079445227277" 44 | ], 45 | "version": "==0.15" 46 | }, 47 | "flask": { 48 | "hashes": [ 49 | "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", 50 | "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" 51 | ], 52 | "index": "pypi", 53 | "version": "==1.1.2" 54 | }, 55 | "flask-cors": { 56 | "hashes": [ 57 | "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", 58 | "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a" 59 | ], 60 | "index": "pypi", 61 | "version": "==3.0.8" 62 | }, 63 | "flask-graphql": { 64 | "hashes": [ 65 | "sha256:825578c044df436cd74503a38bbd31c919a90acda5e9b6e0e45736964bc5235d" 66 | ], 67 | "index": "pypi", 68 | "version": "==2.0.1" 69 | }, 70 | "flask-migrate": { 71 | "hashes": [ 72 | "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732", 73 | "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee" 74 | ], 75 | "index": "pypi", 76 | "version": "==2.5.3" 77 | }, 78 | "flask-sqlalchemy": { 79 | "hashes": [ 80 | "sha256:0b656fbf87c5f24109d859bafa791d29751fabbda2302b606881ae5485b557a5", 81 | "sha256:fcfe6df52cd2ed8a63008ca36b86a51fa7a4b70cef1c39e5625f722fca32308e" 82 | ], 83 | "index": "pypi", 84 | "version": "==2.4.3" 85 | }, 86 | "graphene": { 87 | "hashes": [ 88 | "sha256:09165f03e1591b76bf57b133482db9be6dac72c74b0a628d3c93182af9c5a896", 89 | "sha256:2cbe6d4ef15cfc7b7805e0760a0e5b80747161ce1b0f990dfdc0d2cf497c12f9" 90 | ], 91 | "index": "pypi", 92 | "version": "==2.1.8" 93 | }, 94 | "graphene-sqlalchemy": { 95 | "hashes": [ 96 | "sha256:2b1a9cf4ed44aec78140605f38061a79b51be5902400d10c3d19b2cf64046215", 97 | "sha256:97ed52bc0d01d757df50d25b5bdd490a2327778d41223d4e084d38a239925e8e" 98 | ], 99 | "index": "pypi", 100 | "version": "==2.3.0" 101 | }, 102 | "graphene-sqlalchemy-filter": { 103 | "hashes": [ 104 | "sha256:688ce24e6cce1da070f1f6b77866d3bae8564e88a08585b6005e2f4439a1be25", 105 | "sha256:cff78b7cca7718777107c8041d0cbae678e8b080742437ff3d71c5974a16613e" 106 | ], 107 | "index": "pypi", 108 | "version": "==1.10.2" 109 | }, 110 | "graphql-core": { 111 | "hashes": [ 112 | "sha256:44c9bac4514e5e30c5a595fac8e3c76c1975cae14db215e8174c7fe995825bad", 113 | "sha256:aac46a9ac524c9855910c14c48fc5d60474def7f99fd10245e76608eba7af746" 114 | ], 115 | "version": "==2.3.2" 116 | }, 117 | "graphql-relay": { 118 | "hashes": [ 119 | "sha256:870b6b5304123a38a0b215a79eace021acce5a466bf40cd39fa18cb8528afabb", 120 | "sha256:ac514cb86db9a43014d7e73511d521137ac12cf0101b2eaa5f0a3da2e10d913d" 121 | ], 122 | "version": "==2.0.1" 123 | }, 124 | "graphql-server-core": { 125 | "hashes": [ 126 | "sha256:04ee90da0322949f7b49ff6905688e3a21a9efbd5a7d7835997e431a0afdbd11" 127 | ], 128 | "version": "==1.2.0" 129 | }, 130 | "gunicorn": { 131 | "hashes": [ 132 | "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", 133 | "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" 134 | ], 135 | "index": "pypi", 136 | "version": "==20.0.4" 137 | }, 138 | "itsdangerous": { 139 | "hashes": [ 140 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 141 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 142 | ], 143 | "version": "==1.1.0" 144 | }, 145 | "jinja2": { 146 | "hashes": [ 147 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", 148 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" 149 | ], 150 | "version": "==2.11.2" 151 | }, 152 | "mako": { 153 | "hashes": [ 154 | "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27", 155 | "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9" 156 | ], 157 | "version": "==1.1.3" 158 | }, 159 | "markupsafe": { 160 | "hashes": [ 161 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 162 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 163 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 164 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 165 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 166 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 167 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 168 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 169 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 170 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 171 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 172 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 173 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 174 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 175 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 176 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 177 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 178 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 179 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 180 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 181 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 182 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 183 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 184 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 185 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 186 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 187 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 188 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 189 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 190 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 191 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 192 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 193 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 194 | ], 195 | "version": "==1.1.1" 196 | }, 197 | "promise": { 198 | "hashes": [ 199 | "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0" 200 | ], 201 | "version": "==2.3" 202 | }, 203 | "psycopg2-binary": { 204 | "hashes": [ 205 | "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac", 206 | "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a", 207 | "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5", 208 | "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04", 209 | "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1", 210 | "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5", 211 | "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce", 212 | "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434", 213 | "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9", 214 | "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057", 215 | "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98", 216 | "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522", 217 | "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505", 218 | "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa", 219 | "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3", 220 | "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f", 221 | "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4", 222 | "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4", 223 | "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266", 224 | "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66", 225 | "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38", 226 | "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3", 227 | "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389", 228 | "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab", 229 | "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb", 230 | "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6", 231 | "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d", 232 | "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162", 233 | "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e", 234 | "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd" 235 | ], 236 | "index": "pypi", 237 | "version": "==2.8.5" 238 | }, 239 | "pyasn1": { 240 | "hashes": [ 241 | "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", 242 | "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" 243 | ], 244 | "version": "==0.4.8" 245 | }, 246 | "python-dateutil": { 247 | "hashes": [ 248 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", 249 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" 250 | ], 251 | "version": "==2.8.1" 252 | }, 253 | "python-dotenv": { 254 | "hashes": [ 255 | "sha256:25c0ff1a3e12f4bde8d592cc254ab075cfe734fc5dd989036716fd17ee7e5ec7", 256 | "sha256:3b9909bc96b0edc6b01586e1eed05e71174ef4e04c71da5786370cebea53ad74" 257 | ], 258 | "index": "pypi", 259 | "version": "==0.13.0" 260 | }, 261 | "python-editor": { 262 | "hashes": [ 263 | "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", 264 | "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", 265 | "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8" 266 | ], 267 | "version": "==1.0.4" 268 | }, 269 | "python-jose": { 270 | "hashes": [ 271 | "sha256:1ac4caf4bfebd5a70cf5bd82702ed850db69b0b6e1d0ae7368e5f99ac01c9571", 272 | "sha256:8484b7fdb6962e9d242cce7680469ecf92bda95d10bbcbbeb560cacdff3abfce" 273 | ], 274 | "index": "pypi", 275 | "version": "==3.1.0" 276 | }, 277 | "rsa": { 278 | "hashes": [ 279 | "sha256:109ea5a66744dd859bf16fe904b8d8b627adafb9408753161e766a92e7d681fa", 280 | "sha256:23778f5523461cf86ae075f9482a99317f362bca752ae57cb118044066f4026f" 281 | ], 282 | "version": "==4.6" 283 | }, 284 | "rx": { 285 | "hashes": [ 286 | "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23", 287 | "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105" 288 | ], 289 | "version": "==1.6.1" 290 | }, 291 | "singledispatch": { 292 | "hashes": [ 293 | "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c", 294 | "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8" 295 | ], 296 | "version": "==3.4.0.3" 297 | }, 298 | "six": { 299 | "hashes": [ 300 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 301 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 302 | ], 303 | "index": "pypi", 304 | "version": "==1.15.0" 305 | }, 306 | "sqlalchemy": { 307 | "hashes": [ 308 | "sha256:128bc917ed20d78143a45024455ff0aed7d3b96772eba13d5dbaf9cc57e5c41b", 309 | "sha256:156a27548ba4e1fed944ff9fcdc150633e61d350d673ae7baaf6c25c04ac1f71", 310 | "sha256:27e2efc8f77661c9af2681755974205e7462f1ae126f498f4fe12a8b24761d15", 311 | "sha256:2a12f8be25b9ea3d1d5b165202181f2b7da4b3395289000284e5bb86154ce87c", 312 | "sha256:31c043d5211aa0e0773821fcc318eb5cbe2ec916dfbc4c6eea0c5188971988eb", 313 | "sha256:65eb3b03229f684af0cf0ad3bcc771970c1260a82a791a8d07bffb63d8c95bcc", 314 | "sha256:6cd157ce74a911325e164441ff2d9b4e244659a25b3146310518d83202f15f7a", 315 | "sha256:703c002277f0fbc3c04d0ae4989a174753a7554b2963c584ce2ec0cddcf2bc53", 316 | "sha256:869bbb637de58ab0a912b7f20e9192132f9fbc47fc6b5111cd1e0f6cdf5cf9b0", 317 | "sha256:8a0e0cd21da047ea10267c37caf12add400a92f0620c8bc09e4a6531a765d6d7", 318 | "sha256:8d01e949a5d22e5c4800d59b50617c56125fc187fbeb8fa423e99858546de616", 319 | "sha256:925b4fe5e7c03ed76912b75a9a41dfd682d59c0be43bce88d3b27f7f5ba028fb", 320 | "sha256:9cb1819008f0225a7c066cac8bb0cf90847b2c4a6eb9ebb7431dbd00c56c06c5", 321 | "sha256:a87d496884f40c94c85a647c385f4fd5887941d2609f71043e2b73f2436d9c65", 322 | "sha256:a9030cd30caf848a13a192c5e45367e3c6f363726569a56e75dc1151ee26d859", 323 | "sha256:a9e75e49a0f1583eee0ce93270232b8e7bb4b1edc89cc70b07600d525aef4f43", 324 | "sha256:b50f45d0e82b4562f59f0e0ca511f65e412f2a97d790eea5f60e34e5f1aabc9a", 325 | "sha256:b7878e59ec31f12d54b3797689402ee3b5cfcb5598f2ebf26491732758751908", 326 | "sha256:ce1ddaadee913543ff0154021d31b134551f63428065168e756d90bdc4c686f5", 327 | "sha256:ce2646e4c0807f3461be0653502bb48c6e91a5171d6e450367082c79e12868bf", 328 | "sha256:ce6c3d18b2a8ce364013d47b9cad71db815df31d55918403f8db7d890c9d07ae", 329 | "sha256:e4e2664232005bd306f878b0f167a31f944a07c4de0152c444f8c61bbe3cfb38", 330 | "sha256:e8aa395482728de8bdcca9cc0faf3765ab483e81e01923aaa736b42f0294f570", 331 | "sha256:eb4fcf7105bf071c71068c6eee47499ab8d4b8f5a11fc35147c934f0faa60f23", 332 | "sha256:ed375a79f06cad285166e5be74745df1ed6845c5624aafadec4b7a29c25866ef", 333 | "sha256:f35248f7e0d63b234a109dd72fbfb4b5cb6cb6840b221d0df0ecbf54ab087654", 334 | "sha256:f502ef245c492b391e0e23e94cba030ab91722dcc56963c85bfd7f3441ea2bbe", 335 | "sha256:fe01bac7226499aedf472c62fa3b85b2c619365f3f14dd222ffe4f3aa91e5f98" 336 | ], 337 | "index": "pypi", 338 | "version": "==1.3.17" 339 | }, 340 | "werkzeug": { 341 | "hashes": [ 342 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", 343 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" 344 | ], 345 | "version": "==1.0.1" 346 | } 347 | }, 348 | "develop": {} 349 | } 350 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn 'app:create_app()' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Crafts_API 2 | 3 | A GraphQL API for Custom Crafts web-application. 4 | For more information regarding the client app, please refer to the frontend README here: [GitHub](https://github.com/ZacharyRizer/Custom-Crafts). 5 | 6 | ## Technologies 7 | 8 | * Python / Flask 9 | * Graphene 10 | * PostgreSQL 11 | * SQLAlchemy 12 | * Alembic 13 | * Boto3 (AWS) 14 | * Gunicorn -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | from flask_cors import cross_origin, CORS 3 | from flask_migrate import Migrate 4 | from .config import Configuration 5 | from .models import db 6 | from .auth import AuthError, requires_auth 7 | from flask_graphql import GraphQLView 8 | from .schema import schema 9 | 10 | 11 | def create_app(): 12 | app = Flask(__name__) 13 | app.config.from_object(Configuration) 14 | cors = CORS(app, resources={r"/*": {"origins": "*"}}) 15 | db.init_app(app) 16 | Migrate(app, db) 17 | 18 | @app.route('/', methods=["GET"]) 19 | def wakeDyno(): 20 | return 'Waking those Dinosaurs' 21 | 22 | app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', 23 | schema=schema, 24 | graphiql=True)) 25 | 26 | 27 | 28 | # Error Handler 29 | @app.errorhandler(AuthError) 30 | def handle_auth_error(ex): 31 | response = jsonify(ex.error) 32 | response.status_code = ex.status_code 33 | return response 34 | 35 | return app 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/__pycache__/__init__.cpython-38 2.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacharyRizer/Custom-Crafts-api/f14a3a6e3720cb3f8fc23235afe2ba4c3c14e8d2/app/__pycache__/__init__.cpython-38 2.pyc -------------------------------------------------------------------------------- /app/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacharyRizer/Custom-Crafts-api/f14a3a6e3720cb3f8fc23235afe2ba4c3c14e8d2/app/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /app/__pycache__/config.cpython-38 2.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacharyRizer/Custom-Crafts-api/f14a3a6e3720cb3f8fc23235afe2ba4c3c14e8d2/app/__pycache__/config.cpython-38 2.pyc -------------------------------------------------------------------------------- /app/__pycache__/config.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacharyRizer/Custom-Crafts-api/f14a3a6e3720cb3f8fc23235afe2ba4c3c14e8d2/app/__pycache__/config.cpython-38.pyc -------------------------------------------------------------------------------- /app/__pycache__/models.cpython-38 2.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacharyRizer/Custom-Crafts-api/f14a3a6e3720cb3f8fc23235afe2ba4c3c14e8d2/app/__pycache__/models.cpython-38 2.pyc -------------------------------------------------------------------------------- /app/__pycache__/models.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacharyRizer/Custom-Crafts-api/f14a3a6e3720cb3f8fc23235afe2ba4c3c14e8d2/app/__pycache__/models.cpython-38.pyc -------------------------------------------------------------------------------- /app/__pycache__/schema.cpython-38 2.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacharyRizer/Custom-Crafts-api/f14a3a6e3720cb3f8fc23235afe2ba4c3c14e8d2/app/__pycache__/schema.cpython-38 2.pyc -------------------------------------------------------------------------------- /app/__pycache__/schema.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacharyRizer/Custom-Crafts-api/f14a3a6e3720cb3f8fc23235afe2ba4c3c14e8d2/app/__pycache__/schema.cpython-38.pyc -------------------------------------------------------------------------------- /app/auth.py: -------------------------------------------------------------------------------- 1 | import json 2 | from six.moves.urllib.request import urlopen 3 | from functools import wraps 4 | from flask import request, _request_ctx_stack 5 | from jose import jwt 6 | 7 | AUTH0_DOMAIN = 'custom-crafts.auth0.com' 8 | API_AUDIENCE = 'https://custom-crafts.auth0.com/api/v2/' 9 | ALGORITHMS = ['RS256'] 10 | 11 | 12 | class AuthError(Exception): 13 | def __init__(self, error, status_code): 14 | self.error = error 15 | self.status_code = status_code 16 | 17 | 18 | def get_token_auth_header(): 19 | """ 20 | Obtains the Access Token from the Authorization Header 21 | """ 22 | 23 | auth = request.headers.get("Authorization", None) 24 | if not auth: 25 | raise AuthError({"code": "authorization_header_missing", 26 | "description": 27 | "Authorization header is expected"}, 401) 28 | 29 | parts = auth.split() 30 | 31 | if parts[0].lower() != "bearer": 32 | raise AuthError({"code": "invalid_header", 33 | "description": 34 | "Authorization header must start with" 35 | " Bearer"}, 401).handle_auth_error 36 | elif len(parts) == 1: 37 | raise AuthError({"code": "invalid_header", 38 | "description": "Token not found"}, 401) 39 | elif len(parts) > 2: 40 | raise AuthError({"code": "invalid_header", 41 | "description": 42 | "Authorization header must be" 43 | " Bearer token"}, 401) 44 | 45 | token = parts[1] 46 | return token 47 | 48 | 49 | def requires_auth(f): 50 | """Determines if the Access Token is valid 51 | """ 52 | @wraps(f) 53 | def decorated(*args, **kwargs): 54 | token = get_token_auth_header() 55 | jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json") 56 | jwks = json.loads(jsonurl.read()) 57 | unverified_header = jwt.get_unverified_header(token) 58 | rsa_key = {} 59 | for key in jwks["keys"]: 60 | if key["kid"] == unverified_header["kid"]: 61 | rsa_key = { 62 | "kty": key["kty"], 63 | "kid": key["kid"], 64 | "use": key["use"], 65 | "n": key["n"], 66 | "e": key["e"] 67 | } 68 | if rsa_key: 69 | try: 70 | payload = jwt.decode( 71 | token, 72 | rsa_key, 73 | algorithms=ALGORITHMS, 74 | audience=API_AUDIENCE, 75 | issuer="https://"+AUTH0_DOMAIN+"/" 76 | ) 77 | except jwt.ExpiredSignatureError: 78 | raise AuthError({"code": "token_expired", 79 | "description": "token is expired"}, 401) 80 | except jwt.JWTClaimsError: 81 | raise AuthError({"code": "invalid_claims", 82 | "description": 83 | "incorrect claims," 84 | "please check the audience and issuer"}, 401) 85 | except Exception: 86 | raise AuthError({"code": "invalid_header", 87 | "description": 88 | "Unable to parse authentication" 89 | " token."}, 401) 90 | 91 | _request_ctx_stack.top.current_user = payload 92 | return f(*args, **kwargs) 93 | raise AuthError({"code": "invalid_header", 94 | "description": "Unable to find appropriate key"}, 401) 95 | return decorated -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class Configuration: 5 | SECRET_KEY = os.environ.get('SECRET_KEY') 6 | SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") 7 | SQLALCHEMY_TRACK_MODIFICATIONS = False -------------------------------------------------------------------------------- /app/filters.py: -------------------------------------------------------------------------------- 1 | from graphene_sqlalchemy_filter import FilterableConnectionField, FilterSet 2 | from .models import Ship 3 | # ALL_OPERATIONS = ['eq', 'ne', 'like', 'ilike', 'is_null', 'in', 'not_in', 'lt', 'lte', 'gt', 'gte', 'range'] 4 | OPERATIONS = ['eq', 'range', 'ilike'] 5 | 6 | 7 | class ShipFilter(FilterSet): 8 | class Meta: 9 | model = Ship 10 | fields = { 11 | "id": ['eq'], 12 | "category_id": ['eq'], 13 | "manufacturer_id": ['eq'], 14 | "crew_cap": ['range'], 15 | "size": ['range'], 16 | "travel_range": ['range'], 17 | "price": ['range'], 18 | "ftl": ['eq'], 19 | "used": ['eq'], 20 | "name": ['ilike'], 21 | "category": ['ilike'] 22 | } 23 | 24 | 25 | class MyFilterableConnectionField(FilterableConnectionField): 26 | filters = {Ship: ShipFilter()} 27 | -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | 5 | 6 | class Ship(db.Model): 7 | __tablename__ = 'ships' 8 | 9 | id = db.Column(db.Integer, primary_key=True) # autoincrement=True 10 | name = db.Column(db.String(50), nullable=False, unique=True) 11 | manufacturer_id = db.Column(db.Integer, db.ForeignKey( 12 | 'manufacturers.id'), nullable=False) 13 | category_id = db.Column(db.Integer, db.ForeignKey( 14 | 'categories.id'), nullable=False) 15 | size = db.Column(db.Integer, nullable=False) 16 | designer = db.Column(db.String(50), nullable=False) 17 | crew_cap = db.Column(db.Integer, nullable=False) 18 | travel_range = db.Column(db.Integer, nullable=False) 19 | ftl = db.Column(db.Boolean, nullable=False) 20 | used = db.Column(db.Boolean, nullable=False) 21 | model_link = db.Column(db.String, nullable=False) 22 | description = db.Column(db.String) 23 | stock = db.Column(db.Integer, nullable=False) 24 | price = db.Column(db.Integer, nullable=False) 25 | total_sold = db.Column(db.Integer) 26 | 27 | manufacturer = db.relationship('Manufacturer', back_populates='ships') 28 | category = db.relationship('Category', back_populates='ships') 29 | order_items = db.relationship('OrderItem', back_populates='ship') 30 | reviews = db.relationship('Review', back_populates='ship') 31 | 32 | 33 | class Manufacturer(db.Model): 34 | __tablename__ = 'manufacturers' 35 | 36 | id = db.Column(db.Integer, primary_key=True) 37 | name = db.Column(db.String(50), nullable=False, unique=True) 38 | 39 | ships = db.relationship('Ship', back_populates='manufacturer') 40 | 41 | 42 | class Category(db.Model): 43 | __tablename__ = 'categories' 44 | 45 | id = db.Column(db.Integer, primary_key=True) 46 | name = db.Column(db.String(50), nullable=False, unique=True) 47 | 48 | ships = db.relationship('Ship', back_populates='category') 49 | 50 | 51 | class Customer(db.Model): 52 | __tablename__ = 'customers' 53 | 54 | id = db.Column(db.Integer, primary_key=True) 55 | name = db.Column(db.String(50), nullable=False) 56 | email = db.Column(db.String(50), nullable=False, unique=True) 57 | auth0_id = db.Column(db.String, unique=True) 58 | picture = db.Column(db.String) 59 | 60 | orders = db.relationship('Order', back_populates='customer') 61 | reviews = db.relationship('Review', back_populates='customer') 62 | 63 | 64 | class Order(db.Model): 65 | __tablename__ = 'orders' 66 | 67 | id = db.Column(db.Integer, primary_key=True) 68 | customer_id = db.Column(db.Integer, db.ForeignKey( 69 | 'customers.id'), nullable=False) 70 | 71 | customer = db.relationship('Customer', back_populates='orders') 72 | order_items = db.relationship('OrderItem', back_populates='order') 73 | 74 | 75 | class OrderItem(db.Model): 76 | __tablename__ = 'order_items' 77 | 78 | id = db.Column(db.Integer, primary_key=True) 79 | order_id = db.Column(db.Integer, db.ForeignKey( 80 | 'orders.id'), nullable=False) 81 | ship_id = db.Column(db.Integer, db.ForeignKey('ships.id'), nullable=False) 82 | quantity = db.Column(db.Integer, nullable=False) 83 | color = db.Column(db.String) 84 | created_at = db.Column(db.DateTime) 85 | 86 | order = db.relationship('Order', back_populates='order_items') 87 | ship = db.relationship('Ship', back_populates='order_items') 88 | 89 | 90 | class Review(db.Model): 91 | __tablename__ = 'reviews' 92 | 93 | id = db.Column(db.Integer, primary_key=True) 94 | customer_id = db.Column(db.Integer, db.ForeignKey( 95 | 'customers.id'), nullable=False) 96 | ship_id = db.Column(db.Integer, db.ForeignKey('ships.id'), nullable=False) 97 | rating = db.Column(db.Integer, nullable=False) 98 | description = db.Column(db.String) 99 | upVoteCount = db.Column(db.Integer) 100 | downVoteCount = db.Column(db.Integer) 101 | 102 | customer = db.relationship('Customer', back_populates='reviews') 103 | ship = db.relationship('Ship', back_populates='reviews') 104 | -------------------------------------------------------------------------------- /app/schema.py: -------------------------------------------------------------------------------- 1 | import graphene 2 | from graphene import Connection, Node, relay 3 | from graphene_sqlalchemy import SQLAlchemyObjectType 4 | import threading 5 | import json 6 | import datetime 7 | 8 | from .filters import MyFilterableConnectionField 9 | from .models import db, Category, Customer, Manufacturer, Ship, Order, OrderItem, Review 10 | from .types import CategoryType, CustomerType, ManufacturerType, OrderType, OrderItemType, ReviewType, ShipType 11 | from .auth import AuthError, requires_auth 12 | 13 | 14 | class ShipNode(SQLAlchemyObjectType): 15 | class Meta: 16 | model = Ship 17 | connection_field_factory = MyFilterableConnectionField.factory 18 | 19 | 20 | class ShipConnection(Connection): 21 | class Meta: 22 | node = ShipNode 23 | 24 | 25 | class Query(graphene.ObjectType): 26 | node = relay.Node.Field() 27 | 28 | categories = graphene.List(CategoryType) 29 | category = graphene.Field( 30 | CategoryType, category_id=graphene.Int()) 31 | 32 | manufacturers = graphene.List(ManufacturerType) 33 | manufacturer = graphene.Field( 34 | ManufacturerType, manufacturer_id=graphene.Int()) 35 | 36 | orders = graphene.List(OrderType) 37 | order = graphene.Field( 38 | OrderType, order_id=graphene.Int()) 39 | 40 | order_items = graphene.List(OrderItemType) 41 | order_item = graphene.Field( 42 | OrderItemType, order_item_id=graphene.Int()) 43 | 44 | reviews = graphene.Field(graphene.List(ReviewType), ship_id=graphene.Int()) 45 | review = graphene.Field( 46 | ReviewType, review_id=graphene.Int()) 47 | 48 | ships = MyFilterableConnectionField(ShipConnection) # uses filter 49 | ship = graphene.Field( 50 | ShipType, ship_id=graphene.Int()) 51 | 52 | customers = graphene.List(CustomerType) 53 | customer = graphene.Field( 54 | CustomerType, customer_id=graphene.Int()) 55 | 56 | def resolve_ship(self, info, ship_id): 57 | return Ship.query.get(ship_id) 58 | 59 | def resolve_categories(self, info, **kwargs): 60 | return Category.query.all() 61 | 62 | def resolve_category(self, info, category_id): 63 | return Category.query.get(category_id) 64 | 65 | def resolve_manufacturers(self, info, **kwargs): 66 | return Manufacturer.query.all() 67 | 68 | def resolve_manufacturer(self, info, manufacturer_id): 69 | return Manufacturer.query.get(manufacturer_id) 70 | 71 | def resolve_orders(self, info, **kwargs): 72 | return Order.query.all() 73 | 74 | def resolve_order(self, info, order_id): 75 | print('resolve_order') 76 | return Order.query.get(order_id) 77 | 78 | def resolve_order_items(self, info, **kwargs): 79 | print('resolve order_items') 80 | return OrderItem.query.all() 81 | 82 | def resolve_order_item(self, info, order_item_id): 83 | return OrderItem.query.get(order_item_id) 84 | 85 | def resolve_reviews(self, info, ship_id, **kwargs): 86 | return Review.query.filter(Review.ship_id == ship_id).all() 87 | # return db.session.query(Review).options(joinedload 88 | # (Customer.name, 89 | # Customer.picture)).all() 90 | 91 | def resolve_review(self, info, review_id): 92 | return Review.query.get(review_id) 93 | 94 | def resolve_customers(self, info, **kwargs): 95 | return Customer.query.all() 96 | 97 | def resolve_customer(self, info, customer_id): 98 | print('customer resolver') 99 | return Customer.query.get(customer_id) 100 | 101 | 102 | # Mutations 103 | 104 | 105 | class AddCustomer(graphene.Mutation): 106 | id = graphene.Int() 107 | name = graphene.String() 108 | email = graphene.String() 109 | auth0_id = graphene.String() 110 | picture = graphene.String() 111 | 112 | class Arguments: 113 | name = graphene.String() 114 | email = graphene.String() 115 | auth0_id = graphene.String() 116 | picture = graphene.String() 117 | 118 | @requires_auth 119 | def mutate(self, info, name, email, auth0_id, picture): 120 | customer = Customer.query.filter(Customer.email == email).first() 121 | if customer: 122 | customer.name = name 123 | customer.email = email 124 | customer.auth0_id = auth0_id 125 | customer.picture = picture 126 | else: 127 | customer = Customer(name=name, email=email, 128 | auth0_id=auth0_id, picture=picture) 129 | db.session.add(customer) 130 | db.session.commit() 131 | 132 | return AddCustomer( 133 | id=customer.id, 134 | name=customer.name, 135 | email=customer.email, 136 | auth0_id=customer.auth0_id, 137 | picture=customer.picture 138 | ) 139 | 140 | 141 | # class ItemInputType(graphene.InputObjectType): 142 | # ship_id = graphene.Int(required=True) 143 | # quantity = graphene.Int(required=True) 144 | 145 | 146 | class OrderItemInputType(graphene.InputObjectType): 147 | customer_id = graphene.Int() 148 | items = graphene.JSONString() 149 | # ship_id = graphene.Int(required=True) 150 | # quantity = graphene.Int(required=True) 151 | 152 | 153 | class AddOrder(graphene.Mutation): 154 | id = graphene.Int() 155 | cart = graphene.Field(OrderItemType) 156 | 157 | class Arguments: 158 | cart = graphene.Argument(OrderItemInputType) 159 | 160 | def mutate(self, info, cart): 161 | order = Order(customer_id=cart.customer_id) 162 | 163 | def makeOrderItem(order, item): 164 | order_item = OrderItem( 165 | order_id=order.id, 166 | ship_id=item['shipId'], 167 | quantity=item['quantity'], 168 | color=item['color'], 169 | created_at=datetime.datetime.now()) 170 | db.session.add(order_item) 171 | db.session.commit() 172 | 173 | def incrementStockLocal(shipId): 174 | print(f'shipId {shipId}') 175 | # shipI = Ship.query.get(shipId) 176 | # print('in incrementStockLocal') 177 | # shipI.stock += 4 178 | # db.session.commit() 179 | 180 | def decrementStockLocal(item): 181 | ship = Ship.query.get(item['shipId']) 182 | ship.stock -= item['quantity'] 183 | 184 | # adjust total sold 185 | 186 | ship.total_sold += item['quantity'] 187 | 188 | db.session.commit() 189 | print(f'before if, ship.stock') 190 | if ship.stock == 0: 191 | # print('creating asyncio loop obj') 192 | # loop = asyncio.new_event_loop() 193 | # asyncio.set_event_loop(loop) 194 | # loop.call_later(8, incrementStockLocal, ship) 195 | # loop.run_until_complete() 196 | # loop.close() 197 | 198 | t = threading.Timer(8, incrementStockLocal, ([ship.id])) 199 | t.start() 200 | db.session.commit() 201 | 202 | db.session.add(order) 203 | db.session.commit() 204 | 205 | # loop through cart 206 | 207 | for item in cart.items: 208 | makeOrderItem(order, item) 209 | decrementStockLocal(item) 210 | 211 | # db.session.commit() 212 | 213 | return AddOrder( 214 | id=order.id) 215 | # customer_id=order.customer_id) 216 | 217 | 218 | # class AddOrderItem(graphene.Mutation): 219 | # print('orderItem mutation') 220 | 221 | # id = graphene.Int() 222 | # order_id = graphene.Int() 223 | # ship_id = graphene.Int() 224 | # quantity = graphene.Int() 225 | 226 | # class Arguments: 227 | # order_id = graphene.Int() 228 | # ship_id = graphene.Int() 229 | # quantity = graphene.Int() 230 | 231 | # @requires_auth 232 | # def mutate(self, info, order_id, ship_id, quantity): 233 | # order_item = OrderItem( 234 | # order_id=order_id, ship_id=ship_id, quantity=quantity) 235 | # db.session.add(order_item) 236 | # db.session.commit() 237 | 238 | # return AddOrderItem( 239 | # id=order_item.id, 240 | # order_id=order_item.order_id, 241 | # ship_id=order_item.ship_id, 242 | # quantity=order_item.quantity 243 | # ) 244 | 245 | 246 | class IncrementShipStock(graphene.Mutation): 247 | id = graphene.Int() 248 | stock = graphene.Int() 249 | 250 | class Arguments: 251 | id = graphene.Int() 252 | inc_quantity = graphene.Int() 253 | 254 | # @requires_auth 255 | def mutate(self, info, id, inc_quantity): 256 | ship = Ship.query.get(id) 257 | ship.stock += inc_quantity 258 | db.session.commit() 259 | 260 | return IncrementShipStock( 261 | id=id, 262 | stock=ship.stock 263 | ) 264 | 265 | 266 | class DecrementShipStock(graphene.Mutation): 267 | id = graphene.Int() 268 | stock = graphene.Int() 269 | 270 | class Arguments: 271 | id = graphene.Int() 272 | dec_quantity = graphene.Int() 273 | 274 | # @requires_auth 275 | def mutate(self, info, id, dec_quantity): 276 | ship = Ship.query.get(id) 277 | ship.stock -= dec_quantity 278 | db.session.commit() 279 | 280 | return DecrementShipStock( 281 | id=id, 282 | stock=ship.stock 283 | ) 284 | 285 | 286 | class DeleteOrder(graphene.Mutation): 287 | id = graphene.Int() 288 | 289 | class Arguments: 290 | id = graphene.Int() 291 | 292 | @requires_auth 293 | def mutate(self, info, id): 294 | db.session.delete(Order.query.get(id)) 295 | db.session.commit() 296 | 297 | return DeleteOrder(id=id) 298 | 299 | 300 | class DeleteOrderItem(graphene.Mutation): 301 | id = graphene.Int() 302 | 303 | class Arguments: 304 | id = graphene.Int() 305 | 306 | @requires_auth 307 | def mutate(self, info, id): 308 | db.session.delete(OrderItem.query.get(id)) 309 | db.session.commit() 310 | 311 | return DeleteOrderItem(id=id) 312 | 313 | 314 | # class UpdateOrderItem(graphene.Mutation): 315 | # pass 316 | 317 | 318 | class AddReview(graphene.Mutation): 319 | id = graphene.Int() 320 | customer_id = graphene.Int() 321 | ship_id = graphene.Int() 322 | rating = graphene.Int() 323 | description = graphene.String() 324 | 325 | class Arguments: 326 | customer_id = graphene.Int() 327 | ship_id = graphene.Int() 328 | rating = graphene.Int() 329 | description = graphene.String() 330 | 331 | # @requires_auth 332 | def mutate(self, info, customer_id, ship_id, rating, description): 333 | review = Review(customer_id=customer_id, ship_id=ship_id, 334 | rating=rating, description=description) 335 | db.session.add(review) 336 | db.session.commit() 337 | 338 | return AddReview( 339 | id=review.id, 340 | customer_id=review.customer_id, 341 | ship_id=review.ship_id, 342 | rating=review.rating, 343 | description=review.description 344 | ) 345 | 346 | 347 | class DeleteReview(graphene.Mutation): 348 | id = graphene.Int() 349 | 350 | class Arguments: 351 | id = graphene.Int() 352 | 353 | # @requires_auth 354 | def mutate(self, info, id): 355 | db.session.delete(Review.query.get(id)) 356 | db.session.commit() 357 | 358 | return DeleteReview(id=id) 359 | 360 | 361 | class Mutation(graphene.ObjectType): 362 | add_customer = AddCustomer.Field() 363 | add_order = AddOrder.Field() 364 | delete_order = DeleteOrder.Field() 365 | delete_order_item = DeleteOrderItem.Field() 366 | add_review = AddReview.Field() 367 | delete_review = DeleteReview.Field() 368 | decrement_ship_stock = DecrementShipStock.Field() 369 | increment_ship_stock = IncrementShipStock.Field() 370 | 371 | 372 | schema = graphene.Schema(query=Query, mutation=Mutation) 373 | -------------------------------------------------------------------------------- /app/types.py: -------------------------------------------------------------------------------- 1 | import graphene 2 | from graphene import Connection, Node, relay 3 | from graphene_sqlalchemy import SQLAlchemyObjectType 4 | 5 | from .models import db, Category, Customer, Manufacturer, Ship, Order, OrderItem, Review 6 | 7 | 8 | class ShipType(SQLAlchemyObjectType): 9 | class Meta: 10 | model = Ship 11 | 12 | 13 | class CategoryType(SQLAlchemyObjectType): 14 | class Meta: 15 | model = Category 16 | 17 | 18 | class CustomerType(SQLAlchemyObjectType): 19 | class Meta: 20 | model = Customer 21 | 22 | 23 | class ManufacturerType(SQLAlchemyObjectType): 24 | class Meta: 25 | model = Manufacturer 26 | 27 | 28 | class OrderType(SQLAlchemyObjectType): 29 | class Meta: 30 | model = Order 31 | 32 | 33 | class OrderItemType(SQLAlchemyObjectType): 34 | class Meta: 35 | model = OrderItem 36 | 37 | 38 | class ReviewType(SQLAlchemyObjectType): 39 | class Meta: 40 | model = Review 41 | -------------------------------------------------------------------------------- /customCraftsApi.py: -------------------------------------------------------------------------------- 1 | from app import create_app -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [handler_console] 38 | class = StreamHandler 39 | args = (sys.stderr,) 40 | level = NOTSET 41 | formatter = generic 42 | 43 | [formatter_generic] 44 | format = %(levelname)-5.5s [%(name)s] %(message)s 45 | datefmt = %H:%M:%S 46 | -------------------------------------------------------------------------------- /migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import logging 4 | from logging.config import fileConfig 5 | 6 | from sqlalchemy import engine_from_config 7 | from sqlalchemy import pool 8 | 9 | from alembic import context 10 | 11 | # this is the Alembic Config object, which provides 12 | # access to the values within the .ini file in use. 13 | config = context.config 14 | 15 | # Interpret the config file for Python logging. 16 | # This line sets up loggers basically. 17 | fileConfig(config.config_file_name) 18 | logger = logging.getLogger('alembic.env') 19 | 20 | # add your model's MetaData object here 21 | # for 'autogenerate' support 22 | # from myapp import mymodel 23 | # target_metadata = mymodel.Base.metadata 24 | from flask import current_app 25 | config.set_main_option( 26 | 'sqlalchemy.url', 27 | str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) 28 | target_metadata = current_app.extensions['migrate'].db.metadata 29 | 30 | # other values from the config, defined by the needs of env.py, 31 | # can be acquired: 32 | # my_important_option = config.get_main_option("my_important_option") 33 | # ... etc. 34 | 35 | 36 | def run_migrations_offline(): 37 | """Run migrations in 'offline' mode. 38 | 39 | This configures the context with just a URL 40 | and not an Engine, though an Engine is acceptable 41 | here as well. By skipping the Engine creation 42 | we don't even need a DBAPI to be available. 43 | 44 | Calls to context.execute() here emit the given string to the 45 | script output. 46 | 47 | """ 48 | url = config.get_main_option("sqlalchemy.url") 49 | context.configure( 50 | url=url, target_metadata=target_metadata, literal_binds=True 51 | ) 52 | 53 | with context.begin_transaction(): 54 | context.run_migrations() 55 | 56 | 57 | def run_migrations_online(): 58 | """Run migrations in 'online' mode. 59 | 60 | In this scenario we need to create an Engine 61 | and associate a connection with the context. 62 | 63 | """ 64 | 65 | # this callback is used to prevent an auto-migration from being generated 66 | # when there are no changes to the schema 67 | # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html 68 | def process_revision_directives(context, revision, directives): 69 | if getattr(config.cmd_opts, 'autogenerate', False): 70 | script = directives[0] 71 | if script.upgrade_ops.is_empty(): 72 | directives[:] = [] 73 | logger.info('No changes in schema detected.') 74 | 75 | connectable = engine_from_config( 76 | config.get_section(config.config_ini_section), 77 | prefix='sqlalchemy.', 78 | poolclass=pool.NullPool, 79 | ) 80 | 81 | with connectable.connect() as connection: 82 | context.configure( 83 | connection=connection, 84 | target_metadata=target_metadata, 85 | process_revision_directives=process_revision_directives, 86 | **current_app.extensions['migrate'].configure_args 87 | ) 88 | 89 | with context.begin_transaction(): 90 | context.run_migrations() 91 | 92 | 93 | if context.is_offline_mode(): 94 | run_migrations_offline() 95 | else: 96 | run_migrations_online() 97 | -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /migrations/versions/5842dab8d19b_add_vote_counts_to_review_model.py: -------------------------------------------------------------------------------- 1 | """Add vote counts to Review model. 2 | 3 | Revision ID: 5842dab8d19b 4 | Revises: cbc821e170fa 5 | Create Date: 2020-06-13 12:00:01.270825 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '5842dab8d19b' 14 | down_revision = 'cbc821e170fa' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('reviews', sa.Column('downVoteCount', sa.Integer(), nullable=True)) 22 | op.add_column('reviews', sa.Column('upVoteCount', sa.Integer(), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('reviews', 'upVoteCount') 29 | op.drop_column('reviews', 'downVoteCount') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /migrations/versions/82e82c504529_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: 82e82c504529 4 | Revises: f61ec522d535 5 | Create Date: 2020-06-13 22:12:27.230603 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '82e82c504529' 14 | down_revision = 'f61ec522d535' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('customers', sa.Column('picture', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('customers', 'picture') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /migrations/versions/c783ac6f8c44_add_total_sold_field_to_ship_and_.py: -------------------------------------------------------------------------------- 1 | """Add total_sold field to Ship and created_at to OrderItem to support basic admin functionality. 2 | 3 | Revision ID: c783ac6f8c44 4 | Revises: 82e82c504529 5 | Create Date: 2020-06-14 11:25:29.534113 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'c783ac6f8c44' 14 | down_revision = '82e82c504529' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('order_items', sa.Column('created_at', sa.DateTime(), nullable=True)) 22 | op.add_column('ships', sa.Column('total_sold', sa.Integer(), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('ships', 'total_sold') 29 | op.drop_column('order_items', 'created_at') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /migrations/versions/cbc821e170fa_init_migration.py: -------------------------------------------------------------------------------- 1 | """init migration 2 | 3 | Revision ID: cbc821e170fa 4 | Revises: 5 | Create Date: 2020-06-10 13:13:09.929484 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'cbc821e170fa' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('categories', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('name', sa.String(length=50), nullable=False), 24 | sa.PrimaryKeyConstraint('id'), 25 | sa.UniqueConstraint('name') 26 | ) 27 | op.create_table('customers', 28 | sa.Column('id', sa.Integer(), nullable=False), 29 | sa.Column('name', sa.String(length=50), nullable=False), 30 | sa.Column('email', sa.String(length=50), nullable=False), 31 | sa.Column('auth0_id', sa.String(), nullable=True), 32 | sa.PrimaryKeyConstraint('id'), 33 | sa.UniqueConstraint('auth0_id'), 34 | sa.UniqueConstraint('email') 35 | ) 36 | op.create_table('manufacturers', 37 | sa.Column('id', sa.Integer(), nullable=False), 38 | sa.Column('name', sa.String(length=50), nullable=False), 39 | sa.PrimaryKeyConstraint('id'), 40 | sa.UniqueConstraint('name') 41 | ) 42 | op.create_table('orders', 43 | sa.Column('id', sa.Integer(), nullable=False), 44 | sa.Column('customer_id', sa.Integer(), nullable=False), 45 | sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ), 46 | sa.PrimaryKeyConstraint('id') 47 | ) 48 | op.create_table('ships', 49 | sa.Column('id', sa.Integer(), nullable=False), 50 | sa.Column('name', sa.String(length=50), nullable=False), 51 | sa.Column('manufacturer_id', sa.Integer(), nullable=False), 52 | sa.Column('category_id', sa.Integer(), nullable=False), 53 | sa.Column('size', sa.Integer(), nullable=False), 54 | sa.Column('designer', sa.String(length=50), nullable=False), 55 | sa.Column('crew_cap', sa.Integer(), nullable=False), 56 | sa.Column('travel_range', sa.Integer(), nullable=False), 57 | sa.Column('ftl', sa.Boolean(), nullable=False), 58 | sa.Column('used', sa.Boolean(), nullable=False), 59 | sa.Column('model_link', sa.String(), nullable=False), 60 | sa.Column('description', sa.String(), nullable=True), 61 | sa.Column('stock', sa.Integer(), nullable=False), 62 | sa.Column('price', sa.Integer(), nullable=False), 63 | sa.ForeignKeyConstraint(['category_id'], ['categories.id'], ), 64 | sa.ForeignKeyConstraint(['manufacturer_id'], ['manufacturers.id'], ), 65 | sa.PrimaryKeyConstraint('id'), 66 | sa.UniqueConstraint('name') 67 | ) 68 | op.create_table('order_items', 69 | sa.Column('id', sa.Integer(), nullable=False), 70 | sa.Column('order_id', sa.Integer(), nullable=False), 71 | sa.Column('ship_id', sa.Integer(), nullable=False), 72 | sa.Column('quantity', sa.Integer(), nullable=False), 73 | sa.ForeignKeyConstraint(['order_id'], ['orders.id'], ), 74 | sa.ForeignKeyConstraint(['ship_id'], ['ships.id'], ), 75 | sa.PrimaryKeyConstraint('id') 76 | ) 77 | op.create_table('reviews', 78 | sa.Column('id', sa.Integer(), nullable=False), 79 | sa.Column('customer_id', sa.Integer(), nullable=False), 80 | sa.Column('ship_id', sa.Integer(), nullable=False), 81 | sa.Column('rating', sa.Integer(), nullable=False), 82 | sa.Column('description', sa.String(), nullable=True), 83 | sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ), 84 | sa.ForeignKeyConstraint(['ship_id'], ['ships.id'], ), 85 | sa.PrimaryKeyConstraint('id') 86 | ) 87 | # ### end Alembic commands ### 88 | 89 | 90 | def downgrade(): 91 | # ### commands auto generated by Alembic - please adjust! ### 92 | op.drop_table('reviews') 93 | op.drop_table('order_items') 94 | op.drop_table('ships') 95 | op.drop_table('orders') 96 | op.drop_table('manufacturers') 97 | op.drop_table('customers') 98 | op.drop_table('categories') 99 | # ### end Alembic commands ### 100 | -------------------------------------------------------------------------------- /migrations/versions/f61ec522d535_add_color_to_order_item.py: -------------------------------------------------------------------------------- 1 | """add color to order item 2 | 3 | Revision ID: f61ec522d535 4 | Revises: 5842dab8d19b 5 | Create Date: 2020-06-13 20:49:48.983992 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'f61ec522d535' 14 | down_revision = '5842dab8d19b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('order_items', sa.Column('color', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('order_items', 'color') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /seed.py: -------------------------------------------------------------------------------- 1 | from app.models import Manufacturer, Ship, Category, db 2 | from app import create_app 3 | from dotenv import load_dotenv 4 | load_dotenv() 5 | 6 | # size 1 = 100, 100-500, 500-1000, 1000-5000, 5000+ 7 | # price = 100, 100-1000, 1000-10000, 10000-100000, 100000+ 8 | # range = 25, 25-100, 100-1000, 1000-10000, 10000+ 9 | # crew_cap = 10, 10-100, 100-500, 500-1000, 1000+ 10 | 11 | app = create_app() 12 | 13 | with app.app_context(): 14 | db.drop_all() 15 | db.create_all() 16 | 17 | man1 = Manufacturer(id=1, name='Imperial Galactic Government') 18 | man2 = Manufacturer(id=2, name='Spacing Guild') 19 | man3 = Manufacturer(id=3, name='Corellian Engineering Corporation') 20 | man4 = Manufacturer(id=4, name='Cybertronian Technologies') 21 | man5 = Manufacturer(id=5, name='Weyland-Yutani Corporation') 22 | cat1 = Category(id=1, name='Military') 23 | cat2 = Category(id=2, name='Transport') 24 | cat3 = Category(id=3, name='Cargo') 25 | cat4 = Category(id=4, name='Performance') 26 | cat5 = Category(id=5, name='Luxury') 27 | 28 | ship1 = Ship( 29 | name='Frontier', 30 | manufacturer_id=5, 31 | category_id=1, 32 | size=8473, 33 | designer='T\'Maha', 34 | crew_cap=2500, 35 | travel_range=15600, 36 | ftl=True, 37 | used=False, 38 | model_link='/spaceships/ship1_green.glb', 39 | description="Nicknamed \"The Military's Ostrich,\" this ship has what every general is the Celestial Armed Forces could want: 2 big cannons mounted directly to the front. Needless to say, when it comes to rear attacks, this ship has it's head in the sand.", 40 | stock=3, 41 | total_sold=0, 42 | price=470000 43 | ) 44 | ship2 = Ship( 45 | name='SS Dreadnought', 46 | manufacturer_id=4, 47 | category_id=2, 48 | size=4788, 49 | designer='Glama V\'Leeni', 50 | crew_cap=1000, 51 | travel_range=9500, 52 | ftl=False, 53 | used=True, 54 | model_link='/spaceships/ship2_red.glb', 55 | description="The premier transport ship of the Cerullian Star Airline, the only thing dreadful about the SS Dreadnought is the interior carpeting (and the layovers in Space Atlanta).", 56 | stock=3, 57 | total_sold=0, 58 | price=850000 59 | ) 60 | ship3 = Ship( 61 | name='Despot', 62 | manufacturer_id=3, 63 | category_id=3, 64 | size=8993, 65 | designer='Kr\'Andull', 66 | crew_cap=500, 67 | travel_range=8700, 68 | ftl=True, 69 | used=False, 70 | model_link='/spaceships/ship3_orange.glb', 71 | description="The Milky Way's most robust asteroid cracker, the Despot has a reputation among many space miners as being a total slog to work on. * Custom Crafts cannot be held liable for anything that might emerge from said cracked asteroids.", 72 | stock=3, 73 | total_sold=0, 74 | price=150000 75 | ) 76 | ship4 = Ship( 77 | name='Star Talon', 78 | manufacturer_id=2, 79 | category_id=4, 80 | size=340, 81 | designer='Aren Bellee', 82 | crew_cap=350, 83 | travel_range=850, 84 | ftl=False, 85 | used=True, 86 | model_link='/spaceships/ship4_blue.glb', 87 | description='"There\'s nothing more terrifying than looking in your rear-view-monitor and seeing a metallic eagle\'s claw charging full speed at you!" - Aren Bellee (Designer)', 88 | stock=3, 89 | total_sold=0, 90 | price=8700 91 | ) 92 | ship5 = Ship( 93 | name='Proton', 94 | manufacturer_id=1, 95 | category_id=5, 96 | size=945, 97 | designer='Zach Rizer', 98 | crew_cap=10, 99 | travel_range=13000, 100 | ftl=True, 101 | used=False, 102 | model_link='/spaceships/ship5_red.glb', 103 | description='The Proton was named for the high degree of luxury that it gives to customers. It was also named for the proton.', 104 | stock=3, 105 | total_sold=0, 106 | price=23000 107 | ) 108 | ship6 = Ship( 109 | name='Trenxal', 110 | manufacturer_id=1, 111 | category_id=1, 112 | size=86, 113 | designer='Abdullah Wafy', 114 | crew_cap=50, 115 | travel_range=90, 116 | ftl=False, 117 | used=True, 118 | model_link='/spaceships/ship6_blue.glb', 119 | description='Also known as "that one bug ship," the Trenxal is often compared to a bee, or a dragonfly, or some other kind of insect. It often transports desperate outlaws and the writers of cancelled TV shows.', 120 | stock=3, 121 | total_sold=0, 122 | price=657 123 | ) 124 | ship7 = Ship( 125 | name='The Rubber Ducky', 126 | manufacturer_id=5, 127 | category_id=2, 128 | size=10, 129 | designer='Ian Magenta', 130 | crew_cap=2, 131 | travel_range=23, 132 | ftl=True, 133 | used=False, 134 | model_link='/spaceships/ship7_orange.glb', 135 | description="We're not going to tell you that we found this ship floating in a gigantic space bathtub, but we're also going to tell you that that's exactly how we found it.", 136 | stock=3, 137 | total_sold=0, 138 | price=75 139 | ) 140 | ship8 = Ship( 141 | name='The Æthelwulf', 142 | manufacturer_id=3, 143 | category_id=3, 144 | size=1212, 145 | designer='Take Mayuma', 146 | crew_cap=50, 147 | travel_range=10, 148 | ftl=False, 149 | used=True, 150 | model_link='/spaceships/ship8_green.glb', 151 | description='"Aeethellwulf? Aaethelwulf? Aaattlewolf? EtherWolf? Either way, I \'m buying!" That\'s what you\'ll be saying, after you please please please take this ship and its confusing name off our lot.', 152 | stock=3, 153 | total_sold=0, 154 | price=67000 155 | ) 156 | ship9 = Ship( 157 | name='Poseidon', 158 | manufacturer_id=4, 159 | category_id=4, 160 | size=7564, 161 | designer='Sui Gwu-Pao', 162 | crew_cap=1200, 163 | travel_range=5800, 164 | ftl=True, 165 | used=False, 166 | model_link='/spaceships/ship9_orange.glb', 167 | description='Not just the king of the sea! The Poisiden flies like a boat and warps like a submarine. But DO NOT drive this thing into water. Those ads on the internet are fake, this thing is NOT waterproof.', 168 | stock=3, 169 | total_sold=0, 170 | price=8900 171 | ) 172 | ship10 = Ship( 173 | name='Apollo', 174 | manufacturer_id=2, 175 | category_id=5, 176 | size=765, 177 | designer='William Schrader', 178 | crew_cap=300, 179 | travel_range=3500, 180 | ftl=False, 181 | used=True, 182 | model_link='/spaceships/ship10_red.glb', 183 | description='Bring a little history with you on the road! Each Apollo spaceship is made with real parts from the Apollo missions!', 184 | stock=3, 185 | total_sold=0, 186 | price=435 187 | ) 188 | ship11 = Ship( 189 | name='Oberon Nova', 190 | manufacturer_id=5, 191 | category_id=1, 192 | size=276, 193 | designer='Abdullah Wafy', 194 | crew_cap=100, 195 | travel_range=4500, 196 | ftl=True, 197 | used=False, 198 | model_link='/spaceships/ship11_blue.glb', 199 | description='Tour the galaxies with the authentic merkaba', 200 | stock=3, 201 | total_sold=0, 202 | price=8700 203 | ) 204 | ship12 = Ship( 205 | name='ISV Rimward Gold', 206 | manufacturer_id=4, 207 | category_id=2, 208 | size=764, 209 | designer='Guinevere Watrous', 210 | crew_cap=250, 211 | travel_range=900, 212 | ftl=False, 213 | used=True, 214 | model_link='/spaceships/ship12_green.glb', 215 | description='Gold-plated surfaces abound', 216 | stock=3, 217 | total_sold=0, 218 | price=96000 219 | ) 220 | ship13 = Ship( 221 | name='DH Enterprise', 222 | manufacturer_id=3, 223 | category_id=3, 224 | size=476, 225 | designer='Derrik Alder', 226 | crew_cap=100, 227 | travel_range=5700, 228 | ftl=True, 229 | used=False, 230 | model_link='/spaceships/ship13_green.glb', 231 | description='For hikers through star trails', 232 | stock=3, 233 | total_sold=0, 234 | price=9000 235 | ) 236 | ship14 = Ship( 237 | name='Tycho', 238 | manufacturer_id=2, 239 | category_id=4, 240 | size=156, 241 | designer='Maddox Volante', 242 | crew_cap=50, 243 | travel_range=7500, 244 | ftl=False, 245 | used=True, 246 | model_link='/spaceships/ship14_red.glb', 247 | description='Discover frontiers of the beyond', 248 | stock=3, 249 | total_sold=0, 250 | price=65000 251 | ) 252 | ship15 = Ship( 253 | name='The Leviathan', 254 | manufacturer_id=1, 255 | category_id=5, 256 | size=3745, 257 | designer='Abdullah Wafy', 258 | crew_cap=1000, 259 | travel_range=12000, 260 | ftl=True, 261 | used=False, 262 | model_link='/spaceships/ship15_blue.glb', 263 | description='Mysterious deep-space monster of a ship dominating all others...', 264 | stock=3, 265 | total_sold=0, 266 | price=999999 267 | ) 268 | ship16 = Ship( 269 | name='Pegasus', 270 | manufacturer_id=2, 271 | category_id=4, 272 | size=3745, 273 | designer='Abdullah Wafy', 274 | crew_cap=1000, 275 | travel_range=12000, 276 | ftl=True, 277 | used=False, 278 | model_link='/spaceships/ship16_orange.glb', 279 | description='For those with a heroic bent. Fly at will with lightning speed.', 280 | stock=3, 281 | total_sold=0, 282 | price=999999 283 | ) 284 | ship17 = Ship( 285 | name='Seraph', 286 | manufacturer_id=1, 287 | category_id=1, 288 | size=3745, 289 | designer='Abdullah Wafy', 290 | crew_cap=1000, 291 | travel_range=12000, 292 | ftl=True, 293 | used=False, 294 | model_link='/spaceships/ship17_green.glb', 295 | description='Exalted design with superb flight power', 296 | stock=3, 297 | total_sold=0, 298 | price=999999 299 | ) 300 | ship18 = Ship( 301 | name='Angel', 302 | manufacturer_id=3, 303 | category_id=2, 304 | size=3745, 305 | designer='Abdullah Wafy', 306 | crew_cap=1000, 307 | travel_range=12000, 308 | ftl=True, 309 | used=False, 310 | model_link='/spaceships/ship18_red.glb', 311 | description='Infinity pool inside in a Utopian setting', 312 | stock=3, 313 | total_sold=0, 314 | price=999999 315 | ) 316 | ship19 = Ship( 317 | name='The Milano', 318 | manufacturer_id=4, 319 | category_id=3, 320 | size=3745, 321 | designer='Valdis Simril', 322 | crew_cap=1000, 323 | travel_range=12000, 324 | ftl=True, 325 | used=False, 326 | model_link='/spaceships/ship19_blue.glb', 327 | description='Crush it, smash it, own it', 328 | stock=3, 329 | total_sold=0, 330 | price=999999 331 | ) 332 | ship20 = Ship( 333 | name='Mutation', 334 | manufacturer_id=5, 335 | category_id=5, 336 | size=3745, 337 | designer='Vlegi Moveloi', 338 | crew_cap=1000, 339 | travel_range=12000, 340 | ftl=True, 341 | used=False, 342 | model_link='/spaceships/ship20_orange.glb', 343 | description='Malignant spread, not for benign folks', 344 | stock=3, 345 | total_sold=0, 346 | price=999999 347 | ) 348 | 349 | db.session.add(man1) 350 | db.session.add(man2) 351 | db.session.add(man3) 352 | db.session.add(man4) 353 | db.session.add(man5) 354 | 355 | db.session.add(cat1) 356 | db.session.add(cat2) 357 | db.session.add(cat3) 358 | db.session.add(cat4) 359 | db.session.add(cat5) 360 | 361 | db.session.add(ship1) 362 | db.session.add(ship2) 363 | db.session.add(ship3) 364 | db.session.add(ship4) 365 | db.session.add(ship5) 366 | db.session.add(ship6) 367 | db.session.add(ship7) 368 | db.session.add(ship8) 369 | db.session.add(ship9) 370 | db.session.add(ship10) 371 | db.session.add(ship11) 372 | db.session.add(ship12) 373 | db.session.add(ship13) 374 | db.session.add(ship14) 375 | db.session.add(ship15) 376 | db.session.add(ship16) 377 | db.session.add(ship17) 378 | db.session.add(ship18) 379 | db.session.add(ship19) 380 | db.session.add(ship20) 381 | 382 | db.session.commit() 383 | 384 | print('finish seed') 385 | --------------------------------------------------------------------------------