diff --git a/.github/workflows/workflow_app.yml b/.github/workflows/workflow_app.yml new file mode 100644 index 0000000..d266454 --- /dev/null +++ b/.github/workflows/workflow_app.yml @@ -0,0 +1,44 @@ +name: CI/CD for Web App + +run-name: ${{ github.actor }} is automating using Github Actions + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 5 + strategy: + matrix: + python-version: ["3.9", "3.11"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade pipenv + python -m pip install --upgrade Flask-Pytest + pipenv install --dev + pipenv install tomli + pipenv sync + - name: Test with pytest (web-app) + run: | + pipenv run pytest -v test_app.py + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Deploy to Production + run: | + docker-compose build + docker-compose up -d + docker-compose push \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..130b4fd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.12-slim + +WORKDIR /usr/src/app + +RUN pip install pipenv + +COPY Pipfile Pipfile.lock ./ + +RUN pipenv install --deploy --ignore-pipfile + +COPY . . + +EXPOSE 5000 + +ENV NAME spotifyquiz + +CMD ["pipenv", "run", "python", "./app.py"] \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..d5776f2 --- /dev/null +++ b/Pipfile @@ -0,0 +1,18 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +flask = "*" +requests = "*" +pymongo = "*" +flask-pymongo = "*" +coverage = "*" +exceptiongroup = "*" + +[dev-packages] +pytest = "*" + +[requires] +python = "3.12" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..9bfb780 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,472 @@ +{ + "_meta": { + "hash": { + "sha256": "d8c91efbf17e4d411e01866a93cb9920dc413bc566c7ad48d1857a1216f458e3" + }, + "pipfile-spec": 6, + "requires": { + "python": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "blinker": { + "hashes": [ + "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9", + "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182" + ], + "markers": "python_version >= '3.8'", + "version": "==1.7.0" + }, + "certifi": { + "hashes": [ + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.11.17" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "coverage": { + "hashes": [ + "sha256:007a7e49831cfe387473e92e9ff07377f6121120669ddc39674e7244350a6a29", + "sha256:1191270b06ecd68b1d00897b2daddb98e1719f63750969614ceb3438228c088e", + "sha256:1367aa411afb4431ab58fd7ee102adb2665894d047c490649e86219327183134", + "sha256:1f0f8f0c497eb9c9f18f21de0750c8d8b4b9c7000b43996a094290b59d0e7523", + "sha256:222b038f08a7ebed1e4e78ccf3c09a1ca4ac3da16de983e66520973443b546bc", + "sha256:243576944f7c1a1205e5cd658533a50eba662c74f9be4c050d51c69bd4532936", + "sha256:2e9223a18f51d00d3ce239c39fc41410489ec7a248a84fab443fbb39c943616c", + "sha256:307aecb65bb77cbfebf2eb6e12009e9034d050c6c69d8a5f3f737b329f4f15fb", + "sha256:31c0b1b8b5a4aebf8fcd227237fc4263aa7fa0ddcd4d288d42f50eff18b0bac4", + "sha256:3b15e03b8ee6a908db48eccf4e4e42397f146ab1e91c6324da44197a45cb9132", + "sha256:3c854c1d2c7d3e47f7120b560d1a30c1ca221e207439608d27bc4d08fd4aeae8", + "sha256:475de8213ed95a6b6283056d180b2442eee38d5948d735cd3d3b52b86dd65b92", + "sha256:50c472c1916540f8b2deef10cdc736cd2b3d1464d3945e4da0333862270dcb15", + "sha256:593efa42160c15c59ee9b66c5f27a453ed3968718e6e58431cdfb2d50d5ad284", + "sha256:65d716b736f16e250435473c5ca01285d73c29f20097decdbb12571d5dfb2c94", + "sha256:733537a182b5d62184f2a72796eb6901299898231a8e4f84c858c68684b25a70", + "sha256:757453848c18d7ab5d5b5f1827293d580f156f1c2c8cef45bfc21f37d8681069", + "sha256:79c32f875fd7c0ed8d642b221cf81feba98183d2ff14d1f37a1bbce6b0347d9f", + "sha256:7f3bad1a9313401ff2964e411ab7d57fb700a2d5478b727e13f156c8f89774a0", + "sha256:7fbf3f5756e7955174a31fb579307d69ffca91ad163467ed123858ce0f3fd4aa", + "sha256:811ca7373da32f1ccee2927dc27dc523462fd30674a80102f86c6753d6681bc6", + "sha256:89400aa1752e09f666cc48708eaa171eef0ebe3d5f74044b614729231763ae69", + "sha256:8c944cf1775235c0857829c275c777a2c3e33032e544bcef614036f337ac37bb", + "sha256:9437a4074b43c177c92c96d051957592afd85ba00d3e92002c8ef45ee75df438", + "sha256:9e17d9cb06c13b4f2ef570355fa45797d10f19ca71395910b249e3f77942a837", + "sha256:9ede881c7618f9cf93e2df0421ee127afdfd267d1b5d0c59bcea771cf160ea4a", + "sha256:a1f76cfc122c9e0f62dbe0460ec9cc7696fc9a0293931a33b8870f78cf83a327", + "sha256:a2ac4245f18057dfec3b0074c4eb366953bca6787f1ec397c004c78176a23d56", + "sha256:a702e66483b1fe602717020a0e90506e759c84a71dbc1616dd55d29d86a9b91f", + "sha256:ad2453b852a1316c8a103c9c970db8fbc262f4f6b930aa6c606df9b2766eee06", + "sha256:af75cf83c2d57717a8493ed2246d34b1f3398cb8a92b10fd7a1858cad8e78f59", + "sha256:afdcc10c01d0db217fc0a64f58c7edd635b8f27787fea0a3054b856a6dff8717", + "sha256:c59a3e59fb95e6d72e71dc915e6d7fa568863fad0a80b33bc7b82d6e9f844973", + "sha256:cad9afc1644b979211989ec3ff7d82110b2ed52995c2f7263e7841c846a75348", + "sha256:d299d379b676812e142fb57662a8d0d810b859421412b4d7af996154c00c31bb", + "sha256:d31650d313bd90d027f4be7663dfa2241079edd780b56ac416b56eebe0a21aab", + "sha256:d874434e0cb7b90f7af2b6e3309b0733cde8ec1476eb47db148ed7deeb2a9494", + "sha256:db0338c4b0951d93d547e0ff8d8ea340fecf5885f5b00b23be5aa99549e14cfd", + "sha256:df04c64e58df96b4427db8d0559e95e2df3138c9916c96f9f6a4dd220db2fdb7", + "sha256:e995efb191f04b01ced307dbd7407ebf6e6dc209b528d75583277b10fd1800ee", + "sha256:eda7f6e92358ac9e1717ce1f0377ed2b9320cea070906ece4e5c11d172a45a39", + "sha256:ee453085279df1bac0996bc97004771a4a052b1f1e23f6101213e3796ff3cb85", + "sha256:ee6621dccce8af666b8c4651f9f43467bfbf409607c604b840b78f4ff3619aeb", + "sha256:eee5e741b43ea1b49d98ab6e40f7e299e97715af2488d1c77a90de4a663a86e2", + "sha256:f3bfd2c2f0e5384276e12b14882bf2c7621f97c35320c3e7132c156ce18436a1", + "sha256:f501e36ac428c1b334c41e196ff6bd550c0353c7314716e80055b1f0a32ba394", + "sha256:f9191be7af41f0b54324ded600e8ddbcabea23e1e8ba419d9a53b241dece821d", + "sha256:fbd8a5fe6c893de21a3c6835071ec116d79334fbdf641743332e442a3466f7ea", + "sha256:fc200cec654311ca2c3f5ab3ce2220521b3d4732f68e1b1e79bef8fcfc1f2b97", + "sha256:ff4800783d85bff132f2cc7d007426ec698cdce08c3062c8d501ad3f4ea3d16c", + "sha256:ffb0eacbadb705c0a6969b0adf468f126b064f3362411df95f6d4f31c40d31c1", + "sha256:fff0b2f249ac642fd735f009b8363c2b46cf406d3caec00e4deeb79b5ff39b40" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==7.3.3" + }, + "dnspython": { + "hashes": [ + "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8", + "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984" + ], + "markers": "python_version >= '3.8' and python_version < '4.0'", + "version": "==2.4.2" + }, + "exceptiongroup": { + "hashes": [ + "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", + "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==1.2.0" + }, + "flask": { + "hashes": [ + "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638", + "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, + "flask-pymongo": { + "hashes": [ + "sha256:620eb02dc8808a5fcb90f26cab6cba9d6bf497b15032ae3ca99df80366e33314", + "sha256:8a9577a2c6d00b49f21cb5a5a8d72561730364a2d745551a85349ab02f86fc73" + ], + "index": "pypi", + "version": "==2.3.0" + }, + "idna": { + "hashes": [ + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + ], + "markers": "python_version >= '3.5'", + "version": "==3.6" + }, + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.2" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.2" + }, + "markupsafe": { + "hashes": [ + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.3" + }, + "pymongo": { + "hashes": [ + "sha256:00c199e1c593e2c8b033136d7a08f0c376452bac8a896c923fcd6f419e07bdd2", + "sha256:010bc9aa90fd06e5cc52c8fac2c2fd4ef1b5f990d9638548dde178005770a5e8", + "sha256:026a24a36394dc8930cbcb1d19d5eb35205ef3c838a7e619e04bd170713972e7", + "sha256:061598cbc6abe2f382ab64c9caa83faa2f4c51256f732cdd890bcc6e63bfb67e", + "sha256:13552ca505366df74e3e2f0a4f27c363928f3dff0eef9f281eb81af7f29bc3c5", + "sha256:13d613c866f9f07d51180f9a7da54ef491d130f169e999c27e7633abe8619ec9", + "sha256:144a31391a39a390efce0c5ebcaf4bf112114af4384c90163f402cec5ede476b", + "sha256:1461199b07903fc1424709efafe379205bf5f738144b1a50a08b0396357b5abf", + "sha256:154b361dcb358ad377d5d40df41ee35f1cc14c8691b50511547c12404f89b5cb", + "sha256:1c5654bb8bb2bdb10e7a0bc3c193dd8b49a960b9eebc4381ff5a2043f4c3c441", + "sha256:1de3c6faf948f3edd4e738abdb4b76572b4f4fdfc1fed4dad02427e70c5a6219", + "sha256:1ed23b0e2dac6f84f44c8494fbceefe6eb5c35db5c1099f56ab78fc0d94ab3af", + "sha256:1f2b856518bfcfa316c8dae3d7b412aecacf2e8ba30b149f5eb3b63128d703b9", + "sha256:2346450a075625c4d6166b40a013b605a38b6b6168ce2232b192a37fb200d588", + "sha256:262356ea5fcb13d35fb2ab6009d3927bafb9504ef02339338634fffd8a9f1ae4", + "sha256:27b81ecf18031998ad7db53b960d1347f8f29e8b7cb5ea7b4394726468e4295e", + "sha256:2940aa20e9cc328e8ddeacea8b9a6f5ddafe0b087fedad928912e787c65b4909", + "sha256:2d4ccac3053b84a09251da8f5350bb684cbbf8c8c01eda6b5418417d0a8ab198", + "sha256:2dd2f6960ee3c9360bed7fb3c678be0ca2d00f877068556785ec2eb6b73d2414", + "sha256:3071ec998cc3d7b4944377e5f1217c2c44b811fae16f9a495c7a1ce9b42fb038", + "sha256:3094c7d2f820eecabadae76bfec02669567bbdd1730eabce10a5764778564f7b", + "sha256:30b2c9caf3e55c2e323565d1f3b7e7881ab87db16997dc0cbca7c52885ed2347", + "sha256:3177f783ae7e08aaf7b2802e0df4e4b13903520e8380915e6337cdc7a6ff01d8", + "sha256:31dab1f3e1d0cdd57e8df01b645f52d43cc1b653ed3afd535d2891f4fc4f9712", + "sha256:33bb16a07d3cc4e0aea37b242097cd5f7a156312012455c2fa8ca396953b11c4", + "sha256:349093675a2d3759e4fb42b596afffa2b2518c890492563d7905fac503b20daa", + "sha256:39d77d8bbb392fa443831e6d4ae534237b1f4eee6aa186f0cdb4e334ba89536e", + "sha256:3a7f02a58a0c2912734105e05dedbee4f7507e6f1bd132ebad520be0b11d46fd", + "sha256:3b287e814a01deddb59b88549c1e0c87cefacd798d4afc0c8bd6042d1c3d48aa", + "sha256:3c74f4725485f0a7a3862cfd374cc1b740cebe4c133e0c1425984bcdcce0f4bb", + "sha256:3cadf7f4c8e94d8a77874b54a63c80af01f4d48c4b669c8b6867f86a07ba994f", + "sha256:3d18a9b9b858ee140c15c5bfcb3e66e47e2a70a03272c2e72adda2482f76a6ad", + "sha256:3f0e6a6c807fa887a0c51cc24fe7ea51bb9e496fe88f00d7930063372c3664c3", + "sha256:4344c30025210b9fa80ec257b0e0aab5aa1d5cca91daa70d82ab97b482cc038e", + "sha256:4497d49d785482cc1a44a0ddf8830b036a468c088e72a05217f5b60a9e025012", + "sha256:547dc5d7f834b1deefda51aedb11a7af9c51c45e689e44e14aa85d44147c7657", + "sha256:5556e306713e2522e460287615d26c0af0fe5ed9d4f431dad35c6624c5d277e9", + "sha256:55dac73316e7e8c2616ba2e6f62b750918e9e0ae0b2053699d66ca27a7790105", + "sha256:56816e43c92c2fa8c11dc2a686f0ca248bea7902f4a067fa6cbc77853b0f041e", + "sha256:5bd94c503271e79917b27c6e77f7c5474da6930b3fb9e70a12e68c2dff386b9a", + "sha256:5ec31adc2e988fd7db3ab509954791bbc5a452a03c85e45b804b4bfc31fa221d", + "sha256:69247f7a2835fc0984bbf0892e6022e9a36aec70e187fcfe6cae6a373eb8c4de", + "sha256:6a0ae7a48a6ef82ceb98a366948874834b86c84e288dbd55600c1abfc3ac1d88", + "sha256:6a1810c2cbde714decf40f811d1edc0dae45506eb37298fd9d4247b8801509fe", + "sha256:6dcc95f4bb9ed793714b43f4f23a7b0c57e4ef47414162297d6f650213512c19", + "sha256:76013fef1c9cd1cd00d55efde516c154aa169f2bf059b197c263a255ba8a9ddf", + "sha256:77e0df59b1a4994ad30c6d746992ae887f9756a43fc25dec2db515d94cf0222d", + "sha256:7bb0e9049e81def6829d09558ad12d16d0454c26cabe6efc3658e544460688d9", + "sha256:88beb444fb438385e53dc9110852910ec2a22f0eab7dd489e827038fdc19ed8d", + "sha256:8b47ebd89e69fbf33d1c2df79759d7162fc80c7652dacfec136dae1c9b3afac7", + "sha256:8d219b4508f71d762368caec1fc180960569766049bbc4d38174f05e8ef2fe5b", + "sha256:8ec75f35f62571a43e31e7bd11749d974c1b5cd5ea4a8388725d579263c0fdf6", + "sha256:9167e735379ec43d8eafa3fd675bfbb12e2c0464f98960586e9447d2cf2c7a83", + "sha256:9a710c184ba845afb05a6f876edac8f27783ba70e52d5eaf939f121fc13b2f59", + "sha256:9aafd036f6f2e5ad109aec92f8dbfcbe76cff16bad683eb6dd18013739c0b3ae", + "sha256:9c79d597fb3a7c93d7c26924db7497eba06d58f88f58e586aa69b2ad89fee0f8", + "sha256:a2831e05ce0a4df10c4ac5399ef50b9a621f90894c2a4d2945dc5658765514ed", + "sha256:a5e641f931c5cd95b376fd3c59db52770e17bec2bf86ef16cc83b3906c054845", + "sha256:b10d8cda9fc2fcdcfa4a000aa10413a2bf8b575852cd07cb8a595ed09689ca98", + "sha256:b435b13bb8e36be11b75f7384a34eefe487fe87a6267172964628e2b14ecf0a7", + "sha256:b7b1a83ce514700276a46af3d9e481ec381f05b64939effc9065afe18456a6b9", + "sha256:b8729dbf25eb32ad0dc0b9bd5e6a0d0b7e5c2dc8ec06ad171088e1896b522a74", + "sha256:bbed8cccebe1169d45cedf00461b2842652d476d2897fd1c42cf41b635d88746", + "sha256:c258dbacfff1224f13576147df16ce3c02024a0d792fd0323ac01bed5d3c545d", + "sha256:c30a9e06041fbd7a7590693ec5e407aa8737ad91912a1e70176aff92e5c99d20", + "sha256:c91ea3915425bd4111cb1b74511cdc56d1d16a683a48bf2a5a96b6a6c0f297f7", + "sha256:d0355cff58a4ed6d5e5f6b9c3693f52de0784aa0c17119394e2a8e376ce489d4", + "sha256:d483793a384c550c2d12cb794ede294d303b42beff75f3b3081f57196660edaf", + "sha256:d4c2be9760b112b1caf649b4977b81b69893d75aa86caf4f0f398447be871f3c", + "sha256:d8e62d06e90f60ea2a3d463ae51401475568b995bafaffd81767d208d84d7bb1", + "sha256:da08ea09eefa6b960c2dd9a68ec47949235485c623621eb1d6c02b46765322ac", + "sha256:dd1fa413f8b9ba30140de198e4f408ffbba6396864c7554e0867aa7363eb58b2", + "sha256:e2aced6fb2f5261b47d267cb40060b73b6527e64afe54f6497844c9affed5fd0", + "sha256:e438417ce1dc5b758742e12661d800482200b042d03512a8f31f6aaa9137ad40", + "sha256:e470fa4bace5f50076c32f4b3cc182b31303b4fefb9b87f990144515d572820b", + "sha256:eaf2f65190c506def2581219572b9c70b8250615dc918b3b7c218361a51ec42e", + "sha256:ef102a67ede70e1721fe27f75073b5314911dbb9bc27cde0a1c402a11531e7bd", + "sha256:ef801027629c5b511cf2ba13b9be29bfee36ae834b2d95d9877818479cdc99ea", + "sha256:f7acc03a4f1154ba2643edeb13658d08598fe6e490c3dd96a241b94f09801626", + "sha256:f9756f1d25454ba6a3c2f1ef8b7ddec23e5cdeae3dc3c3377243ae37a383db00", + "sha256:ff62ba8ff70f01ab4fe0ae36b2cb0b5d1f42e73dfc81ddf0758cd9f77331ad25", + "sha256:ff925f1cca42e933376d09ddc254598f8c5fcd36efc5cac0118bb36c36217c41" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==4.6.1" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "urllib3": { + "hashes": [ + "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", + "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" + ], + "markers": "python_version >= '3.8'", + "version": "==2.1.0" + }, + "werkzeug": { + "hashes": [ + "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc", + "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.1" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.3" + } + } +} diff --git a/README.md b/README.md index 73a6948..f94cbe7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ -# Final Project + -An exercise to put to practice software development teamwork, subsystem communication, containers, deployment, and CI/CD pipelines. See [instructions](./instructions.md) for details. +# Do You Know Your Own Music? + +A web app that gives you info about your Spotify listening habits, recommends songs, and also tests your music knowledge! +Please contact us with your spotify email, so that we can register you on the API developer server! +## Deployed App +https://whale-app-4h3br.ondigitalocean.app/ + +## DockerHub Links +https://hub.docker.com/r/emxyz/5-final-project-fivepeat-web + +## Instructions to Run +1. Clone the repository from Github +2. Navigate to the project root folder +# Local +3. `pipenv install` +4. `pipenv shell` +5. `python app.py` + +# Using Docker +3. `docker-compose up —build` + +### Contributors +Danica Jin - https://github.com/dj9771 +Emma Zheng - https://github.com/emxyz +Gabriel Park - https://github.com/gmp9469 +Megan Chen - https://github.com/meganchen99 diff --git a/app.py b/app.py new file mode 100644 index 0000000..860a1c7 --- /dev/null +++ b/app.py @@ -0,0 +1,219 @@ +import requests +from flask import Flask, redirect, request, jsonify, session, render_template +from datetime import datetime +import urllib.parse +import random +import db + +app = Flask(__name__) +app.secret_key = 'a1w12lje-df2jgd45kjg-s2fs3d8' + +CLIENT_ID = 'ebd35c5b8a8644bc9eb08657951956ac' +CLIENT_SECRET = '9a0a789fc56b4a9fa43c66a093dfdd84' +REDIRECT_URI = 'https://whale-app-4h3br.ondigitalocean.app/callback' +AUTH_URL = 'https://accounts.spotify.com/authorize' +TOKEN_URL = 'https://accounts.spotify.com/api/token' +API_BASE_URL = 'https://api.spotify.com/v1/' + +@app.route('/') +def index(): + return render_template('welcome.html') + +@app.route('/login') +def login(): + scope = 'user-read-private user-read-email user-top-read user-library-read' + params = { + 'client_id': CLIENT_ID, + 'response_type': 'code', + 'scope': scope, + 'redirect_uri': REDIRECT_URI, + 'show_dialog': True + } + + auth_url = f"{AUTH_URL}?{urllib.parse.urlencode(params)}" + return redirect(auth_url) + +@app.route('/callback') +def callback(): + if 'error' in request.args: + return jsonify({"error": request.args['error']}) + + if 'code' in request.args: + req_body = { + 'code': request.args['code'], + 'grant_type': 'authorization_code', + 'redirect_uri': REDIRECT_URI, + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET + } + + response = requests.post(TOKEN_URL, data=req_body) + token_info = response.json() + session['access_token'] = token_info['access_token'] + session['refresh_token'] = token_info['refresh_token'] + session['expires_at'] = datetime.now().timestamp() + token_info['expires_in'] + return redirect('/top_artists') + +@app.route('/top_artists') +def top_artists(): + if 'access_token' not in session: + return redirect('/login') + if datetime.now().timestamp() > session['expires_at']: + return redirect('/refresh-token') + + headers = { + 'Authorization': f"Bearer {session['access_token']}" + } + + user_info = requests.get(API_BASE_URL + 'me', headers=headers) + display_name = user_info.json().get('display_name') + response = requests.get(API_BASE_URL + 'me/top/artists?limit=3', headers=headers) + artists_data = response.json().get('items', []) + if artists_data: + artists_info = [] + for artist in artists_data: + artist_info = { + 'name': artist.get('name', 'Unknown Artist'), + 'image_url': artist['images'][0]['url'] if artist.get('images') else None + } + artists_info.append(artist_info) + + return render_template('top_artists.html', display_name=display_name, artists=artists_info) + else: + return "empty response" + + +@app.route('/refresh-token') +def refresh_token(): + req_body = {} + if 'refresh_token' not in session: + return redirect('/login') + if datetime.now().timestamp() > session['expires_at']: + req_body = { + 'grant_type': 'refresh_token', + 'refresh_token': session['refresh_token'], + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET + } + response = requests.post(TOKEN_URL, data=req_body) + new_token_info = response.json() + session['access_token'] = new_token_info['access_token'] + session['expires_at'] = datetime.now().timestamp() + new_token_info['expires_in'] + return redirect('/top_artists') + +@app.route('/quiz') +def quiz(): + if 'access_token' not in session: + return redirect('/login') + if datetime.now().timestamp() > session['expires_at']: + return redirect('/refresh-token') + + headers = { + 'Authorization': f"Bearer {session['access_token']}" + } + saved_tracks_response = requests.get(API_BASE_URL + 'me/top/tracks?limit=100', headers=headers) + saved_tracks = saved_tracks_response.json()['items'] + selected_tracks = random.sample(saved_tracks, 2) + + track_ids = ','.join([track['id'] for track in selected_tracks]) + features_response = requests.get(API_BASE_URL + f'audio-features?ids={track_ids}', headers=headers) + audio_features = features_response.json()['audio_features'] + + song1, song2 = selected_tracks + if random.choice([True, False]): + features1 = [feature for feature in audio_features if feature['id'] == song1['id']][0] + features2 = [feature for feature in audio_features if feature['id'] == song2['id']][0] + session['correct_answer'] = song1['id'] + else: + features1 = [feature for feature in audio_features if feature['id'] == song2['id']][0] + features2 = [feature for feature in audio_features if feature['id'] == song1['id']][0] + session['correct_answer'] = song2['id'] + + session['quiz_data'] = { + 'song1': song1, + 'song2': song2, + 'features1': features1, + 'features2': features2, + 'song1_image_url': selected_tracks[0].get('album', {}).get('images', [])[0]['url'] if selected_tracks[0].get('album') else None, + 'song2_image_url': selected_tracks[1].get('album', {}).get('images', [])[0]['url'] if selected_tracks[1].get('album') else None + } + + return render_template('quiz.html', song1_name=song1, song2_name=song2, features1=features1, features2=features2) + +@app.route('/quiz/submit', methods=['POST']) +def quiz_submit(): + submitted_answer = request.form['answer1'] + correct_answer = session.get('correct_answer') + quiz_data = session.get('quiz_data', {}) + + song1_features = quiz_data['song1'].get('features1') if 'features1' in quiz_data.get('song1', {}) else None + song2_features = quiz_data['song2'].get('features2') if 'features2' in quiz_data.get('song2', {}) else None + + if submitted_answer == correct_answer: + result = "Correct!" + else: + result = "Incorrect!" + + """Saves quiz into the MongoDB database""" + data = { + "Song 1 Name": quiz_data['song1']['name'], + "Song 1 Features": song1_features, + "Song 2 Name": quiz_data['song2']['name'], + "Song 2 Features": song2_features, + "Song 1 Image URL": quiz_data['song1_image_url'], + "Song 2 Image URL": quiz_data['song2_image_url'], + "Result": result, + "timestamp_field": datetime.utcnow() + + } + + db.collection.insert_one(data) + + return render_template('quiz_result.html', result=result) + +@app.route('/random_song') +def random_song(): + if 'access_token' not in session: + return redirect('/login') + if datetime.now().timestamp() > session['expires_at']: + return redirect('/refresh-token') + + headers = { + 'Authorization': f"Bearer {session['access_token']}" + } + + response = requests.get(API_BASE_URL + 'browse/featured-playlists', headers=headers) + featured_playlists_data = response.json().get('playlists', {}).get('items', []) + if featured_playlists_data: + selected_playlist = random.choice(featured_playlists_data) + playlist_endpoint = selected_playlist['href'] + playlist_response = requests.get(playlist_endpoint, headers=headers) + playlist_data = playlist_response.json() + + tracks_data = playlist_data.get('tracks', {}).get('items', []) + if tracks_data: + random_track = random.choice(tracks_data)['track'] + track_info = { + 'name': random_track.get('name', 'Unknown Track'), + 'artists': [artist['name'] for artist in random_track.get('artists', [])], + 'album': random_track.get('album', {}).get('name', 'Unknown Album'), + 'image_url': random_track.get('album', {}).get('images', [])[0]['url'] if random_track.get('album') else None, + 'external_url': random_track.get('external_urls', {}).get('spotify', '') + } + + return render_template('song_gen.html', track_info=track_info) + else: + return jsonify({"message": "No tracks found in the selected playlist"}) + else: + return jsonify({"message": "No featured playlists available"}) + +@app.route('/history') +def history(): + if 'access_token' not in session: + return redirect('/login') + quiz_history = db.collection.find().sort("timestamp_field", -1) + return render_template('history.html', quiz_history=quiz_history) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/db.py b/db.py new file mode 100644 index 0000000..f6fdb47 --- /dev/null +++ b/db.py @@ -0,0 +1,23 @@ +"""Database for this application""" +import os +from pymongo import MongoClient + +"""DATABASE_HOST = os.getenv("DATABASE_HOST", "localhost") +CONNECTION_STRING = f"mongodb://{DATABASE_HOST}:27017/" + +client = MongoClient(CONNECTION_STRING) +db = client['quiz_game_db'] +collection = db['quiz_scores']""" + + +username = "doadmin" +password = "Si7Hu4p9ln31F026" +host = "db-mongodb-nyc3-33511-709f7b62.mongo.ondigitalocean.com" +database_name = "admin" + +uri = f"mongodb+srv://{username}:{password}@{host}/{database_name}?retryWrites=true&w=majority" +client = MongoClient(uri) +db = client['quiz_game_db'] +collection = db['quiz_scores'] + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d1dd344 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.12' +services: + web: + build: + context: . + dockerfile: ./Dockerfile + ports: + - "5000:5000" + depends_on: + - db + environment: + - DATABASE_HOST=db + + db: + image: mongo + ports: + - "27017:27017" \ No newline at end of file diff --git a/static/css/history.css b/static/css/history.css new file mode 100644 index 0000000..ec9beda --- /dev/null +++ b/static/css/history.css @@ -0,0 +1,106 @@ + +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f4f4; + background-color: black; + color: rgb(255, 255, 255); +} +h1 { + text-align: center; + color: rgb(215, 215, 215); +} +ul { + list-style-type: none; + padding: 0; + display: flex; + flex-direction: column; + align-items: center; +} +li { + margin-bottom: 20px; + padding: 15px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + width: 80%; + max-width: 800px; +} +.quiz-box { + display: flex; + justify-content: space-around; + align-items: center; + padding: 20px; + gap: 20px; + border: 2px solid #00ff00; +} +.quiz-box.incorrect { + border-color: #9b1010; +} +.quiz-box.correct { + border-color: #1DB954; +} +.quiz-box.incorrect .song img { + border-color: #9b1010; +} +.song { + text-align: center; + flex: 1; +} +.song img { + max-width: 40%; + height: auto; + border-radius: 8px; + margin-right: 10px; + margin-bottom: 10px; + border: 2px solid #1DB954 +} +.song strong { + display: block; + margin-top: 8px; +} +.features { + display: flex; + justify-content: space-between; + margin-top: 10px; +} + +.column { + display: flex; + flex-direction: column; + flex: 1; +} +.column:first-child { + margin-right: -20px; +} + +.column:last-child { + margin-left: -20px; +} +.song p { + margin: 5px 0; + font-size: 0.5em; +} + +.navigation-buttons { + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} +.btn { + display: inline-block; + padding: 10px 20px; + margin: 5px; + border: 1px solid #000; + background-color: #1DB954; + color: #ffffff; + text-decoration: none; + border-radius: 5px; + text-align: center; +} + +.btn:hover { + background-color: #f4f4f4; + color: #333; +} diff --git a/static/css/quiz_result.css b/static/css/quiz_result.css new file mode 100644 index 0000000..33388d2 --- /dev/null +++ b/static/css/quiz_result.css @@ -0,0 +1,37 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f4f4; + background-color: black; + color: rgb(255, 255, 255); + justify-content: center; +} +h1 { + text-align: center; + color: rgb(215, 215, 215); + padding-top: 50px; +} +.navigation-buttons { + text-align: center; + display: flex; + justify-content: center; + align-items: center; + height: 30vh; +} +.btn { + display: inline-block; + padding: 10px 20px; + margin: 5px; + border: 1px solid #000; + background-color: #1DB954; + color: #ffffff; + text-decoration: none; + border-radius: 5px; + text-align: center; +} + +.btn:hover { + background-color: #f4f4f4; + color: #333; +} diff --git a/static/css/welcome_style.css b/static/css/welcome_style.css new file mode 100644 index 0000000..9a8f856 --- /dev/null +++ b/static/css/welcome_style.css @@ -0,0 +1,37 @@ +body { + font-family: Arial, sans-serif; + background-color: #000000; + color: white; + text-align: center; + padding: 20px; +} + +header h1 { + font-size: 2em; +} + +.quiz-intro p { + font-size: 1.2em; +} + +.button { + display: inline-block; + background-color: #1DB954; + color: white; + padding: 10px 20px; + font-size: 16px; + border: none; + border-radius: 5px; + text-decoration: none; + cursor: pointer; + transition: background-color 0.3s; +} + +.button:hover { + background-color: #1ed760; +} + + +footer p { + font-size: 0.8em; +} diff --git a/templates/history.html b/templates/history.html new file mode 100644 index 0000000..a98ee4c --- /dev/null +++ b/templates/history.html @@ -0,0 +1,53 @@ + + +
+{{ feature_key }}: {{ quiz['Song 1 Features'][feature_key] }}
+ {% endfor %} +{{ feature_key }}: {{ quiz['Song 1 Features'][feature_key] }}
+ {% endfor %} +{{ feature_key }}: {{ quiz['Song 2 Features'][feature_key] }}
+ {% endfor %} +{{ feature_key }}: {{ quiz['Song 2 Features'][feature_key] }}
+ {% endfor %} +{{ feature_key }}: {{ features1[feature_key] }}
+ {% endfor %} +{{ feature_key }}: {{ features2[feature_key] }}
+ {% endfor %} +Artist: {{ track_info.artists | join(', ') }}
+Album: {{ track_info.album }}
+ +No image available for {{ artist.name }}
+ {% endif %} +{{ artist.name }}
+Let's test your music knowledge...
+Are you ready to challenge yourself with our music quiz based off of your Spotify listening metrics?
+ Login with Spotify +