diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..210f4cf5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + #- package-ecosystem: "npm" + # directory: "/expo" + # schedule: + # interval: "weekly" + - package-ecosystem: "npm" + directory: "/backend" + schedule: + interval: "weekly" diff --git "a/Document du projet/It\303\251rations/Iteration1.md" "b/Document du projet/It\303\251rations/Iteration1.md" new file mode 100644 index 00000000..854a7dd7 --- /dev/null +++ "b/Document du projet/It\303\251rations/Iteration1.md" @@ -0,0 +1,29 @@ +# Itération 1 + +## Objectifs de l'itération + +L'objectif de l'itération était de développer le coeur de notre application, c'est-à-dire : + +- La possbilité de créer un compte à partir de son compte Spotify +- La possibilité de créer des salles d'écoute +- La possibilité de générer un lien vers une salle d'écoute et de rejoindre celle-ci via ce lien (dans l'application ou dans le web) +- Ajouter une musique dans la file d'attente d'une salle d'écoute grâce à un lien Spotify +- Consulter la file d'attente d'une salle d'écoute +- Pouvoir gérer le flux de lecture (gestion du volume, play/pause, suivant/précédent), et ce de différentes manières (Widget intégré sur le site ou lecteur externe sur l'application de la plateforme) + +En d'autres termes, l'utilisateur devait déjà pouvoir utiliser notre application. Il devait rejoindre l'application avec son compte Spotify, créer une salle d'écoute, inviter et écouter de la musique avec ses amis. + +## Ce qui est réellement terminé + +A la fin de cette première itération, nous pouvons : + +- Créer un compte avec Spotify +- Créer une salle d'écoute +- Ajouter une musique dans la file d'attente avec un lien Spotify +- Créer un lien d'invitation et utiliser ce lien pour rejoindre la salle + +## Ce qui est prévu pour l'itération 2 + +Lors de l'itération 2, nous allons continuer l'implémentation du lecteur audio des différentes plateformes de streaming. + +Pour ce qui est des nouvelles fonctionnalités, nous allons ajouter d'autres options pour créer un compte, notamment avec un compte Google et une adresse email/mot de passe. Nous allons également ajouter la possiblité de lier son compte Spotify ou SoundCloud à son compte utilisateur. L'utilisateur pourra aussi consulter l'historique et les détails de ses salles d'écoute passées. \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 41f7aa06..286f0ddb 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,11 +10,15 @@ "license": "ISC", "dependencies": { "@fastify/cors": "^8.5.0", + "@fastify/cookie": "^9.2.0", + "@supabase/auth-helpers-nextjs": "^0.8.7", + "@supabase/ssr": "^0.0.10", "@supabase/supabase-js": "^2.39.1", "dotenv": "^16.3.1", "fastify": "^4.25.2", "fastify-socket.io": "^5.0.0", - "socket.io": "^4.7.3" + "socket.io": "^4.7.3", + "supabase": "^1.131.3" }, "devDependencies": { "@types/node": "^20.10.5", @@ -46,6 +50,15 @@ "fast-uri": "^2.0.0" } }, + "node_modules/@fastify/cookie": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-9.2.0.tgz", + "integrity": "sha512-fkg1yjjQRHPFAxSHeLC8CqYuNzvR6Lwlj/KjrzQcGjNBK+K82nW+UfCjfN71g1GkoVoc1GTOgIWkFJpcMfMkHQ==", + "dependencies": { + "cookie-signature": "^1.1.0", + "fastify-plugin": "^4.0.0" + } + }, "node_modules/@fastify/cors": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.5.0.tgz", @@ -103,6 +116,29 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, + "node_modules/@supabase/auth-helpers-nextjs": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.8.7.tgz", + "integrity": "sha512-iYdOjFo0GkRvha340l8JdCiBiyXQuG9v8jnq7qMJ/2fakrskRgHTCOt7ryWbip1T6BExcWKC8SoJrhCzPOxhhg==", + "dependencies": { + "@supabase/auth-helpers-shared": "0.6.3", + "set-cookie-parser": "^2.6.0" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.19.0" + } + }, + "node_modules/@supabase/auth-helpers-shared": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.6.3.tgz", + "integrity": "sha512-xYQRLFeFkL4ZfwC7p9VKcarshj3FB2QJMgJPydvOY7J5czJe6xSG5/wM1z63RmAzGbCkKg+dzpq61oeSyWiGBQ==", + "dependencies": { + "jose": "^4.14.4" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.19.0" + } + }, "node_modules/@supabase/functions-js": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz", @@ -148,6 +184,18 @@ "ws": "^8.14.2" } }, + "node_modules/@supabase/ssr": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.0.10.tgz", + "integrity": "sha512-eVs7+bNlff8Fd79x8K3Jbfpmf8P8QRA1Z6rUDN+fi4ReWvRBZyWOFfR6eqlsX6vTjvGgTiEqujFSkv2PYW5kbQ==", + "dependencies": { + "cookie": "^0.5.0", + "ramda": "^0.29.0" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.33.1" + } + }, "node_modules/@supabase/storage-js": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.5.tgz", @@ -274,6 +322,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -380,6 +439,20 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/bin-links": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.3.tgz", + "integrity": "sha512-obsRaULtJurnfox/MDwgq6Yo9kzbv1CPTk/1/s7Z/61Lezc8IKkFCOXNeVLXz0456WRzBQmSsDWlai2tIhBsfA==", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -461,6 +534,22 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/cmd-shim": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.2.tgz", + "integrity": "sha512-+FFYbB0YLaAkhkcrjkyNLYDiOsFSfRjwjY19LXk/psmMx1z00xlCv7hhQoTGXXIKi+YXHL/iiFo8NqMVQX9nOw==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -481,6 +570,14 @@ "node": ">= 0.6" } }, + "node_modules/cookie-signature": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz", + "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -499,6 +596,14 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -736,6 +841,28 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -761,6 +888,17 @@ "node": ">=14" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -769,6 +907,28 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -810,6 +970,18 @@ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "dev": true }, + "node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -835,6 +1007,14 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -885,6 +1065,14 @@ "node": ">=0.12.0" } }, + "node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -979,6 +1167,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mnemonist": { "version": "0.39.6", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz", @@ -1000,6 +1230,41 @@ "node": ">= 0.6" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/nodemon": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.2.tgz", @@ -1052,6 +1317,14 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1208,6 +1481,23 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, + "node_modules/ramda": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", + "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", @@ -1332,6 +1622,17 @@ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -1437,6 +1738,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/supabase": { + "version": "1.131.3", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.131.3.tgz", + "integrity": "sha512-C+d7OtqiGIpsmwD+XmGfnwcPIG2uI8wvHiQHv4tBQMmuEfb9oBMc5XDe8S4DsBh7gEPPUG69WDKoAne5MVf3/Q==", + "hasInstallScript": true, + "dependencies": { + "bin-links": "^4.0.3", + "https-proxy-agent": "^7.0.2", + "node-fetch": "^3.3.2", + "tar": "6.2.0" + }, + "bin": { + "supabase": "bin/supabase" + }, + "engines": { + "npm": ">=8" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1449,6 +1768,22 @@ "node": ">=4" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/thread-stream": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", @@ -1588,6 +1923,14 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", + "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -1608,6 +1951,18 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/ws": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", diff --git a/backend/package.json b/backend/package.json index a57b719e..21e658b6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,12 +10,16 @@ "author": "", "license": "ISC", "dependencies": { + "@fastify/cookie": "^9.2.0", + "@fastify/cors": "^8.5.0", + "@supabase/auth-helpers-nextjs": "^0.8.7", + "@supabase/ssr": "^0.0.10", "@supabase/supabase-js": "^2.39.1", "dotenv": "^16.3.1", "fastify": "^4.25.2", "fastify-socket.io": "^5.0.0", "socket.io": "^4.7.3", - "@fastify/cors": "^8.5.0" + "supabase": "^1.131.3" }, "devDependencies": { "@types/node": "^20.10.5", diff --git a/backend/src/lib/supabase.ts b/backend/src/lib/supabase.ts new file mode 100644 index 00000000..c31e7f0e --- /dev/null +++ b/backend/src/lib/supabase.ts @@ -0,0 +1,38 @@ +import { createServerClient } from "@supabase/ssr"; +import { FastifyReply, FastifyRequest } from "fastify"; + +export default function createClient(context: { + request: FastifyRequest; + response: FastifyReply; +}) { + if (!process.env.SUPABASE_URL || !process.env.SUPABASE_ANON_KEY) { + throw new Error( + "Missing SUPABASE_URL or SUPABASE_ANON_KEY environment variable" + ); + } + return createServerClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_ANON_KEY, + { + cookies: { + get: (key: any) => { + const cookies = context.request.cookies; + const cookie = cookies[key] ?? ""; + return decodeURIComponent(cookie); + }, + set: (key: any, value: any, options: any) => { + if (!context.response) return; + context.response.cookie(key, encodeURIComponent(value), { + ...options, + sameSite: "Lax", + httpOnly: true, + }); + }, + remove: (key: any, options: any) => { + if (!context.response) return; + context.response.cookie(key, "", { ...options, httpOnly: true }); + }, + }, + } + ); +} diff --git a/backend/src/route/AuthCallbackGET.ts b/backend/src/route/AuthCallbackGET.ts new file mode 100644 index 00000000..f3385c2e --- /dev/null +++ b/backend/src/route/AuthCallbackGET.ts @@ -0,0 +1,167 @@ +import { FastifyRequest, FastifyReply } from "fastify"; +import createClient from "../lib/supabase"; +import { adminSupabase } from "../server"; +import { Database } from "../types/dbTypes"; +import { PostgrestError } from "@supabase/supabase-js"; + +enum StreamingService { + Spotify = "a2d17b25-d87e-42af-9e79-fd4df6b59222", + SoundCloud = "c99631a2-f06c-4076-80c2-13428944c3a8", +} + +export default async function AuthCallbackGET( + request: FastifyRequest, + response: FastifyReply +) { + const supabaseUrl = process.env.SUPABASE_URL; + if (!supabaseUrl) + return response.code(400).send({ error: "Missing SUPABASE_URL" }); + const supabaseId = supabaseUrl.split(".")[0].split("//")[1]; + + if (!request.cookies["sb-" + supabaseId + "-auth-token-code-verifier"]) { + return response + .code(400) + .send({ error: "Missing cookie auth-token-code-verifier " }); + } + + const supabase = createClient({ + request, + response, + }); + const code = ( + request.query as { + code: string; + } + ).code; + if (!code) response.code(400).send({ error: "Missing code" }); + + const { data } = await supabase.auth.exchangeCodeForSession(code); + if (!data.session) + return response.code(400).send({ error: "Missing session" }); + const providerToken = data.session.provider_token; + const providerRefreshToken = data.session.provider_refresh_token; + const providerName = data.session.user.app_metadata.provider; + + if (!providerToken || !providerRefreshToken) + return response + .code(400) + .send({ error: "Missing provider token from " + providerName }); + + // verify if user already have an user_profile (if new acc, create one) + let userProfileId = await getUserProfile(data.user.id); + + if (!userProfileId) { + // New account + const { userProfileId: newUserProfileId, error } = await createAccount({ + full_name: data.user.user_metadata.full_name, + account_id: data.user.id, + username: null, + }); + if (error || !newUserProfileId) { + request.log.error("Impossible to create account: " + error); + return response.code(500).send({ error: error }); + } + userProfileId = newUserProfileId; + } + const providerTokenEnd = new Date(); + providerTokenEnd.setHours(providerTokenEnd.getHours() + 1); + const timestampZProviderTokenEnd = providerTokenEnd.toISOString(); + + const error = await upsertService({ + access_token: providerToken, + refresh_token: providerRefreshToken, + expires_in: timestampZProviderTokenEnd, + user_profile_id: userProfileId, + service_id: StreamingService.Spotify, + }); + + if (error) { + request.log.error("Upsert impossible, ", error); + return response.code(500).send({ error: "Server error." }); + } + + const refresh_token = data.session.refresh_token; + const redirectUrl = decodeURIComponent(request.url).split("redirect_url=")[1]; + + // redirect user to the redirect url with the refresh token + response.redirect( + redirectUrl + "#refresh_token=" + encodeURIComponent(refresh_token) + ); +} + +const getUserProfile = async (userId: string): Promise => { + const { data: userData } = await adminSupabase + .from("user_profile") + .select("*") + .eq("account_id", userId) + .single(); + return userData?.user_profile_id ?? null; +}; + +const alreadyBoundService = async ({ + service, + user_profile_id, +}: { + service: StreamingService; + user_profile_id: string; +}): Promise<{ alreadyBound: boolean; error: PostgrestError | null }> => { + const { data, error } = await adminSupabase + .from("bound_services") + .select("*") + .eq("user_profile_id", user_profile_id) + .eq("service_id", service); + return { + alreadyBound: data !== null && data.length > 0, + error: error, + }; +}; + +const createAccount = async ({ + full_name, + account_id, + username, +}: { + full_name: string; + account_id: string; + username: string | null; +}): Promise<{ userProfileId: string | null; error: PostgrestError | null }> => { + const { data, error } = await adminSupabase + .from("profile") + .insert({ + nickname: full_name, + }) + .select("*") + .single(); + + const user_profile_id = data?.id; + + if (!user_profile_id) + return { + userProfileId: null, + error: error, + }; + + const { data: dataUserProfile, error: errorUserprofile } = await adminSupabase + .from("user_profile") + .insert({ + account_id: account_id, + user_profile_id: user_profile_id, + username: username, // Add the missing 'username' property + }); + if (errorUserprofile) + return { + userProfileId: null, + error: errorUserprofile, + }; + return { + userProfileId: user_profile_id, + error: null, + }; +}; + +const upsertService = async ( + service: Database["public"]["Tables"]["bound_services"]["Row"] +): Promise => { + const { error } = await adminSupabase.from("bound_services").upsert(service); + return error; +}; diff --git a/backend/src/route/AuthRedirectionGET.ts b/backend/src/route/AuthRedirectionGET.ts new file mode 100644 index 00000000..fc1a3f62 --- /dev/null +++ b/backend/src/route/AuthRedirectionGET.ts @@ -0,0 +1,26 @@ +import { FastifyRequest, FastifyReply } from "fastify"; + +export default function AuthRedirectionGET( + req: FastifyRequest, + reply: FastifyReply +) { + reply.header("Content-Type", "text/html"); + reply.code(200).send(ReponseHTML); +} + +const ReponseHTML = ` + + +`; diff --git a/backend/src/route/RoomGET.ts b/backend/src/route/RoomGET.ts index 10eda346..d3d913ef 100644 --- a/backend/src/route/RoomGET.ts +++ b/backend/src/route/RoomGET.ts @@ -1,7 +1,7 @@ import { FastifyReply, FastifyRequest } from "fastify"; -import { supabase } from "../server"; +import createClient from "../lib/supabase"; -interface QueryParams { +export interface QueryParams { id: string; } @@ -10,6 +10,10 @@ export default function RoomGET(req: FastifyRequest, reply: FastifyReply) { if (!id) { reply.code(400).send({ error: "Missing id" }); } + const supabase = createClient({ + request: req, + response: reply, + }); supabase .from("rooms") .select("*") diff --git a/backend/src/server.ts b/backend/src/server.ts index 1c8984b8..d97f9356 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,14 +1,16 @@ +import type { FastifyCookieOptions } from "@fastify/cookie"; +import fastifyCors from "@fastify/cors"; +import { createClient } from "@supabase/supabase-js"; +import { config } from "dotenv"; import fastify from "fastify"; import fastifyIO from "fastify-socket.io"; -import cors from "@fastify/cors"; +import path from "path"; import { Server } from "socket.io"; -import HelloGet from "./route/HelloGET"; +import AuthCallbackGET from "./route/AuthCallbackGET"; +import AuthRedirectionGET from "./route/AuthRedirectionGET"; import RoomsGET from "./route/RoomGET"; -import RoomPOST from "./route/RoomPOST"; -import { createClient } from "@supabase/supabase-js"; -import { config } from "dotenv"; -import path from "path"; import StreamingServicesGET from "./route/StreamingServicesGET"; +import { Database } from "./types/dbTypes"; config({ path: path.resolve(__dirname, "../.env.local") }); @@ -21,24 +23,31 @@ const server = fastify({ }); if (!process.env.SUPABASE_URL || !process.env.SERVICE_ROLE) { - throw new Error("Missing SUPABASE_URL or SUPABASE_KEY environment variable"); + throw new Error( + "Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE environment variable" + ); } - -export const supabase = createClient( +export const adminSupabase = createClient( process.env.SUPABASE_URL, - process.env.SERVICE_ROLE, + process.env.SERVICE_ROLE ); server.register(fastifyIO); - -server.register(cors, { - origin: "*", // Allow all origins - methods: ["GET", "POST", "OPTIONS"], -}); +server.register(require("@fastify/cookie"), { + secret: process.env.FASTIFY_COOKIE_SECRET ?? "", // for cookies signature + hook: "onRequest", // set to false to disable cookie autoparsing or set autoparsing on any of the following hooks: 'onRequest', 'preParsing', 'preHandler', 'preValidation'. default: 'onRequest' + parseOptions: {}, // options for parsing cookies +} as FastifyCookieOptions); server.get("/rooms", RoomsGET); +server.register(fastifyCors, { + origin: [true], // or true to allow all origins + methods: ["*"], // or just ['*'] for all methods +}); -server.get("/hello", HelloGet); +// Auth +server.get("/auth/callback", AuthCallbackGET); +server.get("/auth/redirection", AuthRedirectionGET); server.get("/streaming-services", StreamingServicesGET); @@ -76,7 +85,7 @@ server.ready().then(() => { }); }); -server.listen({ port: 3000 }); +server.listen({ port: 3000, host: "0.0.0.0" }); declare module "fastify" { interface FastifyInstance { diff --git a/backend/src/types/dbTypes.ts b/backend/src/types/dbTypes.ts new file mode 100644 index 00000000..f5e78290 Binary files /dev/null and b/backend/src/types/dbTypes.ts differ diff --git a/commons/Database-types.ts b/commons/Database-types.ts new file mode 100644 index 00000000..f5e78290 Binary files /dev/null and b/commons/Database-types.ts differ diff --git a/expo/.env.exemple b/expo/.env.exemple new file mode 100644 index 00000000..46266789 --- /dev/null +++ b/expo/.env.exemple @@ -0,0 +1,3 @@ +EXPO_PUBLIC_SUPABASE_URL= +EXPO_PUBLIC_SUPABASE_ANON_KEY= + diff --git a/expo/.gitignore b/expo/.gitignore index dce32018..44589e58 100644 --- a/expo/.gitignore +++ b/expo/.gitignore @@ -5,4 +5,5 @@ web-build/ # The following patterns were generated by expo-cli expo-env.d.ts -# @end expo-cli \ No newline at end of file +# @end expo-cli +.env \ No newline at end of file diff --git a/expo/app.json b/expo/app.json index 6f78638d..ad95d641 100644 --- a/expo/app.json +++ b/expo/app.json @@ -5,7 +5,7 @@ "version": "1.0.0", "orientation": "portrait", "icon": "./assets/images/icon.png", - "scheme": "myapp", + "scheme": "datsmysong", "userInterfaceStyle": "automatic", "splash": { "image": "./assets/images/splash.png", diff --git a/expo/app/(auth)/_layout.tsx b/expo/app/(auth)/_layout.tsx new file mode 100644 index 00000000..1aded23a --- /dev/null +++ b/expo/app/(auth)/_layout.tsx @@ -0,0 +1,14 @@ +import { Stack } from "expo-router"; + +export default function TabLayout() { + return ( + + + + + + ); +} diff --git a/expo/app/(auth)/ask-name.tsx b/expo/app/(auth)/ask-name.tsx new file mode 100644 index 00000000..45add526 --- /dev/null +++ b/expo/app/(auth)/ask-name.tsx @@ -0,0 +1,91 @@ +import { router } from "expo-router"; +import { useState } from "react"; +import { Pressable, StyleSheet, Text, TextInput, View } from "react-native"; +import { Screen } from "react-native-screens"; +import Alert from "../../components/Alert"; +import { SupabaseErrorCode } from "../../constants/SupabaseErrorCode"; +import { supabase } from "../../lib/supabase"; +import useSupabaseUser from "../../lib/useSupabaseUser"; + +export default function AskName() { + const [username, setUsername] = useState(""); + + const handleSubmitUsername = async () => { + const user = await useSupabaseUser(); + if (!user?.id) return; + + if (username.length < 5) { + Alert.alert("Le pseudo doit faire au moins 5 caractères"); + return; + } + const { data, error } = await supabase + .from("user_profile") + .update({ username: username }) + .eq("account_id", user?.id); + if (error) { + if (error.code === SupabaseErrorCode.CONSTRAINT_VIOLATION) { + Alert.alert("Attention, Ce pseudo est déjà pris"); + return; + } + Alert.alert("Erreur, Une erreur est survenue"); + } + router.replace("/(tabs)"); + }; + + return ( + + + Choisi ton pseudo + + + + handleSubmitUsername()}> + Valider + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + // justifyContent: "center", + alignItems: "center", + gap: 20, + width: "100%", + }, + form: { + padding: 20, + alignItems: "center", + gap: 30, + }, + buttonContainer: { + alignItems: "center", + }, + title: { + fontSize: 30, + fontWeight: "bold", + textAlign: "center", + }, + titleContainer: { + marginTop: 100, + marginBottom: 100, + }, + containerWithDivider: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + }, +}); diff --git a/expo/app/(auth)/connect-with-spotify.tsx b/expo/app/(auth)/connect-with-spotify.tsx new file mode 100644 index 00000000..529ccfed --- /dev/null +++ b/expo/app/(auth)/connect-with-spotify.tsx @@ -0,0 +1,100 @@ +import "react-native-url-polyfill/auto"; + +import { Platform, Pressable, Text } from "react-native"; +import { supabase } from "../../lib/supabase"; + +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { makeRedirectUri } from "expo-auth-session"; +import * as WebBrowser from "expo-web-browser"; +import Alert from "../../components/Alert"; +import { getSpotifyScopes } from "../../constants/Api"; + +const directUri = makeRedirectUri(); +WebBrowser.maybeCompleteAuthSession(); // required for web only + +export default function ConnectWithSpotify() { + const handleSignUp = async () => { + const baseUrl = directUri.includes("exp://") + ? "http://" + directUri.split(":8081")[0].split("//")[1] + : directUri.split(":8081")[0]; + + // Get from auth manager the url to redirect the user to Spotify + // and a cookie to verify the user later + const { data, error } = await supabase.auth.signInWithOAuth({ + provider: "spotify", + options: { + skipBrowserRedirect: true, + redirectTo: encodeURI( + baseUrl + + ":3000/auth/callback?redirect_url=" + + directUri.split(":3000")[0] + ), + scopes: getSpotifyScopes(), + }, + }); + if (error || !data || !data.url) { + console.error("error", error ? error : "No data url"); + Alert.alert( + "Une erreur est survenue, impossible de contacter le serveur" + ); + } + + // Backend need verify the user, so we use it to add the cookie on the WebBrowser + // with the route /auth/redirection, who will redirect to the Spotify auth page with the code verifier + const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL; + if (!supabaseUrl) throw new Error("No supabaseUrl"); + const supabaseProjectId = supabaseUrl.split(".")[0].split("//")[1]; + + const codeVerifier: string | null = await getCookie( + "sb-" + supabaseProjectId + "-auth-token-code-verifier" + ); + if (!codeVerifier) throw new Error("No codeVerifier"); + const urlEncodedCodeVerifier = encodeURIComponent( + codeVerifier.replace(/"/g, "") + ); + + const urlBackendRedirection = + baseUrl + + ":3000/auth/redirection?redirect_url=" + + data.url + + "#code_verifier=" + + urlEncodedCodeVerifier; + + const webBrowser = await WebBrowser.openAuthSessionAsync( + urlBackendRedirection, + directUri + ); + // At end, if all is good, user come back to the app with a refresh_token to fetch new session + if (webBrowser.type === "success" && webBrowser.url) { + const refreshToken = decodeURIComponent( + webBrowser.url.split("#refresh_token=")[1] + ); + const { error } = await supabase.auth.refreshSession({ + refresh_token: refreshToken, + }); + if (!error) return; + console.error("error", error); + Alert.alert( + "Une erreur est survenue, l'authentification est impossible pour le moment" + ); + } + console.error("WebBrowser n'a pas retourné le bon type", webBrowser.type); + Alert.alert("Une erreur est survenue avec votre navigateur."); + }; + + return ( + + Rejoindre avec Spotify + + ); +} + +const getCookie = async (key: string): Promise => { + if (Platform.OS === "web") { + if (typeof localStorage === "undefined") { + return null; + } + return localStorage.getItem(key); + } + return await AsyncStorage.getItem(key); +}; diff --git a/expo/app/(auth)/index.tsx b/expo/app/(auth)/index.tsx new file mode 100644 index 00000000..ad7bede9 --- /dev/null +++ b/expo/app/(auth)/index.tsx @@ -0,0 +1,57 @@ +import { Link, router } from "expo-router"; +import { Button, Text, View, StyleSheet, Pressable } from "react-native"; +import { Screen } from "react-native-screens"; +import ConnectWithSpotify from "./connect-with-spotify"; +import { Divider } from "react-native-elements"; + +export default function Onboarding() { + return ( + + + Datsmysong + + + + router.push("/ask-name")}> + Rejoindre avec Google + + + + ou + + + + + Rejoindre avec une adresse email + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + // justifyContent: "center", + alignItems: "center", + gap: 20, + width: "100%", + }, + buttonContainer: { + alignItems: "center", + }, + title: { + fontSize: 30, + fontWeight: "bold", + }, + titleContainer: { + marginTop: 100, + marginBottom: 100, + }, + containerWithDivider: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + }, +}); diff --git a/expo/app/(auth)/login.tsx b/expo/app/(auth)/login.tsx new file mode 100644 index 00000000..25be7cd7 --- /dev/null +++ b/expo/app/(auth)/login.tsx @@ -0,0 +1,11 @@ + +import React from "react"; +import { Text, View } from "react-native"; + +export default function Login() { + return ( + + Connexion page + + ); +} diff --git a/expo/app/(tabs)/Profile.tsx b/expo/app/(tabs)/Profile.tsx index ea42532e..006dbdbe 100644 --- a/expo/app/(tabs)/Profile.tsx +++ b/expo/app/(tabs)/Profile.tsx @@ -1,5 +1,23 @@ -import { Text } from "react-native"; +import { Button, Text } from "react-native"; +import { supabase } from "../../lib/supabase"; +import useSupabaseUser from "../../lib/useSupabaseUser"; +import { useEffect, useState } from "react"; +import { User } from "@supabase/supabase-js"; export default function TabsProfile() { - return Profile; + const [user, setUser] = useState(); + useEffect(() => { + useSupabaseUser().then((res) => { + setUser(res); + }); + }, []); + + return ( + <> + Profile + {user && ( + + + + + + + + + + + + + + + + + + ); +} diff --git a/expo/assets/fonts/outfit/Outfit-Black.ttf b/expo/assets/fonts/outfit/Outfit-Black.ttf new file mode 100644 index 00000000..487752bb Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-Black.ttf differ diff --git a/expo/assets/fonts/outfit/Outfit-Bold.ttf b/expo/assets/fonts/outfit/Outfit-Bold.ttf new file mode 100644 index 00000000..0a081bc9 Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-Bold.ttf differ diff --git a/expo/assets/fonts/outfit/Outfit-ExtraBold.ttf b/expo/assets/fonts/outfit/Outfit-ExtraBold.ttf new file mode 100644 index 00000000..0977ed52 Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-ExtraBold.ttf differ diff --git a/expo/assets/fonts/outfit/Outfit-ExtraLight.ttf b/expo/assets/fonts/outfit/Outfit-ExtraLight.ttf new file mode 100644 index 00000000..938fe317 Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-ExtraLight.ttf differ diff --git a/expo/assets/fonts/outfit/Outfit-Light.ttf b/expo/assets/fonts/outfit/Outfit-Light.ttf new file mode 100644 index 00000000..c18b0c18 Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-Light.ttf differ diff --git a/expo/assets/fonts/outfit/Outfit-Medium.ttf b/expo/assets/fonts/outfit/Outfit-Medium.ttf new file mode 100644 index 00000000..7ae796b9 Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-Medium.ttf differ diff --git a/expo/assets/fonts/outfit/Outfit-Regular.ttf b/expo/assets/fonts/outfit/Outfit-Regular.ttf new file mode 100644 index 00000000..826899cf Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-Regular.ttf differ diff --git a/expo/assets/fonts/outfit/Outfit-SemiBold.ttf b/expo/assets/fonts/outfit/Outfit-SemiBold.ttf new file mode 100644 index 00000000..6b37eeb6 Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-SemiBold.ttf differ diff --git a/expo/assets/fonts/outfit/Outfit-Thin.ttf b/expo/assets/fonts/outfit/Outfit-Thin.ttf new file mode 100644 index 00000000..7d84201a Binary files /dev/null and b/expo/assets/fonts/outfit/Outfit-Thin.ttf differ diff --git a/expo/components/Alert.ts b/expo/components/Alert.ts new file mode 100644 index 00000000..68d4e0b5 --- /dev/null +++ b/expo/components/Alert.ts @@ -0,0 +1,8 @@ +import { Alert as ReactNativeAlert, Platform } from "react-native"; + +function alert(content: string) { + Platform.OS === "web" + ? window.alert(content) + : ReactNativeAlert.alert(content); +} +export default { alert }; diff --git a/expo/components/Button.tsx b/expo/components/Button.tsx new file mode 100644 index 00000000..901f4860 --- /dev/null +++ b/expo/components/Button.tsx @@ -0,0 +1,180 @@ +import { MaterialIcons } from "@expo/vector-icons"; +import { router } from "expo-router"; +import { + Pressable, + PressableStateCallbackType, + StyleSheet, +} from "react-native"; +import { Text } from "./Tamed"; + +type ButtonProps = { + children: React.ReactNode; + onPress?: () => void; + onLongPress?: () => void; + href?: string; + disabled?: boolean; + type?: "filled" | "outline"; + icon?: keyof typeof MaterialIcons.glyphMap; + prependIcon?: keyof typeof MaterialIcons.glyphMap; + appendIcon?: keyof typeof MaterialIcons.glyphMap; + size?: "small" | "normal"; +}; + +const ICON_SIZE_SMALL = 20; +const ICON_SIZE_NORMAL = 30; +const ICON_COLOR_FILLED = "white"; +const ICON_COLOR_OUTLINE = "#1A1A1A"; + +const Button: React.FC = ({ + children, + type = "filled", + size = "normal", + disabled = false, + appendIcon, + href, + icon, + onPress, + onLongPress, + prependIcon, +}) => { + const isSmall = size === "small"; + const isFilled = type === "filled"; + const iconSize = isSmall ? ICON_SIZE_SMALL : ICON_SIZE_NORMAL; + const iconColor = isFilled ? ICON_COLOR_FILLED : ICON_COLOR_OUTLINE; + + const buttonStyles = [ + styles.button, + icon && (isSmall ? styles.iconSmall : styles.iconNormal), + !icon && (isSmall ? styles.small : styles.normal), + prependIcon && + (isSmall + ? styles.smallPrependIconPadding + : styles.normalPrependIconPadding), + !prependIcon && + !icon && + (isSmall ? styles.smallPadding : styles.normalPadding), + appendIcon && + (isSmall + ? styles.smallAppendIconPadding + : styles.normalAppendIconPadding), + !appendIcon && + !icon && + (isSmall ? styles.smallPadding : styles.normalPadding), + isFilled ? styles.filled : styles.outline, + disabled && styles.disabled, + ]; + + const pressableStyle = (state: PressableStateCallbackType) => [ + buttonStyles, + state.hovered && styles.hovered, + state.pressed && styles.pressed, + ]; + + const handlePress = href ? () => router.push(href as any) : onPress; + + return ( + + {prependIcon && ( + + )} + {icon && } + {!icon && ( + + {children} + + )} + {appendIcon && ( + + )} + + ); +}; + +const styles = StyleSheet.create({ + button: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + alignSelf: "flex-start", + gap: 8, + borderCurve: "continuous", + }, + iconSmall: { + paddingHorizontal: 8, + paddingVertical: 8, + borderRadius: 8, + }, + iconNormal: { + paddingHorizontal: 16, + paddingVertical: 16, + borderRadius: 16, + }, + small: { + borderRadius: 8, + }, + normal: { + borderRadius: 16, + }, + smallPadding: { + paddingHorizontal: 14, + paddingVertical: 5, + }, + normalPadding: { + paddingHorizontal: 26, + paddingVertical: 15, + }, + smallPrependIconPadding: { + paddingLeft: 8, + paddingRight: 14, + paddingVertical: 5, + }, + normalPrependIconPadding: { + paddingLeft: 16, + paddingRight: 26, + paddingVertical: 15, + }, + smallAppendIconPadding: { + paddingLeft: 14, + paddingRight: 8, + paddingVertical: 5, + }, + normalAppendIconPadding: { + paddingLeft: 26, + paddingRight: 16, + paddingVertical: 15, + }, + filled: { + backgroundColor: "#1A1A1A", + }, + outline: { + borderWidth: 2, + borderColor: "#1A1A1A", + }, + disabled: { + opacity: 0.5, + }, + buttonText: { + textAlign: "center", + }, + hovered: { + opacity: 0.8, + }, + pressed: { + opacity: 0.5, + }, +}); + +export default Button; diff --git a/expo/constants/Api.ts b/expo/constants/Api.ts new file mode 100644 index 00000000..b31dc3e7 --- /dev/null +++ b/expo/constants/Api.ts @@ -0,0 +1,22 @@ +// https://developer.spotify.com/documentation/web-api/concepts/scopes +const scopes = [ + "user-read-playback-state", + "user-modify-playback-state", + "user-read-currently-playing", + "streaming", + "playlist-read-private", + "playlist-read-collaborative", + "playlist-modify-private", + "playlist-modify-public", + "user-read-playback-position", + "user-top-read", + "user-read-recently-played", + "user-library-modify", + "user-library-read", + "user-read-email", + "user-read-private", +]; + +export const getSpotifyScopes = () => { + return scopes.join(" "); +}; diff --git a/expo/constants/SupabaseErrorCode.ts b/expo/constants/SupabaseErrorCode.ts new file mode 100644 index 00000000..34dcd48b --- /dev/null +++ b/expo/constants/SupabaseErrorCode.ts @@ -0,0 +1,3 @@ +export const SupabaseErrorCode = { + CONSTRAINT_VIOLATION: "23505", +}; diff --git a/expo/lib/supabase.ts b/expo/lib/supabase.ts new file mode 100644 index 00000000..3bec6997 --- /dev/null +++ b/expo/lib/supabase.ts @@ -0,0 +1,54 @@ +import "react-native-url-polyfill/auto"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { createClient } from "@supabase/supabase-js"; +import { Platform } from "react-native"; +import { Database } from "../../commons/Database-types"; +// import * as SecureStore from 'expo-secure-store'; +// var aesjs = require('aes-js'); + +// https://github.com/supabase/supabase-js/issues/786 +// Bug with classical AsyncStorage, so I use this one +class SupabaseStorage { + async getItem(key: string) { + if (Platform.OS === "web") { + if (typeof localStorage === "undefined") { + return null; + } + return localStorage.getItem(key); + } + return AsyncStorage.getItem(key); + } + async removeItem(key: string) { + // console.log("remove item", key); + + if (Platform.OS === "web") { + return localStorage.removeItem(key); + } + return AsyncStorage.removeItem(key); + } + async setItem(key: string, value: string) { + // console.log("set item", key, value); + if (Platform.OS === "web") { + document.cookie = `${key}=${value}`; + return localStorage.setItem(key, value); + } + return AsyncStorage.setItem(key, value); + } +} + +const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL; +const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY; +if (!supabaseUrl || !supabaseAnonKey) { + throw new Error("Missing env variables for Supabase"); +} + +export const supabase = createClient(supabaseUrl, supabaseAnonKey, { + auth: { + // storage: new LargeSecureStore(), + storage: new SupabaseStorage(), + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: true, + flowType: "pkce", + }, +}); diff --git a/expo/lib/useSupabaseUser.ts b/expo/lib/useSupabaseUser.ts new file mode 100644 index 00000000..4e62c849 --- /dev/null +++ b/expo/lib/useSupabaseUser.ts @@ -0,0 +1,10 @@ +import { supabase } from "./supabase"; + +export default async function useSupabaseUser() { + const { data, error } = await supabase.auth.getSession(); + if (!data.session || error) { + return null; + } + const user = data.session.user; + return user; +} diff --git a/expo/lib/userProfile.ts b/expo/lib/userProfile.ts new file mode 100644 index 00000000..68dd4fe9 --- /dev/null +++ b/expo/lib/userProfile.ts @@ -0,0 +1,13 @@ +import { supabase } from "./supabase"; + +export const getUserProfile = async (id: string) => { + const { data, error } = await supabase + .from("user_profile") + .select("*") + .eq("account_id", id) + .single(); + if (error) { + throw error; + } + return data; +}; diff --git a/expo/package-lock.json b/expo/package-lock.json index f2c24ff4..b17cc7ab 100644 --- a/expo/package-lock.json +++ b/expo/package-lock.json @@ -9,17 +9,23 @@ "version": "1.0.0", "dependencies": { "@expo/vector-icons": "^13.0.0", + "@react-native-async-storage/async-storage": "1.18.2", "@react-native-community/checkbox": "^0.5.16", "@react-navigation/native": "^6.0.2", + "@supabase/ssr": "^0.0.10", + "@supabase/supabase-js": "^2.39.2", "@unimodules/core": "^7.1.2", + "aes-js": "^3.1.2", "expo": "~49.0.15", "expo-auth-session": "~5.0.2", "expo-blur": "~12.4.1", "expo-checkbox": "~2.4.0", + "expo-crypto": "~12.4.1", "expo-font": "~11.4.0", "expo-image": "~1.3.5", "expo-linking": "~5.0.2", "expo-router": "^2.0.0", + "expo-secure-store": "~12.3.1", "expo-splash-screen": "~0.20.5", "expo-status-bar": "~1.6.0", "expo-system-ui": "~2.4.0", @@ -27,11 +33,13 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.72.6", + "react-native-elements": "^3.4.3", "react-native-gesture-handler": "~2.12.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", "react-native-svg": "13.9.0", "react-native-unimodules": "^0.14.10", + "react-native-url-polyfill": "^2.0.0", "react-native-web": "~0.19.6" }, "devDependencies": { @@ -4372,6 +4380,17 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.18.2.tgz", + "integrity": "sha512-dM8AfdoeIxlh+zqgr0o5+vCTPQ0Ru1mrPzONZMsr7ufp5h+6WgNxQNza7t0r5qQ6b04AJqTlBNixTWZxqP649Q==", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || 0.60 - 0.72 || 1000.0.0" + } + }, "node_modules/@react-native-community/checkbox": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@react-native-community/checkbox/-/checkbox-0.5.17.tgz", @@ -6535,6 +6554,104 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@supabase/functions-js": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz", + "integrity": "sha512-BNzC5XhCzzCaggJ8s53DP+WeHHGT/NfTsx2wUSSGKR2/ikLFQTBCDzMvGz/PxYMqRko/LwncQtKXGOYp1PkPaw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/gotrue-js": { + "version": "2.62.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.62.0.tgz", + "integrity": "sha512-4eBuZNXGOk7ewqJuHPYMnk8clCtEx6Hfnu6yHLjZlx7w18TqcojcTRUBZagErtpgwwdfzUwKbquexhbrpH/ysw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@supabase/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@supabase/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.9.2.tgz", + "integrity": "sha512-I6yHo8CC9cxhOo6DouDMy9uOfW7hjdsnCxZiaJuIVZm1dBGTFiQPgfMa9zXCamEWzNyWRjZvupAUuX+tqcl5Sw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.9.3.tgz", + "integrity": "sha512-lAp50s2n3FhGJFq+wTSXLNIDPw5Y0Wxrgt44eM5nLSA3jZNUUP3Oq2Ccd1CbZdVntPCWLZvJaU//pAd2NE+QnQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/ssr": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.0.10.tgz", + "integrity": "sha512-eVs7+bNlff8Fd79x8K3Jbfpmf8P8QRA1Z6rUDN+fi4ReWvRBZyWOFfR6eqlsX6vTjvGgTiEqujFSkv2PYW5kbQ==", + "dependencies": { + "cookie": "^0.5.0", + "ramda": "^0.29.0" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.33.1" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.5.tgz", + "integrity": "sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.39.3", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.39.3.tgz", + "integrity": "sha512-NoltJSaJNKDJNutO5sJPAAi5RIWrn1z2XH+ig1+cHDojT6BTN7TvZPNa3Kq3gFQWfO5H1N9El/bCTZJ3iFW2kQ==", + "dependencies": { + "@supabase/functions-js": "^2.1.5", + "@supabase/gotrue-js": "^2.60.0", + "@supabase/node-fetch": "^2.6.14", + "@supabase/postgrest-js": "^1.9.0", + "@supabase/realtime-js": "^2.9.3", + "@supabase/storage-js": "^2.5.4" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -6644,11 +6761,15 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/phoenix": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.4.tgz", + "integrity": "sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==" + }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/qs": { "version": "6.9.10", @@ -6659,18 +6780,33 @@ "version": "18.2.45", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", - "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, + "node_modules/@types/react-native": { + "version": "0.70.19", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.19.tgz", + "integrity": "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-native-vector-icons": { + "version": "6.4.18", + "resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz", + "integrity": "sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==", + "dependencies": { + "@types/react": "*", + "@types/react-native": "^0.70" + } + }, "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/stack-utils": { "version": "2.0.3", @@ -6689,6 +6825,14 @@ "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -6925,6 +7069,11 @@ "node": ">=0.4.0" } }, + "node_modules/aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==" + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -8266,6 +8415,14 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/core-js-compat": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", @@ -8520,8 +8677,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/dag-map": { "version": "1.0.2", @@ -9439,6 +9595,14 @@ } } }, + "node_modules/expo-secure-store": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-12.3.1.tgz", + "integrity": "sha512-XLIgWDiIuiR0c+AA4NCWWibAMHCZUyRcy+lQBU49U6rvG+xmd3YrBJfQjfqAPyLroEqnLPGTWUX57GyRsfDOQw==", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-splash-screen": { "version": "0.20.5", "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.20.5.tgz", @@ -9870,19 +10034,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -13388,139 +13539,6 @@ "lightningcss-win32-x64-msvc": "1.19.0" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz", - "integrity": "sha512-wIJmFtYX0rXHsXHSr4+sC5clwblEMji7HHQ4Ub1/CznVRxtCFha6JIt5JZaNf8vQrfdZnBxLLC6R8pC818jXqg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.19.0.tgz", - "integrity": "sha512-Lif1wD6P4poaw9c/4Uh2z+gmrWhw/HtXFoeZ3bEsv6Ia4tt8rOJBdkfVaUJ6VXmpKHALve+iTyP2+50xY1wKPw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.19.0.tgz", - "integrity": "sha512-P15VXY5682mTXaiDtbnLYQflc8BYb774j2R84FgDLJTN6Qp0ZjWEFyN1SPqyfTj2B2TFjRHRUvQSSZ7qN4Weig==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.19.0.tgz", - "integrity": "sha512-zwXRjWqpev8wqO0sv0M1aM1PpjHz6RVIsBcxKszIG83Befuh4yNysjgHVplF9RTU7eozGe3Ts7r6we1+Qkqsww==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.19.0.tgz", - "integrity": "sha512-vSCKO7SDnZaFN9zEloKSZM5/kC5gbzUjoJQ43BvUpyTFUX7ACs/mDfl2Eq6fdz2+uWhUh7vf92c4EaaP4udEtA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.19.0.tgz", - "integrity": "sha512-0AFQKvVzXf9byrXUq9z0anMGLdZJS+XSDqidyijI5njIwj6MdbvX2UZK/c4FfNmeRa2N/8ngTffoIuOUit5eIQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.19.0.tgz", - "integrity": "sha512-SJoM8CLPt6ECCgSuWe+g0qo8dqQYVcPiW2s19dxkmSI5+Uu1GIRzyKA0b7QqmEXolA+oSJhQqCmJpzjY4CuZAg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.19.0.tgz", @@ -13569,6 +13587,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -13864,6 +13887,25 @@ "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", "integrity": "sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-options/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -15124,6 +15166,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/ora": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", @@ -15932,6 +15982,15 @@ } ] }, + "node_modules/ramda": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", + "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -16117,6 +16176,35 @@ "react": "18.2.0" } }, + "node_modules/react-native-elements": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz", + "integrity": "sha512-VtZc25EecPZyUBER85zFK9ZbY6kkUdcm1ZwJ9hdoGSCr1R/GFgxor4jngOcSYeMvQ+qimd5No44OVJW3rSJECA==", + "hasInstallScript": true, + "dependencies": { + "@types/react-native-vector-icons": "^6.4.6", + "color": "^3.1.2", + "deepmerge": "^4.2.2", + "hoist-non-react-statics": "^3.3.2", + "lodash.isequal": "^4.5.0", + "opencollective-postinstall": "^2.0.3", + "react-native-ratings": "8.0.4", + "react-native-size-matters": "^0.3.1" + }, + "peerDependencies": { + "react-native-safe-area-context": ">= 3.0.0", + "react-native-vector-icons": ">7.0.0" + } + }, + "node_modules/react-native-elements/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.1.tgz", @@ -16133,6 +16221,18 @@ "react-native": "*" } }, + "node_modules/react-native-ratings": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/react-native-ratings/-/react-native-ratings-8.0.4.tgz", + "integrity": "sha512-Xczu5lskIIRD6BEdz9A0jDRpEck/SFxRqiglkXi0u67yAtI1/pcJC76P4MukCbT8K4BPVl+42w83YqXBoBRl7A==", + "dependencies": { + "lodash": "^4.17.15" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-reanimated": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.1.tgz", @@ -16178,6 +16278,14 @@ "react-native": "*" } }, + "node_modules/react-native-size-matters": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/react-native-size-matters/-/react-native-size-matters-0.3.1.tgz", + "integrity": "sha512-mKOfBLIBFBcs9br1rlZDvxD5+mAl8Gfr5CounwJtxI6Z82rGrMO+Kgl9EIg3RMVf3G855a85YVqHJL2f5EDRlw==", + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/react-native-svg": { "version": "13.9.0", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.9.0.tgz", @@ -16732,6 +16840,71 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/react-native-url-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz", + "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==", + "dependencies": { + "whatwg-url-without-unicode": "8.0.0-3" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-vector-icons": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.0.3.tgz", + "integrity": "sha512-ZgVlV5AdQgnPHHvBEihGf2xwyziT1acpXV1U+WfCgCv3lcEeCRsmwAsBU+kUSNsU+8TcWVsX04kdI6qUaS8D7w==", + "peer": true, + "dependencies": { + "prop-types": "^15.7.2", + "yargs": "^16.1.1" + }, + "bin": { + "fa-upgrade.sh": "bin/fa-upgrade.sh", + "fa5-upgrade": "bin/fa5-upgrade.sh", + "fa6-upgrade": "bin/fa6-upgrade.sh", + "generate-icon": "bin/generate-icon.js" + } + }, + "node_modules/react-native-vector-icons/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "peer": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/react-native-web": { "version": "0.19.9", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.9.tgz", @@ -18480,6 +18653,27 @@ "node": ">=12" } }, + "node_modules/whatwg-url-without-unicode": { + "version": "8.0.0-3", + "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", + "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==", + "dependencies": { + "buffer": "^5.4.3", + "punycode": "^2.1.1", + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "engines": { + "node": ">=8" + } + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/expo/package.json b/expo/package.json index da67a128..f1f03489 100644 --- a/expo/package.json +++ b/expo/package.json @@ -19,10 +19,16 @@ "@unimodules/core": "^7.1.2", "expo": "~49.0.15", "expo-blur": "~12.4.1", + "@react-native-async-storage/async-storage": "1.18.2", + "@supabase/ssr": "^0.0.10", + "@supabase/supabase-js": "^2.39.2", + "aes-js": "^3.1.2", + "expo-crypto": "~12.4.1", "expo-font": "~11.4.0", "expo-image": "~1.3.5", "expo-linking": "~5.0.2", "expo-router": "^2.0.0", + "expo-secure-store": "~12.3.1", "expo-splash-screen": "~0.20.5", "expo-status-bar": "~1.6.0", "expo-system-ui": "~2.4.0", @@ -30,14 +36,16 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.72.6", + "react-native-elements": "^3.4.3", "react-native-gesture-handler": "~2.12.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", "react-native-svg": "13.9.0", "react-native-unimodules": "^0.14.10", - "react-native-web": "~0.19.6", "expo-checkbox": "~2.4.0", - "expo-auth-session": "~5.0.2" + "expo-auth-session": "~5.0.2", + "react-native-url-polyfill": "^2.0.0", + "react-native-web": "~0.19.6" }, "devDependencies": { "@babel/core": "^7.20.0",