Compare commits

..

No commits in common. 'master' and 'BackupApi' have entirely different histories.

@ -1,6 +1,7 @@
kind: pipeline
type: docker
name: FLAD-CLI
name: FLAD
trigger:
event:
@ -11,9 +12,10 @@ steps:
image: node:latest
commands:
- cd ./src/FLAD/
- npm install expo-cli
- npm install
- npm run
- name: code-analysis
image: node:latest
environment:
@ -28,84 +30,5 @@ steps:
- unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
- export PATH=$SONAR_SCANNER_HOME/bin:$PATH
- export SONAR_SCANNER_OPTS="-server"
- sonar-scanner -D sonar.projectKey=FLAD -D sonar.sources=./src/FLAD -D sonar.host.url=https://codefirst.iut.uca.fr/sonar
- sonar-scanner -D sonar.projectKey=FLAD -D sonar.sources=. -D sonar.host.url=https://codefirst.iut.uca.fr/sonar
depends_on: [ app-build ]
---
kind: pipeline
type: docker
name: FLAD-API-MQTT
trigger:
event:
- push
steps:
- name: docker-build-and-push
image: plugins/docker
settings:
dockerfile: src/Api/Dockerfile
context: src/Api
registry: hub.codefirst.iut.uca.fr
repo: hub.codefirst.iut.uca.fr/emre.kartal/flad
username:
from_secret: SECRET_REGISTRY_USERNAME
password:
from_secret: SECRET_REGISTRY_PASSWORD
depends_on: [ app-build ]
- name: deploy-container
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest
environment:
IMAGENAME: hub.codefirst.iut.uca.fr/emre.kartal/flad:latest
CONTAINERNAME: flad
CODEFIRST_CLIENTDRONE_ENV_PORT: 80
CODEFIRST_CLIENTDRONE_ENV_MONGO_PASSWORD:
from_secret: MONGO_PASSWORD
CODEFIRST_CLIENTDRONE_ENV_CLIENT_ID_SPOTIFY:
from_secret: CLIENT_ID_SPOTIFY
CODEFIRST_CLIENTDRONE_ENV_CLIENT_SECRET_SPOTIFY:
from_secret: CLIENT_SECRET_SPOTIFY
COMMAND: create
OVERWRITE: true
ADMINS: emrekartal,davidd_almeida,
depends_on: [ docker-build-and-push ]
- name: docker-build-and-push-mqtt
image: plugins/docker
settings:
dockerfile: src/Mqtt/Dockerfile
context: src/Mqtt
registry: hub.codefirst.iut.uca.fr
repo: hub.codefirst.iut.uca.fr/david.d_almeida/flad
username:
from_secret: SECRET_REGISTRY_USERNAME_MQTT
password:
from_secret: SECRET_REGISTRY_PASSWORD_MQTT
- name: deploy-container-mqtt
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest
environment:
IMAGENAME: hub.codefirst.iut.uca.fr/david.d_almeida/flad:latest
CONTAINERNAME: mqtt
COMMAND: create
OVERWRITE: true
ADMINS: emrekartal,davidd_almeida
depends_on: [ docker-build-and-push-mqtt ]
- name: code-analysis
image: node:latest
environment:
SONAR_TOKEN:
from_secret: SONAR_TOKEN_API
settings:
sources: ./src/Api/
commands:
- export SONAR_SCANNER_VERSION=4.7.0.2747
- export SONAR_SCANNER_HOME=$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION-linux
- curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip
- unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
- export PATH=$SONAR_SCANNER_HOME/bin:$PATH
- export SONAR_SCANNER_OPTS="-server"
- sonar-scanner -D sonar.projectKey=FLAD-API -D sonar.sources=./src/Api -D sonar.host.url=https://codefirst.iut.uca.fr/sonar

@ -4,20 +4,6 @@
</div>
<div align = center>
---
&nbsp; ![Docker](https://img.shields.io/badge/Docker-2496ED.svg?style=for-the-badge&logo=Docker&logoColor=white)
&nbsp; ![React Native](https://img.shields.io/badge/React_Native-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)
&nbsp; ![Spotify Api](https://img.shields.io/badge/Spotify-1ED760?&style=for-the-badge&logo=spotify&logoColor=white)
&nbsp; ![TypeScript](https://img.shields.io/badge/TypeScript-000?style=for-the-badge&logo=typescript&logoColor=white&color=blue)
&nbsp; ![JavaScript](https://img.shields.io/badge/JavaScript-000?style=for-the-badge&logo=javascript&logoColor=white&color=yellow)
---
</div>
**Nom de lapplication** : FLAD :musical_note:
</br>
@ -29,13 +15,13 @@
:information_source: Ce projet est un travail universitaire pour la deuxième année du B.U.T Informatique de Clermont-Ferrand.
## Répartition du Git
## Répartition du Gitlab
La racine de notre gitlab est composée de deux dossiers essentiels au projet:
[**src**](src) : **Ensemble du code pour l'application mobile et les services web** (Application React Native, API Express en TypeScript, et messagerie MQTT)
[**src**](src) : **Toute la partie codage de l'application mobile** (contient un dossier API pour l'API FLAD qui effectue les requêtes vers l'API SPOTIFY et la base de données, ainsi qu'un dossier FLAD qui contient toute la partie côté client de l'application)
[**doc**](doc) : **Documentation de l'application** (Inclut des diagrammes, des maquettes et des images)
[**doc**](doc) : **Documentation de l'application**
## Fonctionnement
@ -61,11 +47,11 @@ Pour la suite, il suffit seulement de vérifier que node.js est à jour et insta
Maintenant vous pouvez à tout moment lancer l'application grâce à la commande : **npx expo start :sunglasses:**
<br>
:information_source: *N'oubliez pas d'installer 'Expo Go' depuis le store de votre téléphone.*
:information_source: *Cliquer sur la touche 'w' si vous voulez le visualiser sur un navigateur (ce que je ne conseille pas) ou installer l'application 'Expo go' de votre téléphone et scanner le QR code proposer pour le visualiser (à noter que l'ordinateur dans lequel il se voit lancer doit être dans le même réseau local que votre téléphone)*
- ### Comment le lancer à partir de l'IUT d'Aubière ?
- ### Comment le lancer à partir de l'iut d'Aubière ?
Cela est un peu plus difficile mais faisable !
Cela est un peu plus difficile mais faisable !!!
<br>
Tout d'abord aller dans votre compte scratch : **cd home/scratch/compte**
@ -88,54 +74,39 @@ Et entrer la commande : **export NODE_OPTIONS=--openssl-legacy-provider**
Maintenant vous pouvez à tout moment lancer l'application grâce à la commande : **npx expo start :sunglasses:**
<br>
:information_source: *Cliquer sur la touche 'w' si vous voulez le visualiser sur un navigateur (ce que je ne conseille pas) ou installer l'application 'Expo go' de votre téléphone et scanner le QR code proposer pour le visualiser (à noter que l'ordinateur dans lequel il se voit lancer doit être dans le même réseau local que votre téléphone)*
- ### Comment s'inscrire sur l'application ?
Tout d'abord, il faut fournir votre *adresse e-mail* et votre *nom Spotify* aux **techniciens de l'application** (voir plus bas). Ils s'occuperont de vous ajouter définitivement à l'application. Une fois que cela est fait, inscrivez-vous via la **page d'inscription** de l'application :
Tout d'abord, il faut fournir votre *adresse e-mail* et votre *nom Spotify* aux **techniciens de l'application** (voir plus bas). Ils s'occuperont de vous ajouter définitivement à l'application. Une fois que cela est fait, inscrivez-vous via la **page d'inscription** de l'application en cliquant d'abord sur le bouton 'lier mon compte':
<div align = center>
<img src="doc/Maquettes/RegisterPage.png" width="200" >
<img src="doc/Images/Real_RegisterPage.png" width="250" >
</div>
Une fois sur la page, saisissez votre nom, votre adresse e-mail, et votre mot de passe en tant qu'utilisateur FLAD (n'oubliez pas ces informations, vous en aurez besoin pour vous connecter). Pour lier votre compte à Spotify, vous serez automatiquement redirigé vers la page de connexion Spotify. Entrez vos identifiants Spotify, puis cliquez sur le bouton ```Suivant``` et bienvenue sur l'application !"
Vous serez normalement redirigé sur la page Spotify où vous devrez vous connecter. Une fois connecté, entrez votre nom, votre adresse e-mail et votre mot de passe en tant qu'utilisateur FLAD (n'oubliez pas ces informations car vous en aurez besoin pour vous connecter). Ensuite, cliquez sur le bouton ```suivant``` et bienvenue sur l'application !
## Visuel de l'Application
## Environnement de Travail
<div align = center>
<img src="doc/Images/Overview.png">
Notre environnement de travail se base sur plusieurs outils et langages :👇
</div>
<div align = center>
:information_source: Lorsque vous entrez dans notre application, la page d'accueil (**home**) vous permet de découvrir les musiques :notes: des utilisateurs autour de vous. Vous pouvez valider une musique soit en cliquant sur le bouton, soit en la glissant vers la droite :point_up_2:. Cette musique sera alors ajoutée à la page **favoris** :heart: et vous pourrez entamer une discussion avec l'utilisateur dans la page **chat** :speech_balloon:.
<br/>
---
Pour accéder aux détails d'une musique, maintenez votre doigt appuyé sur un Spot ou rendez-vous sur la page des favoris. Vous pourrez écouter la musique :arrow_forward:, obtenir des informations sur l'artiste et la chanson, découvrir des musiques similaires, et même l'ajouter à votre playlist Spotify ou la partager.
&nbsp; ![Redux](https://img.shields.io/badge/Redux-593D88?style=for-the-badge&logo=redux&logoColor=white)
&nbsp; ![Docker](https://img.shields.io/badge/Docker-2496ED.svg?style=for-the-badge&logo=Docker&logoColor=white)
&nbsp; ![React Native](https://img.shields.io/badge/React_Native-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)
&nbsp; ![Spotify Api](https://img.shields.io/badge/Spotify-1ED760?&style=for-the-badge&logo=spotify&logoColor=white)
&nbsp; ![TypeScript](https://img.shields.io/badge/TypeScript-000?style=for-the-badge&logo=typescript&logoColor=white&color=blue)
&nbsp; ![JavaScript](https://img.shields.io/badge/JavaScript-000?style=for-the-badge&logo=javascript&logoColor=white&color=yellow)
<br/>
Dans la page **settings** ⚙️, vous avez accès à toutes vos informations ```Spotify```, que vous pouvez modifier à votre guise. Toutefois, ces modifications ne seront prises en compte que dans notre application. Vous pouvez également choisir le mode sombre (dark mode) dans les paramètres pour une expérience de navigation plus confortable.
<br/>
---
### Voici un petit récapitulatif
<div align="center">
<table>
<tr>
<td align="center"><img src="doc/Images/DisLike_Img.png" alt="Button 1" width="100" height="100"></td>
<td align="center"><img src="doc/Images/Discovery_Img.png" alt="Button 2" width="100" height="100"></td>
<td align="center"><img src="doc/Images/Like_Img.png" alt="Button 3" width="100" height="100"></td>
</tr>
<tr>
<td align="center">Supprimer de la pile un spot</td>
<td align="center">Ajout dans une playlist de votre compte Spotify (créée spécialement par l'application)</td>
<td align="center">Ajouter à vos favoris</td>
</tr>
</table>
</div>
<br/>
## Deploiement
- [x] &nbsp; ![IOS](https://img.shields.io/badge/IOS-000?style=for-the-badge&logo=apple&logoColor=black&color=white)
@ -151,19 +122,17 @@ La composition pour le projet se voit réaliser par deux élèves de l'IUT d'Aub
<div align="center">
<a href = "https://codefirst.iut.uca.fr/git/emre.kartal">
<img src="https://codefirst.iut.uca.fr/git/avatars/1ff65c9c5ab0e8c8883fb48adbcf972f?size=72" width="50" >
<img src="https://codefirst.iut.uca.fr/git/avatars/402cf312e853192f42c0135a888725c2?size=870" width="50" >
</a>
<a href = "https://codefirst.iut.uca.fr/git/david.d_almeida">
<img src="https://codefirst.iut.uca.fr/git/avatars/a16fa2dc52ceae18d8923c91121caa66?size=870" width="50" >
<img src="https://codefirst.iut.uca.fr/git/avatars/0f8eaaad1e26d3de644ca522eccaea7c?size=870" width="50" >
</a>
</div>
<div align = center>
© FladDev
© PM2 (Projet inspiré par nos très chers développeurs de la Dafl Team (S.O les Dafl dev))
</div>
<div align = right>
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png" /></a>
<hr>
</div>
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png" /></a><br />Ce(tte) œuvre est mise à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/">Licence Creative Commons Attribution - Pas d&#39;Utilisation Commerciale - Pas de Modification 4.0 International</a>.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

@ -0,0 +1,15 @@
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.
> What do the files contain?
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
- "packager-info.json": contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
- "settings.json": contains the server configuration that is used to serve the application manifest.
> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.

@ -0,0 +1,8 @@
{
"hostType": "lan",
"lanType": "ip",
"dev": true,
"minify": false,
"urlRandomness": null,
"https": false
}

144
src/Api/.gitignore vendored

@ -1,144 +0,0 @@
# Xcode
!**/*.xcodeproj
!**/*.pbxproj
!**/*.xcworkspacedata
!**/*.xcsettings
!**/*.xcscheme
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
**/.xcode.env.local
**/src/config.ts
# Gradle
/build/
/packages/react-native-gradle-plugin/build/
/packages/rn-tester/build
/packages/rn-tester/android/app/.cxx/
/packages/rn-tester/android/app/build/
/packages/rn-tester/android/app/gradle/
/packages/rn-tester/android/app/gradlew
/packages/rn-tester/android/app/gradlew.bat
/ReactAndroid/build/
/ReactAndroid/.cxx/
/ReactAndroid/gradle/
/ReactAndroid/gradlew
/ReactAndroid/gradlew.bat
/ReactAndroid/external-artifacts/build/
/ReactAndroid/external-artifacts/artifacts/
/ReactAndroid/hermes-engine/build/
/ReactAndroid/hermes-engine/.cxx/
/template/android/app/build/
/template/android/build/
# Buck
.buckd
buck-out
/.lsp.buckd
/.lsp-buck-out
/ReactAndroid/src/main/jni/prebuilt/lib/
/ReactAndroid/src/main/gen
# Android Studio
.project
.settings
.classpath
# Watchman
.watchmanconfig
# Android
.idea
.gradle
local.properties
*.iml
/android/*
!/android/README.md
# Node
node_modules
*.log
.nvm
package-lock.json
dist
**/.env
# OS X
.DS_Store
# Test generated files
/ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
*.js.meta
/coverage
/third-party
# Test Reports
/reports
# Stack Dumps generated when programs crash (Ex. bash.exe.stackdump on Win)
*.stackdump
# Root dir shouldn't have Xcode project
/*.xcodeproj
# ReactCommon subdir shouldn't have Xcode project
/ReactCommon/**/*.xcodeproj
# Libs that shouldn't have Xcode project
/Libraries/FBLazyVector/**/*.xcodeproj
/Libraries/RCTRequired/**/*.xcodeproj
/React/CoreModules/**/*.xcodeproj
/React/FBReactNativeSpec/**/*.xcodeproj
/packages/react-native-codegen/**/*.xcodeproj
# Ruby Gems (Bundler)
/vendor
/template/vendor
# iOS / CocoaPods
/template/ios/build/
/template/ios/Pods/
/template/ios/Podfile.lock
/packages/rn-tester/Gemfile.lock
# Ignore RNTester specific Pods, but keep the __offline_mirrors__ here.
/packages/rn-tester/Pods/*
!/packages/rn-tester/Pods/__offline_mirrors_hermes__
!/packages/rn-tester/Pods/__offline_mirrors_jsc__
# @react-native/codegen
/React/FBReactNativeSpec/FBReactNativeSpec
/packages/react-native-codegen/lib
/packages/react-native-codegen/tmp/
/ReactCommon/react/renderer/components/rncore/
/packages/rn-tester/NativeModuleExample/ScreenshotManagerSpec*
# Additional SDKs
/sdks/download
/sdks/hermes
/sdks/hermesc
# Visual studio
.vscode
.vs
# Android memory profiler files
*.hprof
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*

@ -1,8 +0,0 @@
FROM node:latest
WORKDIR /Api
COPY package.json /Api
COPY tsconfig.json /Api
COPY . /Api
RUN npm install && npm run build
EXPOSE 80
CMD ["node", "."]

@ -1,5 +0,0 @@
{
"watch": ["src"],
"ext": ".ts.js",
"exec": "ts-node ./src/index.ts"
}

@ -1,11 +1,10 @@
{
"name": "flad_api",
"name": "api",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"main": "src/server.ts",
"scripts": {
"build": "tsc",
"dev": "nodemon",
"start": "nodemon src/server.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
@ -13,30 +12,30 @@
"license": "ISC",
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/body-parser": "^1.19.2",
"@types/cors": "^2.8.13",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.16",
"@types/jsonwebtoken": "^9.0.1",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6",
"@types/morgan": "^1.9.4",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
},
"dependencies": {
"@types/cookie-parser": "^1.4.3",
"@types/crypto-js": "^4.1.1",
"@types/mongoose": "^5.11.97",
"@types/request": "^2.48.8",
"axios": "^1.2.6",
"bcrypt": "^5.1.0",
"build": "^0.1.4",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-winston": "^4.2.0",
"joi": "^17.8.1",
"jsonwebtoken": "^9.0.0",
"mongodb": "^5.0.0",
"mongoose": "^6.9.0",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0"
"morgan": "^1.10.0",
"request": "^2.88.2",
"swagger-ui-express": "^4.6.0",
"winston": "^3.8.2"
}
}

@ -1,60 +1,79 @@
import express, { Application } from 'express';
// import compression from 'compression';
import cors from 'cors';
import cookieParser from 'cookie-parser';
// import db from './database';
// import morgan from 'morgan';
import Controller from './controller/Icontroller';
// import ErrorMiddleware from './middleware/error.middleware';
import bodyParser from 'body-parser';
import IController from './controllers/interfaces/IController';
import mongoose from 'mongoose';
import swaggerUi from "swagger-ui-express";
import { specs } from './utils/swagger';
// to secure
// import helmet from 'helmet';
import http from 'http';
class App {
public express: Application;
public port: number;
public dataBase: null;
public server : any;
constructor(controllers: IController[], port: number) {
constructor(controllers: Controller[], port: number) {
this.express = express();
this.port = port;
this.initDatabase();
this.initMiddleware();
this.initControllers(controllers);
this.initSwagger();
this.dataBase = null;
this.initialiseDatabase();
this.initialiseMiddleware();
this.initialiseControllers(controllers);
// this.initialiseErrorHandling();
}
private initMiddleware(): void {
private initialiseMiddleware(): void {
// this.express.use(helmet());
this.express.use(cors());
this.express.use(cookieParser());
// this.express.use(morgan('dev'));
this.express.use(express.json());
this.express.use(express.urlencoded({ extended: false }));
// this.express.use(compression());
// mine
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({
extended: true
}));
}));
}
private initControllers(controllers: IController[]): void {
controllers.forEach((controller: IController) => {
private initialiseControllers(controllers: Controller[]): void {
controllers.forEach((controller: Controller) => {
this.express.use('/api', controller.router);
this.express.get('/toto', (req, res) => {
res.send('Hello World!');
})
});
}
// private initialiseErrorHandling(): void {
// this.express.use(ErrorMiddleware);
// }
public listen(): void {
this.express.listen(this.port, () => {
const server = this.express.listen(this.port, () => {
console.log(`⚡️[server] : App listening on the port ${this.port}`);
});
}
private initDatabase(): void {
const MONGO_URL = `mongodb+srv://FladDev:${process.env.MONGO_PASSWORD}@flad.mliekr2.mongodb.net/?retryWrites=true&w=majority`;
mongoose.connect(MONGO_URL)
.then(() => console.log("Connect to MongoDB database successfully"))
.catch(error => console.log("Error connecting : " + error));
}
public initSwagger(): void {
this.express.use("/swagger", swaggerUi.serve, swaggerUi.setup(specs),);
console.log(`Docs available at /${this.port}/swagger`);
private initialiseDatabase(): void {
const { MONGO_USER, MONGO_PASSWORD, MONGO_PATH } = process.env;
const uri = "mongodb+srv://fladDevDb:ZslYlNRWIOUU7i6o@fladcluster.b29tytu.mongodb.net/?retryWrites=true&w=majority"
mongoose.connect(uri)
.then(() => console.log("Connect to MongoDB database successfully"))
.catch(err => console.log("Error connecting : "+ err ));
}
}
export default App;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

@ -0,0 +1,15 @@
import { Router } from 'express';
interface Controller {
path: string;
router: Router;
// constructor() {
// this.initialiseRoutes();
// }
// initialiseRoutes(): void ;
}
// il y a un truc inject
export default Controller;

@ -0,0 +1,27 @@
import { Router } from "express";
import Controller from "./Icontroller";
type PingResponse = {
message: string;
}
export default class PingController implements Controller {
public path = '/ping';
public router = Router();
constructor() {
this.initialiseRoutes();
}
private initialiseRoutes(): void {
this.router.get("/ping", async (_req, res) => {
const response = await this.getMessage();
return res.send(response);
});
}
async getMessage(): Promise<PingResponse> {
return {
message: "pong",
};
}
}

@ -0,0 +1,19 @@
export default class CryptString{
stringCrypt: string;
constructor(length : number){
this.stringCrypt = this.generateRandomString(length);
}
generateRandomString (length : number){
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
}

@ -0,0 +1,6 @@
export type AuthReqBody = {
grant_type: string,
redirect_uri?: string,
code?: string,
refresh_token?: string,
}

@ -0,0 +1,219 @@
import Controller from '../Icontroller';
import { Router, Request, Response, NextFunction, RequestHandler } from 'express';
import { AuthReqBody } from './request/authReqBody';
import HttpException from '../../middleware/exeption/httpExeption';
import axios from 'axios';
import CryptString from './crypt';
import AES from 'crypto-js'
import querystring from 'querystring';
import qs from 'qs';
class SpotifyController implements Controller {
public path = '/spotify';
public router = Router();
constructor() {
console.log("useeeee");
this.initialiseRoutes();
}
initialiseRoutes() {
// this.router.post(`${this.path}`,this.createTask);
this.router.get(`${this.path}/exchange`,this.login);
this.router.get(`${this.path}/callback`,this.getAccessToken);
// this.router.post(`${this.path}/refresh`,this.getRefreshToken);
// this.router.get(`${this.path}/play/:musicId`, this.getMusic);
this.router.get(`${this.path}/spot`, this.getSpot);
}
// need to put in ENvironement file
// private readonly CLIENT_CALLBACK_URL = "http://localhost:8080/callback";
private readonly API_URL = "https://accounts.spotify.com/api/token";
private readonly CLIENT_ID = "1f1e34e4b6ba48b388469dba80202b10";
private readonly CLIENT_SECRET = "779371c6d4994a68b8dd6e84b0873c82";
private readonly CLIENT_CALLBACK_URL = "https://auth.expo.io/@thed47/FLAD//callback";
private readonly CALLBACK_URL = "http://localhost:8080/api/spotify/callback";
private readonly SCOPES ='user-read-private user-read-email user-read-playback-state user-read-currently-playing user-read-recently-played playlist-modify-public ugc-image-upload user-modify-playback-state';
private readonly ENCRYPTION_SECRET = new CryptString(16);
private login = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
console.log("useeeee");
try {
// const params = req.body;
// if (!params.refresh_token) {
// return res.json({
// "error": "Parameter missing"
// });
// }
// this.spotifyRequest({
// grant_type: "authorization_code",
// redirect_uri: this.CLIENT_CALLBACK_URL,
// // code: params.code
// })
res.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: this.CLIENT_ID,
scope: this.SCOPES,
redirect_uri: this.CALLBACK_URL,
state: this.ENCRYPTION_SECRET.stringCrypt
}));
// .then(session => {
// let result = {
// "access_token": session.access_token,
// "expires_in": session.expires_in,
// "refresh_token": this.encrypt(session.refresh_token)
// };
// return res.send(result);
// })
// .catch(response => {
// return res.json(response);
// });
} catch (error) {
next(new HttpException(400, 'Cannot create spot'));
}
};
private getRefreshToken = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
console.log('UUse2');
try {
const params = req.query.refresh_token;
if (!req.query.refresh_token) {
return res.json({
"error": "Parameter refresh_token missing"
});
}
var authOptions = {
method: 'POST',
url: 'https://accounts.spotify.com/api/token',
data: qs.stringify({
grant_type: 'refresh_token',
refresh_token: params
}),
headers: {
'Authorization': 'Basic ' + ( Buffer.from(this.CLIENT_ID + ':' + this.CLIENT_SECRET).toString('base64')),
'Content-Type' : 'application/x-www-form-urlencoded'
},
json: true
};
// request.post(authOptions, function(error, response, body) {
// if (!error && response.statusCode === 200) {
// var access_token = body.access_token;
// res.send({
// 'access_token': access_token
// });
// }
// });
axios(authOptions)
.then(session => {
if(session.status === 200){
res.send({
"access_token": session.data.access_token,
"expires_in": session.data.expires_in
});
}});
console.log("goood");
} catch (error) {
console.log("errur");
next(new HttpException(400, 'Cannot create post'));
}
}
public getSpot = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
const spots = [
{
name: "blue",
sourceUrl: "https://cdns-images.dzcdn.net/images/artist/399e7e760d8fedf3cc2891e9c0c41658/200x200-000000-80-0-0.jpg",
index: 3
},
{
name: "strange history",
sourceUrl: "https://images.genius.com/339dfe2a7c0adf9a5d08febf29a845f4.1000x1000x1.jpg",
index: 7
},
{
name: "oboy album",
sourceUrl: "https://i.pinimg.com/originals/ad/cc/d5/adccd58a0d0ff516a6114703cd05810e.jpg",
index: 1
}
];
try {
res.send(spots);
} catch (error) {
console.log('heuuuuuuuuuuuuuuuuuuuuubizzzaaarrreeee');
console.log(error);
next(new HttpException(400, 'On peut pas avoir darray mec'));
} }
private getAccessToken = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
var code = req.query.code;
var state = req.query.state || null;
// var storedState = req.cookies ? req.cookies[stateKey] : null;
var authOptions = {
method: 'POST',
url: 'https://accounts.spotify.com/api/token',
data: qs.stringify({
code: code,
redirect_uri: this.CALLBACK_URL,
grant_type: 'authorization_code'
}),
headers: {
'Authorization': 'Basic ' + ( Buffer.from(this.CLIENT_ID + ':' + this.CLIENT_SECRET).toString('base64')),
'Content-Type' : 'application/x-www-form-urlencoded'
},
json: true
};
try {
console.log('presssquuueee');
var resp = await axios(authOptions);
if (resp.status === 200) {
console.log(resp);
console.log('oon esttt laaa');
var access_token = resp.data.access_token;
console.log(access_token);
// should redirect res.redirect('/')
res.json("ok");
}
} catch (error) {
console.log('heuuuuuuuuuuuuuuuuuuuuubizzzaaarrreeee');
console.log(error);
next(new HttpException(400, 'On peut pas te connecter mec'));
}
};
}
export default SpotifyController;

@ -0,0 +1,248 @@
import { Router, Request, Response, NextFunction, RequestHandler } from 'express';
import Controller from '../Icontroller';
import HttpException from '../../middleware/exeption/httpExeption';
// import LocationService from '../../service/LocationService';
import IUser from '../../database/schema/User/UserInterface';
import UserService from '../../service/UserService';
import validator from '../../database/schema/User/UserValidation'
import validationMiddleware from '../../middleware/validation/ValidatorMiddleware';
import authenticator from '../../middleware/authMiddleware'
import LocationService from '../../service/LocationService';
class UserController implements Controller {
public path = '/users';
public router = Router();
private userService = new UserService();
private locationService = new LocationService();
constructor() {
this.initialiseRoutes();
}
private initialiseRoutes(): void {
this.router.post(
`${this.path}/register`,
validationMiddleware(validator.register),
this.register
);
this.router.post(
`${this.path}/login`,
validationMiddleware(validator.login),
this.login
);
this.router.get(`${this.path}`, authenticator, this.getUser);
this.router.get(`${this.path}/nextTo`, authenticator, this.getUserNext);
// //create
// this.router.post(`${this.path}`,this.createUser);
// // // get One
// this.router.get (`${this.path}/:userId`, this.getUserById);
// // // get All
// this.router.get (`${this.path}`, this.getAllUsers);
// //update One
// this.router.put (`${this.path}/:userId`, this.updateUser);
// //Delete One
// this.router.delete (`${this.path}/:userId`, this.deleteUser);
}
// private createUser = async (
// req: Request,
// res: Response,
// next: NextFunction
// ): Promise<Response | void> => {
// try {
// console.log(req.body);
// const reqBody:CreateTaskReqBody = Object.assign({}, req.body);
// checkIfIsValidCreateTaskReqBody(reqBody);
// await this.userService.createUserById(reqBody.fin
// );
// res.status(200).send({ status: "Success", msg: "Success add" });
// } catch (error) {
// next(new HttpException(400, 'Cannot create post'));
// }
// };
// private readonly getUserById: RequestHandler = async (
// req: Request,
// res: Response,
// next: NextFunction
// ): Promise<Response | void> => {
// try {
// const id = req.params.taskId;
// const userId = req.params.userId;
// const data = await this.userService.getUserById(id, userId);
// res.status(201).send(data);
// }
// catch(error){
// next(new HttpException(400, 'Cannot create post'));
// }
// }
// private readonly getAllUsers: RequestHandler = async (
// req: Request,
// res: Response,
// next: NextFunction
// ): Promise<Response | void> => {
// try {
// const userId = req.params.userId;
// const tasks = await this.userService.getUsers(userId);
// const responseList = tasks.map(task => new TaskResumedRes(task));
// res.status(201).send(responseList);
// }
// catch(error){
// next(new HttpException(400, 'Cannot get user task'));
// }
// }
// private deleteUser = async (
// req: Request,
// res: Response,
// next: NextFunction
// ): Promise<Response | void> => {
// try {
// const id = req.params.taskId;
// const userId = req.params.userId;
// await this.userService.DeleteUser(id, userId);
// return res.status(200).send({ status: "Success", msg: "Data Removed" });
// } catch (error) {
// next(new HttpException(400, 'Cannot create post'));
// }
// };
// private updateUser = async (
// req: Request,
// res: Response,
// next: NextFunction
// ): Promise<Response | void> => {
// try {
// const taskId = req.params.taskId;
// const userId = req.params.userId;
// const reqBody:CreateTaskReqBody = Object.assign({}, req.body);
// const updatedTask = await this.userService.UpdateTask(
// // req.auth!.uid,
// taskId,
// userId,
// // firebase.auth().currentUser.getIdToken()
// reqBody.nom,
// reqBody.description,
// reqBody.logo,
// reqBody.duration,
// reqBody.done,
// // reqBody.tags,
// reqBody.repepat,
// reqBody.deb,
// reqBody.fin
// );
// // res.send('Success add');
// // res.status(201).json({ task });
// res.status(204).send(`Update a new contact: ${updatedTask}`);
// } catch (error) {
// console.log(error);
// next(new HttpException(403, 'Cannot create post'));
// }
// };
private register = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
try {
// the FladId should be created by the Userservice
const { name, email, password , idFlad, idSpotify } = req.body;
console.log(name, email, password, idFlad, idSpotify);
const token = await this.userService.register(
name,
email,
password,
idFlad,
idSpotify
);
res.status(201).json({ token });
} catch (error : any) {
next(new HttpException(400, error.message));
}
};
private login = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
try {
const { email, password } = req.body;
const token = await this.userService.login(email, password);
res.status(200).json({ token });
} catch (error : any) {
next(new HttpException(400, error.message));
}
};
private getUser = (
req: Request,
res: Response,
next: NextFunction
): Response | void => {
if (!req.user) {
return next(new HttpException(404, 'No logged in user'));
}
res.status(200).send({ data: req.user });
};
private getUserNext = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
try {
const longitude = Number(req.body.longitude);
const latitude = Number(req.body.latitude);
//verify::val_int(){
if (isNaN(longitude) || isNaN(latitude)) {
console.log('Impossible de convertir la chaîne en nombre');
}
//}
const currentMusicId = req.body.currentMusicId;
const userId = req.user.idFlad;
const data = await this.locationService.getNearUser(userId,latitude,longitude, currentMusicId);
console.log(data);
res.status(201).send(data);
}
catch(error : any){
next(new HttpException(400, 'Cannot create get netUser'));
}
}
}
export default UserController;
declare global {
namespace Express {
export interface Request {
user: IUser;
}
}
}

@ -1,8 +0,0 @@
import { Router } from 'express';
interface IController {
path: string;
router: Router;
}
export default IController;

@ -1,196 +0,0 @@
import IController from './interfaces/IController';
import { Router, Request, Response } from 'express';
import axios from 'axios';
import qs from 'qs';
class SpotifyController implements IController {
public path = '/spotify';
public router = Router();
public readonly CLIENT_ID_SPOTIFY = process.env.CLIENT_ID_SPOTIFY;
public readonly CLIENT_SECRET_SPOTIFY = process.env.CLIENT_SECRET_SPOTIFY;
private readonly API_URL = "https://accounts.spotify.com/api/token";
private readonly CALLBACK = 'http://localhost:8080/api/spotify/callback';
private readonly SCOPES = 'user-read-private user-read-email user-read-playback-state user-read-currently-playing user-read-recently-played playlist-modify-public ugc-image-upload user-modify-playback-state';
constructor() {
this.initRoutes();
}
initRoutes() {
/**
* @swagger
* /api/spotify/exchange:
* get:
* summary: Initiate the Spotify login flow
* description: Redirect the user to the Spotify login page for authorization
* tags:
* - Spotify
* parameters:
* - in: query
* name: redirectUrl
* schema:
* type: string
* description: The URL to redirect the user after Spotify authorization (optional)
* responses:
* 302:
* description: Redirecting to Spotify login page
* 400:
* description: Bad request - Cannot connect to Spotify
*/
this.router.get(`${this.path}/exchange`, this.login);
/**
* @swagger
* /api/spotify/callback:
* get:
* summary: Handle Spotify callback and exchange code for access token
* description: Handle Spotify callback and exchange the received code for an access token
* tags:
* - Spotify
* responses:
* 302:
* description: Redirecting with access token information
* 400:
* description: Bad request - Error connecting to Spotify
*/
this.router.get(`${this.path}/callback`, this.getAccessToken);
/**
* @swagger
* /api/spotify/refresh:
* get:
* summary: Refresh the Spotify access token using a refresh token
* description: Refresh the Spotify access token using a refresh token
* tags:
* - Spotify
* parameters:
* - in: query
* name: refresh_token
* schema:
* type: string
* required: true
* description: The refresh token obtained during the initial authorization
* responses:
* 200:
* description: Successfully refreshed access token
* content:
* application/json:
* schema:
* type: object
* properties:
* access_token:
* type: string
* description: The new access token
* refresh_token:
* type: string
* description: The new refresh token
* expires_in:
* type: number
* description: The time until the access token expires (in seconds)
* 400:
* description: Bad request - Cannot refresh the access token
*/
this.router.get(`${this.path}/refresh`, this.getRefreshToken);
}
private readonly clientRedirect = 'spotify_final_redirect-uri-key';
private login = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const redirectResponse = req.query.redirectUrl ? req.query.redirectUrl : req.headers.referer;
res.cookie(this.clientRedirect, redirectResponse);
res.redirect('https://accounts.spotify.com/authorize?' +
qs.stringify({
response_type: 'code',
client_id: this.CLIENT_ID_SPOTIFY,
scope: this.SCOPES,
redirect_uri: this.CALLBACK,
}));
} catch (error) {
res.status(400).send('Cannot connect: ' + error.message);
}
};
private getRefreshToken = async (
req: Request,
res: Response
): Promise<Response | void> => {
const params = req.query.refresh_token;
if (!req.query.refresh_token) {
return res.json({
"error": "Parameter refresh_token missing"
});
}
const authOptions = {
method: 'POST',
url: 'https://accounts.spotify.com/api/token',
data: qs.stringify({
grant_type: 'refresh_token',
refresh_token: params
}),
headers: {
'Authorization': 'Basic ' + (Buffer.from(this.CLIENT_ID_SPOTIFY + ':' + this.CLIENT_SECRET_SPOTIFY).toString('base64')),
'Content-Type': 'application/x-www-form-urlencoded'
},
json: true
};
axios(authOptions)
.then(session => {
if (session.status === 200) {
res.send({
"access_token": session.data.access_token,
"refresh_token": session.data.refresh_token,
"expires_in": session.data.expires_in
});
}
})
.catch(error => {
res.status(400).send("Cannot get a new refresh token");
});
}
private getAccessToken = async (
req: Request,
res: Response
): Promise<Response | void> => {
let code = req.query.code;
let storedRedirectUri = req.cookies ? req.cookies[this.clientRedirect] : null;
let authOptions = {
method: 'POST',
url: this.API_URL,
data: qs.stringify({
code: code,
redirect_uri: this.CALLBACK,
grant_type: 'authorization_code'
}),
headers: {
'Authorization': 'Basic ' + (Buffer.from(this.CLIENT_ID_SPOTIFY + ':' + this.CLIENT_SECRET_SPOTIFY).toString('base64')),
'Content-Type': 'application/x-www-form-urlencoded'
},
json: true
};
try {
const resp = await axios(authOptions);
if (resp.status === 200) {
let access_token = resp.data.access_token;
let expiration = resp.data.expires_in;
let refresh = resp.data.refresh_token
res.clearCookie(this.clientRedirect);
res.redirect(`${storedRedirectUri}?` +
qs.stringify({
"access_token": access_token,
"expires_in": expiration,
"refresh_token": refresh
}));
}
} catch (error) {
res.status(400).send('Error connection: ' + error.message);
}
};
}
export default SpotifyController;

@ -1,803 +0,0 @@
import { Router, Request, Response } from 'express';
import IController from './interfaces/IController';
import User from '../models/User';
import UserService from '../services/UserService';
import validator from '../middlewares/UserValidation'
import validationMiddleware from '../middlewares/validationMiddleware';
import authenticator from '../middlewares/authMiddleware'
import LocationService from '../services/LocationService';
import axios from 'axios';
import { IMusic } from '../models/Music';
import * as fs from 'fs';
import * as base64js from 'base64-js';
class UserController implements IController {
public path = '/user';
public authPath = '/auth';
public router = Router();
private userService = new UserService();
private locationService = new LocationService();
constructor() {
this.initRoutes();
}
private initRoutes(): void {
/**
* @swagger
* /api/auth/register:
* post:
* summary: Register a new user
* description: Register a new user with the provided details
* tags:
* - Authentication
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* default: john.doe@example.com
* password:
* type: string
* default: stringPassword123
* name:
* type: string
* default: john_doe
* tokenSpotify:
* type: string
* responses:
* 201:
* description: User registered successfully
* 400:
* description: Bad request - Invalid input data
* 401:
* description: Unauthorized - Spotify token is invalid
* 409:
* description: Conflict - Email or username is already in use
* 500:
* description: Internal Server Error - Spotify account not authorized or not found
*/
this.router.post(
`${this.authPath}/register`,
validationMiddleware(validator.register),
this.register
);
/**
* @swagger
* /api/auth/login:
* post:
* summary: Login a user
* description: Login with the provided email and password
* tags:
* - Authentication
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* default: john.doe@example.com
* password:
* type: string
* default: stringPassword123
* responses:
* 200:
* description: User logged in successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* token:
* type: string
* 400:
* description: Bad request - Invalid input data
*/
this.router.post(
`${this.authPath}/login`,
validationMiddleware(validator.login),
this.login
);
/**
* @swagger
* /api/user:
* get:
* summary: Get user information
* description: Get information about the authenticated user
* tags:
* - User
* security:
* - bearerAuth: []
* responses:
* 200:
* description: User logged in successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
*/
this.router.get(`${this.path}`, authenticator, this.getUser);
/**
* @swagger
* /api/users:
* get:
* summary: Get information about multiple users
* description: Get information about multiple users based on provided user ids
* tags:
* - User
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: ids
* schema:
* type: string
* description: Comma-separated list of user ids
* responses:
* 200:
* description: Users information retrieved successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
*/
this.router.get(`${this.path}s`, authenticator, this.getUsers);
/**
* @swagger
* /api/user:
* delete:
* summary: Delete the authenticated user
* description: Delete the authenticated user and associated data
* tags:
* - User
* security:
* - bearerAuth: []
* responses:
* 204:
* description: User deleted successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 404:
* description: User not found
*/
this.router.delete(`${this.path}`, authenticator, this.deleteUser);
/**
* @swagger
* /api/user/nextTo:
* get:
* summary: Get users near the authenticated user
* description: Get information about users near the authenticated user based on location
* tags:
* - User
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: longitude
* schema:
* type: number
* description: Longitude of the user's current location
* - in: query
* name: latitude
* schema:
* type: number
* description: Latitude of the user's current location
* - in: query
* name: currentMusic
* schema:
* type: string
* description: The ID of the currently playing music
* responses:
* 201:
* description: Users near the authenticated user retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* data:
* type: array
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 400:
* description: Bad request - Invalid input data
*/
this.router.get(`${this.path}/nextTo`, authenticator, this.getUserNext);
/**
* @swagger
* /api/user/musics/{id}:
* delete:
* summary: Delete a music from the authenticated user's liked list
* description: Delete a music from the authenticated user's liked list by music id
* tags:
* - User
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* schema:
* type: string
* description: The ID of the music to delete
* responses:
* 200:
* description: Music deleted successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 404:
* description: Music not found
*/
this.router.delete(`${this.path}/musics/:id`, authenticator, this.deleteMusic);
/**
* @swagger
* /api/user/musics:
* post:
* summary: Add a music to the authenticated user's liked list
* description: Add a music to the authenticated user's liked list
* tags:
* - User
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* musicId:
* type: string
* description: The ID of the music to add
* userId:
* type: string
* description: The ID of the user who liked the music
* responses:
* 201:
* description: Music added to liked list successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 400:
* description: Bad request - Invalid input data
*/
this.router.post(`${this.path}/musics`, authenticator, this.addMusic);
/**
* @swagger
* /api/user/musics:
* get:
* summary: Get the list of musics liked by the authenticated user
* description: Get the list of musics liked by the authenticated user
* tags:
* - User
* security:
* - bearerAuth: []
* responses:
* 200:
* description: List of musics retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* musics:
* type: array
* 401:
* description: Unauthorized - Invalid or missing authentication token
*/
this.router.get(`${this.path}/musics`, authenticator, this.getMusics);
/**
* @swagger
* /api/user/name:
* put:
* summary: Update the name of the authenticated user
* description: Update the name of the authenticated user
* tags:
* - User
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* description: The new name for the user
* responses:
* 200:
* description: User name updated successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 400:
* description: Bad request - Invalid input data
* 409:
* description: Conflict - The provided name is already in use by another user
*/
this.router.put(`${this.path}/name`, authenticator, this.setName);
/**
* @swagger
* /api/user/email:
* put:
* summary: Update the email of the authenticated user
* description: Update the email of the authenticated user
* tags:
* - User
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* format: email
* description: The new email for the user
* responses:
* 200:
* description: User email updated successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 400:
* description: Bad request - Invalid input data
* 409:
* description: Conflict - The provided email is already in use by another user
*/
this.router.put(`${this.path}/email`, authenticator, this.setEmail);
/**
* @swagger
* /api/user/spotify:
* put:
* summary: Update the spotify account
* description: Update the spotify account of the authenticated user
* tags:
* - User
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* tokenSpotify:
* type: string
* description: Spotify token
* responses:
* 200:
* description: Spotify account updated successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 409:
* description: Conflict - The provided token is already in use by another user
* 500:
* description: Internal Server Error - Spotify account not authorized or not found
*/
this.router.put(`${this.path}/spotify`, authenticator, this.setSpotify);
/**
* @swagger
* /api/user/image:
* put:
* summary: Update the profile image of the authenticated user
* description: Update the profile image of the authenticated user
* tags:
* - User
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* image:
* type: string
* format: base64
* description: The new profile image for the user (base64 encoded)
* responses:
* 200:
* description: User profile image updated successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 500:
* description: Internal Server Error - Unable to update the profile image
*/
this.router.put(`${this.path}/image`, authenticator, this.setImage);
/**
* @swagger
* /api/user/password:
* put:
* summary: Update the password of the authenticated user
* description: Update the password of the authenticated user
* tags:
* - User
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* oldPassword:
* type: string
* description: The current password of the user
* newPassword:
* type: string
* description: The new password for the user
* responses:
* 200:
* description: User password updated successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 500:
* description: Internal Server Error - Unable to update the password
*/
this.router.put(`${this.path}/password`, authenticator, this.setPassword);
}
private register = async (
req: Request,
res: Response
): Promise<Response | void> => {
let access_token;
let idSpotify: string;
let image: string;
const { name, email, password, tokenSpotify } = req.body;
const apiBaseUrl = process.env.API_BASE_URL || 'http://localhost:8080/api';
const refreshUrl = `${apiBaseUrl}/spotify/refresh?refresh_token=${tokenSpotify}`;
try {
const authOptions = {
method: 'GET',
url: refreshUrl,
json: true
};
const authResponse = await axios(authOptions);
if (authResponse.status === 200) {
access_token = authResponse.data.access_token;
const headers = {
Authorization: `Bearer ${access_token}`,
};
const resp = await axios.get('https://api.spotify.com/v1/me', { headers });
if (resp.status == 200) {
const images = resp.data.images;
idSpotify = resp.data.id;
if (images && images.length > 0) {
images.sort((a: any, b: any) => b.height - a.height);
image = images[0].url;
}
else {
const imagePath = './src/assets/images/default_user.png';
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = 'data:image/png;base64,' + base64js.fromByteArray(imageBuffer);
image = base64Image
}
}
}
} catch (error: any) {
console.log(error);
if (error.response.status === 400) {
res.status(401).send("Unauthorized: Spotify token is invalid");
return;
}
res.status(500).send("Internal Server Error: Unable to authenticate with Spotify");
return;
}
try {
const token = await this.userService.register(
name.toLowerCase(),
email.toLowerCase(),
password,
idSpotify,
tokenSpotify,
image
);
res.status(201).json({ token });
} catch (error: any) {
res.status(409).json(error.message);
}
};
private login = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const { email, password } = req.body;
const token = await this.userService.login(email, password);
res.status(200).json({ token });
} catch (error: any) {
res.status(400).json(error.message)
}
};
private getUser = (
req: Request,
res: Response
): Response | void => {
res.status(200).send({ data: req.user });
};
private getUsers = async (
req: Request,
res: Response
): Promise<Response | void> => {
const userIds = req.query.ids as string;
if (!userIds) {
return res.status(200).json([]);
}
const userIdArray = userIds.split('&');
try {
const users = await this.userService.getUsers(userIdArray);
res.json(users);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
private deleteUser = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const { _id } = req.user;
await this.userService.delete(_id);
await this.locationService.delete(_id);
res.status(204).send();
} catch (error: any) {
res.status(404).json(error.message)
}
};
private getUserNext = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const longitude = Number(req.query.longitude);
const latitude = Number(req.query.latitude);
if (isNaN(longitude) || isNaN(latitude)) {
console.log('Unable to convert string to number');
throw new Error('Unable to convert string to number');
}
const userId = req.user.id;
const musicId = String(req.query.currentMusic);
const data = await this.locationService.getNearUser(userId, musicId, latitude, longitude);
res.status(201).send(data);
}
catch (error: any) {
res.status(400).json(error.message)
}
}
private deleteMusic = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const { _id } = req.user;
const musicId: string = req.params.id;
if (!musicId) {
return res.status(400).json({ error: 'musicId are required fields.' });
}
const deleted = await this.userService.deleteMusic(_id, musicId);
if (deleted) {
res.status(200).send({ message: 'Music deleted successfully.' });
} else {
res.status(404).json({ error: 'Music not found.' });
}
} catch (error: any) {
res.status(404).json(error.message)
}
}
private addMusic = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const { _id } = req.user;
const { musicId, userId } = req.body;
if (!musicId || !userId) {
return res.status(400).json({ error: 'musicId and userId are required fields.' });
}
const music: IMusic = {
musicId,
userId,
date: new Date(),
};
await this.userService.addMusic(_id, music);
res.status(201).send({ music });
} catch (error: any) {
res.status(400).json(error.message)
}
}
private getMusics = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const userId: string = req.user.id;
const musics = await this.userService.getMusics(userId);
return res.status(200).json({ musics });
} catch (error: any) {
res.status(400).json(error.message)
}
}
private setName = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const { _id } = req.user;
const { name } = req.body;
const regex = /^\w+$/;
if (!regex.test(name) || !name) {
return res.status(400).json({ error: "Name should only contain alphanumeric characters (letters, numbers, and underscores)" });
}
await this.userService.setName(_id, name.toLowerCase());
res.status(200).json({ message: 'Name updated successfully' });
} catch (error: any) {
res.status(409).json(error.message)
}
}
private setEmail = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const { _id } = req.user;
const { email } = req.body;
const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!regex.test(email) || !email) {
return res.status(400).json({ error: "Invalid email" });
}
await this.userService.setEmail(_id, email.toLowerCase());
res.status(200).json({ message: 'Email updated successfully' });
} catch (error: any) {
res.status(409).json(error.message)
}
}
private setSpotify = async (
req: Request,
res: Response
): Promise<Response | void> => {
let access_token;
let idAccount: string;
let image: string;
const { _id, idSpotify } = req.user;
const { tokenSpotify } = req.body;
if (!tokenSpotify) {
return res.status(400).json({ error: 'TokenSpotify is missing in the request.' });
}
const apiBaseUrl = process.env.API_BASE_URL || 'http://localhost:8080/api';
const refreshUrl = `${apiBaseUrl}/spotify/refresh?refresh_token=${tokenSpotify}`;
try {
const authOptions = {
method: 'GET',
url: refreshUrl,
json: true
};
const authResponse = await axios(authOptions);
if (authResponse.status === 200) {
access_token = authResponse.data.access_token;
const headers = {
Authorization: `Bearer ${access_token}`,
};
const resp = await axios.get('https://api.spotify.com/v1/me', { headers });
if (resp.status == 200) {
const images = resp.data.images;
idAccount = resp.data.id;
if (idSpotify === idAccount) {
return res.status(400).json({ error: 'idSpotify cannot be the same as idAccount.' });
}
if (images && images.length > 0) {
images.sort((a: any, b: any) => b.height - a.height);
image = images[0].url;
}
else {
const imagePath = './src/assets/images/default_user.png';
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = 'data:image/png;base64,' + base64js.fromByteArray(imageBuffer);
image = base64Image
}
}
}
} catch (error: any) {
console.log(error);
res.status(500).send("Internal Server Error: Unable to authenticate with Spotify");
return;
}
try {
await this.userService.setSpotify(_id, tokenSpotify, idAccount, image);
res.status(200).json({ message: 'Spotify token updated successfully' });
} catch (error: any) {
res.status(409).json(error.message)
}
}
private setImage = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const { _id } = req.user;
const { image } = req.body;
await this.userService.setImage(_id, image);
res.status(200).json({ message: 'Image updated successfully' });
} catch (error: any) {
res.status(500).json(error.message)
}
}
private setPassword = async (
req: Request,
res: Response
): Promise<Response | void> => {
try {
const { _id } = req.user;
const { oldPassword, newPassword } = req.body;
await this.userService.setPassword(_id, oldPassword, newPassword);
res.status(200).json({ message: 'Password updated successfully' });
} catch (error: any) {
res.status(500).json(error.message)
}
}
}
export default UserController;
declare global {
namespace Express {
export interface Request {
user: User;
}
}
}

@ -1,26 +0,0 @@
import { Schema, model } from 'mongoose';
import { Location } from '../models/Location';
const locationSchema = new Schema({
userId: {
type: String,
required: true,
unique: true,
},
musicId: {
type: String,
required: true,
},
latitude: {
type: Number,
required: true,
},
longitude: {
type: Number,
required: true,
}
},
{ timestamps: true }
);
export default model<Location>('Location', locationSchema);

@ -0,0 +1,4 @@
// export default db = new MongoClient(uri);

@ -1,62 +0,0 @@
import User from "../models/User";
import { Schema, model } from 'mongoose';
import bcrypt from 'bcrypt';
const userSchema = new Schema({
idSpotify: {
type: String,
required: true,
unique: true
},
tokenSpotify: {
type: String,
required: true
},
name: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true,
trim: true
},
password: {
type: String,
required: true
}
,
image: {
type: String,
required: true
},
musics_likes: {
type: [{
musicId: String,
userId: String,
date: Date
}],
default: []
}
},
{ timestamps: true }
);
userSchema.pre<User>('save', async function (next) {
if (!this.isModified('password')) {
return next();
}
const hash = await bcrypt.hash(this.password, 8);
this.password = hash;
next();
});
userSchema.methods.isValidPassword = async function (
password: string
): Promise<boolean | Error> {
return await bcrypt.compare(password, this.password);
};
export default model<User>('User', userSchema);

@ -0,0 +1,39 @@
import { Schema, model,Document } from 'mongoose';
const locationSchema = new Schema(
{
idFlad: {
type: String,
required: true,
unique: true,
},
latitude: {
type: Number,
required: true,
},
longitude: {
type: Number,
required: true,
},
currentMusicId : {
type: String,
}
},
{ timestamps: true }
);
// fladDevDb
// ZslYlNRWIOUU7i6o
export default model<ILocation>('Location', locationSchema);
export interface ILocation extends Document {
idFlad: string;
latitude : number;
longitude: number;
}

@ -0,0 +1,8 @@
import { Schema, model } from 'mongoose';
const notificationSchema = new Schema({
type: {type: String, required: true},
content: {type: String, required: true}
});
export default {Notification: model("nofitication", notificationSchema)}

@ -1,6 +1,8 @@
import { Schema } from 'mongoose';
export default interface Token extends Object {
interface IToken extends Object {
id: Schema.Types.ObjectId;
expiresIn: number;
}
}
export default IToken;

@ -0,0 +1,11 @@
import { Document } from 'mongoose';
export default interface IUser extends Document {
email: string;
name: string;
password: string;
idFlad : string;
idSpotify : string;
isValidPassword(password: string): Promise<Error | boolean>;
}

@ -0,0 +1,79 @@
// maye this file should me the UserModel like we had in php cause it's here we verrify the password
import IUser from "./UserInterface";
import { Schema, model } from 'mongoose';
import bcrypt from 'bcrypt';
// const userSchema: Schema = new Schema<IUser>({
// pseudo: {type: String, index: { unique: true }},
// email: {type: String},
// idDafl: {type: String, index: { unique: true }},
// idSpotify: {type: String},
// password: {type: String},
// prenom: {type: String, default: ""},
// description: {type: String, default: ""},
// nom: {type: String, default: ""},
// ville: {type: String, default: ""},
// profilPic: {type: String},
// noteList: [],
// notifications: [],
// friends: {type: [String] },
// favoris: [],
// conversations: {type: [String] }
// });
const userSchema = new Schema(
{
idFlad: {
type: String,
required: true,
unique: true,
},
idSpotify: {
type: String,
required: true,
unique: true,
},
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
// this mean that we identify user by email
unique: true,
// delete the whitespace
trim: true,
},
password: {
type: String,
},
},
{ timestamps: true }
);
// this means that we hash the user password before saving it to the database
userSchema.pre<IUser>('save', async function (next) {
if (!this.isModified('password')) {
//just had that to be sure that the api still going
return next();
}
const hash = await bcrypt.hash(this.password, 8);
this.password = hash;
next();
});
userSchema.methods.isValidPassword = async function (
password: string
): Promise< boolean | Error> {
return await bcrypt.compare(password, this.password);
};
// fladDevDb
// ZslYlNRWIOUU7i6o
export default model<IUser>('User', userSchema);
// export const User: Model<IUser> = model('User', userSchema);

@ -1,11 +1,14 @@
import Joi from 'joi';
const register = Joi.object({
name: Joi.string().max(30).required().regex(/^\w+$/)
.message("Name should only contain alphanumeric characters (letters, numbers, and underscores)"),
name: Joi.string().max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
tokenSpotify: Joi.string().required()
// can add an field like confimPassword and cheked that the password is equal to the confirmPassword
idSpotify: Joi.string(),
idFlad : Joi.string(),
});
const login = Joi.object({

@ -1,12 +0,0 @@
import App from "./app";
import SpotifyController from "./controllers/spotifyController";
import UserController from "./controllers/userController";
import dotenv from 'dotenv'
dotenv.config();
const app = new App(
[new SpotifyController(), new UserController()],
Number(process.env.PORT)
);
app.listen();

@ -0,0 +1,45 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import IToken from '../database/schema/Token/IToken';
import UserSchema from '../database/schema/User/UserSchema';
import token from '../model/token';
import HttpException from './exeption/httpExeption';
async function authenticatedMiddleware(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const bearer = req.headers.authorization;
if (!bearer || !bearer.startsWith('Bearer ')) {
return next(new HttpException(401, 'Unauthorised'));
}
const accessToken = bearer.split('Bearer ')[1].trim();
try {
const payload: IToken | jwt.JsonWebTokenError = await token.verifyToken(
accessToken
);
if (payload instanceof jwt.JsonWebTokenError) {
return next(new HttpException(401, 'Unauthorised'));
}
const user = await UserSchema.findById(payload.id)
.select('-password')
.exec();
if (!user) {
return next(new HttpException(401, 'Unauthorised'));
}
req.user = user;
return next();
} catch (error) {
return next(new HttpException(401, 'Unauthorised'));
}
}
export default authenticatedMiddleware;

@ -8,5 +8,6 @@ class HttpException extends Error {
this.message = message;
}
}
// en fontion de l'exeption firebas,etc une bonne exeption
export default HttpException;

@ -0,0 +1,8 @@
// export const loggerOptions: expressWinston.LoggerOptions = {
// transports: [new winston.transports.Console()],
// format: winston.format.combine(
// winston.format.json(),
// winston.format.prettyPrint(),
// winston.format.colorize({ all: true })
// ),
// };

@ -1,45 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import Token from '../models/Token';
import UserSchema from '../database/UserSchema';
import token from '../services/TokenService';
import HttpException from '../exception/HttpException';
async function authMiddleware(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const bearer = req.headers.authorization;
if (!bearer || !bearer.startsWith('Bearer ')) {
return next(new HttpException(401, 'Unauthorized'));
}
const accessToken = bearer.split('Bearer')[1].trim();
try {
const payload: Token | jwt.JsonWebTokenError = await token.verifyToken(
accessToken
);
if (payload instanceof jwt.JsonWebTokenError) {
return next(new HttpException(401, 'Unauthorized'));
}
const user = await UserSchema.findById(payload.id)
.select('-password')
.exec();
if (!user) {
return next(new HttpException(401, 'Unauthorized'));
}
req.user = user;
return next();
} catch (error) {
return next(new HttpException(401, 'Unauthorized'));
}
}
export default authMiddleware;

@ -0,0 +1,5 @@
export default interface IUser{
name: string;
email: string;
avatar?: string;
}

@ -0,0 +1,59 @@
export interface Position {
/**
* Creation timestamp for coords
*/
timestamp: number;
/**
* The GPS coordinates along with the accuracy of the data
*/
coords: {
/**
* Latitude in decimal degrees
*/
latitude: number;
/**
* longitude in decimal degrees
*/
longitude: number;
};
}
export class PlacePosition implements Position {
timestamp: number;
coords: {
latitude: number;
longitude: number;
};
constructor(timestamp: number,latitude : number ,longitude: number){
this.timestamp = timestamp;
this.coords = {latitude, longitude};
}
}
export class UserLocation {
uuid: string;
latitude : number;
longitude: number;
currentMusicId : string;
constructor(uuid: string, latitude: number, longitude: number, currentMusicId : string) {
this.uuid = uuid;
this.latitude = latitude;
this.longitude = longitude;
this.currentMusicId = currentMusicId;
}
}
export class Place{
position: Position;
address: Address;
constructor(address: Address,position: Position){
this.position = position;
this.address = address;
}
}
export type Address = {
street : string;
city : string;
state : string;
zip : string;
}

@ -0,0 +1,27 @@
import jwt from 'jsonwebtoken';
import IUser from '../database/schema/User/UserInterface';
import IToken from '../database/schema/Token/IToken';
export const createToken = (user: IUser): string => {
return jwt.sign({ id: user._id }, "foo" as jwt.Secret, {
expiresIn: '100d',
});
};
export const verifyToken = async (
token: string
): Promise<jwt.VerifyErrors | IToken> => {
return new Promise((resolve, reject) => {
jwt.verify(
token,
"foo" as jwt.Secret,
(err, payload) => {
if (err) return reject(err);
resolve(payload as IToken);
}
);
});
};
export default { createToken, verifyToken };

@ -1,24 +0,0 @@
import { Document } from 'mongoose';
export class UserLocation {
_id: string;
userId: string;
musicId: string;
distance: number;
date: Date;
constructor(id: string, userId: string, musicId: string, distance: number, date: Date) {
this._id = id;
this.userId = userId;
this.musicId = musicId;
this.distance = distance;
this.date = date;
}
}
export class Location extends Document {
userId: string;
musicId: string;
latitude: number;
longitude: number;
updatedAt: Date;
}

@ -1,5 +0,0 @@
export interface IMusic {
musicId: string;
userId: string;
date: Date;
}

@ -1,5 +0,0 @@
export interface IPerson {
_id: string;
name: string;
image: string;
}

@ -1,13 +0,0 @@
import { Document } from 'mongoose';
import { IMusic } from './Music';
export default interface User extends Document {
idSpotify: string;
tokenSpotify: string;
name: string;
email: string;
password: string;
isValidPassword(password: string): Promise<Error | boolean>;
image: string;
musics_likes: IMusic[];
}

@ -0,0 +1,16 @@
import App from "./app";
import SpotifyController from "./controller/spotify-controller/spotifyCtrl";
import PingController from "./controller/TestCtrl";
import UserController from "./controller/user-controller/userCtrl";
const app = new App(
[new PingController(), new SpotifyController(), new UserController()],
Number(8080)
// Number(process.env.PORT)
);
app.listen();

@ -0,0 +1,139 @@
// import db from '../database';
import { Place, PlacePosition, Position, UserLocation } from '../model/locationModel';
import axios from 'axios';
import LocationSchema from "../database/schema/LocationSchema";
class LocationService {
private locationCollection = LocationSchema;
// private API_KEY : string = "AIzaSyBFCEAtmhZ8jvw84UTQvX3Aqpr66GVqB_A";
public async getNearUser(idFlad : string, latitude : number, longitude : number, currentMusicId: string)
{
await this.locationCollection.findOneAndUpdate(
{ idFlad },
{ idFlad, latitude, longitude, currentMusicId },
{ upsert: true }
);
const snapshot = await this.locationCollection.find({ idFlad: { $ne: idFlad } });
if (snapshot.length === 0) {
console.log('No matching documents.');
return;
}
let dbUsersList:UserLocation[] = [];
snapshot.forEach(doc => {
dbUsersList.push(new UserLocation(doc.idFlad,doc.latitude,doc.longitude, doc.currentMusicId));
console.log(doc.idFlad, '=>', doc);
});
// missing the curent music
let listUser: {userid: string, music : string}[] = [];
dbUsersList.forEach(user => {
console.log(user);
const dist = this.distanceBetween(latitude , longitude , user.latitude, user.longitude);
console.log(user.uuid,dist);
if (dist <= 100) {
listUser.push({userid : user.uuid, music : user.currentMusicId});
}
});
return listUser;
// $listUser[] = {userID,idMusic};
}
public getCenter (points: Position[]) {
if (Array.isArray(points) === false || points.length === 0) {
return false;
}
const numberOfPoints = points.length;
const sum = points.reduce(
(acc, point) => {
const pointLat = this.toRad(point.coords.latitude);
const pointLon = this.toRad(point.coords.longitude);
return {
X: acc.X + Math.cos(pointLat) * Math.cos(pointLon),
Y: acc.Y + Math.cos(pointLat) * Math.sin(pointLon),
Z: acc.Z + Math.sin(pointLat),
};
},
{ X: 0, Y: 0, Z: 0 }
);
const X = sum.X / numberOfPoints;
const Y = sum.Y / numberOfPoints;
const Z = sum.Z / numberOfPoints;
return {
longitude: this.toDeg(Math.atan2(Y, X)),
latitude: this.toDeg(Math.atan2(Z, Math.sqrt(X * X + Y * Y))),
};
};
public toRad = (value: number) => (value * Math.PI) / 180;
public toDeg = (value: number) => (value * 180) / Math.PI;
// sa c'est un utils du coup mettre dans une calss utils
// resulta en km
private distanceBetween (lat1 : number, lon1 : number, lat2: number, lon2 : number) : number {
if ((lat1 == lat2) && (lon1 == lon2)) {
return 0;
}
else {
var radlat1 = Math.PI * lat1/180;
var radlat2 = Math.PI * lat2/180;
var theta = lon1-lon2;
var radtheta = Math.PI * theta/180;
var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
if (dist > 1) {
dist = 1;
}
dist = Math.acos(dist);
dist = dist * 180/Math.PI;
dist = dist * 60 * 1.1515;
dist = dist * 1.609344;
return dist;
}
}
private distanceBetweenPosition(first : Position, second : Position) : number {
return this.distanceBetween (first.coords.latitude, first.coords.longitude, second.coords.latitude, second.coords.longitude)
}
// give a array of position sorted by distance and return the first
private findNearest(main : Position, list : Position[]){
this.orderByDistance(main, list)[0]
}
//distanceFn: DistanceFn = getDistance est param sa serrait cool de lui passer un fonction
private orderByDistance (mainPos: Position,coords: Position[]){
return coords
.slice()
.sort((a, b) => this.distanceBetweenPosition(mainPos, a) - this.distanceBetweenPosition(mainPos, b));
};
// getCenter(coords)
}
export default LocationService;

@ -0,0 +1,62 @@
import UserSchema from "../database/schema/User/UserSchema";
import token from "../model/token";
class UserService {
private user = UserSchema;
/**
* Register a new user
*/
public async register(
name: string,
email: string,
password: string,
idFlad : string,
idSpotify : string
): Promise<string | Error> {
try {
const user = await this.user.create({
name,
email,
password,
idFlad,
idSpotify
});
const accessToken = token.createToken(user);
return accessToken;
} catch (error : any) {
throw new Error(error.message);
}
}
/**
* Attempt to login a user
*/
public async login(
email: string,
password: string
): Promise<string | Error> {
// should maybe creat a method base on id and other information for better security
// need to view with Emre
const user = await this.user.findOne({ email });
console.log(user?._id);
// const user = await this.user.findById(idFlad);
if (user === undefined || user === null) {
console.log("Could")
throw new Error('Unable to find user with that email address');
}
if (await user.isValidPassword(password)) {
return token.createToken(user);
} else {
throw new Error('Wrong credentials given');
}
}
}
export default UserService;

@ -1,66 +0,0 @@
import { UserLocation } from '../models/Location';
import LocationSchema from "../database/LocationSchema";
class LocationService {
private location = LocationSchema;
public async getNearUser(userId: string, musicId: string, latitude: number, longitude: number) {
await this.location.findOneAndUpdate(
{ userId },
{ userId, musicId, latitude, longitude },
{ upsert: true }
);
const snapshot = await this.location.find({ userId: { $ne: userId } });
if (!snapshot.length) {
console.log('No matching documents.');
return;
}
let usersLocation: UserLocation[] = [];
snapshot.forEach(location => {
const distance = this.distanceBetween(latitude, longitude, location.latitude, location.longitude);
if (distance <= 1000) {
usersLocation.push(new UserLocation(location._id, location.userId, location.musicId, Math.ceil(distance + 0.1 / 200) * 200, location.updatedAt));
}
});
return { data: usersLocation };
}
private distanceBetween(lat1: number, lon1: number, lat2: number, lon2: number): number {
if ((lat1 == lat2) && (lon1 == lon2)) {
return 0;
}
else {
const radlat1 = Math.PI * lat1 / 180;
const radlat2 = Math.PI * lat2 / 180;
const theta = lon1 - lon2;
const radtheta = Math.PI * theta / 180;
let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
if (dist > 1) {
dist = 1;
}
dist = Math.acos(dist);
dist = dist * 180 / Math.PI;
dist = dist * 60 * 1.1515;
dist = dist * 1.609344 * 1000;
return dist;
}
}
public async delete(
id: string
): Promise<void | Error> {
try {
await this.location.findByIdAndRemove(id);
} catch (error: any) {
throw new Error(error.message);
}
}
}
export default LocationService;

@ -1,26 +0,0 @@
import jwt from 'jsonwebtoken';
import User from '../models/User';
import Token from '../models/Token';
export const createToken = (user: User): string => {
return jwt.sign({ id: user._id }, process.env.SECRET_JWT as jwt.Secret, {
expiresIn: '15d',
});
};
export const verifyToken = async (
token: string
): Promise<jwt.VerifyErrors | Token> => {
return new Promise((resolve, reject) => {
jwt.verify(
token,
process.env.SECRET_JWT as jwt.Secret,
(err, payload) => {
if (err) return reject(err);
resolve(payload as Token);
}
);
});
};
export default { createToken, verifyToken };

@ -1,175 +0,0 @@
import { IMusic } from "../models/Music";
import UserSchema from "../database/UserSchema";
import token from "./TokenService";
import { IPerson } from "../models/Person";
import mongoose from "mongoose";
class UserService {
private user = UserSchema;
public async register(
name: string,
email: string,
password: string,
idSpotify: string,
tokenSpotify: string,
image: string
): Promise<string | Error> {
try {
const user = await this.user.create({
name,
email,
password,
tokenSpotify,
idSpotify,
image
});
return token.createToken(user);
} catch (error: any) {
throw new Error(error.message);
}
}
public async login(
email: string,
password: string
): Promise<string | Error> {
const user = await this.user.findOne({ email });
if (!user) {
throw new Error('Wrong credentials given');
}
if (await user.isValidPassword(password)) {
return token.createToken(user);
} else {
throw new Error('Wrong credentials given');
}
}
public async delete(
id: string
): Promise<void | Error> {
try {
await this.user.findByIdAndRemove(id);
} catch (error: any) {
throw new Error(error.message);
}
}
public async getUsers(
ids: string[]
): Promise<IPerson[] | Error> {
try {
const validIds = ids.filter(id => mongoose.Types.ObjectId.isValid(id));
if (validIds.length === 0) {
return [];
}
return await this.user.find({ _id: { $in: validIds } })
.select('_id name image')
} catch (error: any) {
throw new Error(error.message);
}
}
public async addMusic(userId: string, music: IMusic): Promise<string | Error> {
try {
return await this.user.findByIdAndUpdate(userId, {
$push: { musics_likes: music },
});
} catch (error: any) {
throw new Error(error.message);
}
}
public async deleteMusic(userId: string, musicId: string): Promise<boolean | Error> {
try {
const userOld = await this.user.findById(userId);
const userNew = await this.user.findByIdAndUpdate(userId, {
$pull: { musics_likes: { _id: musicId } },
}, { new: true });
if (userOld.musics_likes.length === userNew.musics_likes.length) {
return false;
}
return true;
} catch (error) {
throw new Error(error.message);
}
}
public async getMusics(userId: string): Promise<IMusic[] | Error> {
try {
const user = await this.user.findById(userId);
return user?.musics_likes || [];
} catch (error) {
throw new Error(error.message);
}
}
public async setName(userId: string, newName: string): Promise<void | Error> {
try {
await this.user.findByIdAndUpdate(
userId,
{ name: newName }
);
} catch (error) {
throw new Error(error.message);
}
}
public async setEmail(userId: string, newEmail: string): Promise<void | Error> {
try {
await this.user.findByIdAndUpdate(
userId,
{ email: newEmail }
);
} catch (error) {
throw new Error(error.message);
}
}
public async setSpotify(userId: string, tokenSpotify: string, idSpotify: string, image: string): Promise<void | Error> {
try {
await this.user.findByIdAndUpdate(
userId,
{
tokenSpotify: tokenSpotify,
idSpotify: idSpotify,
image: image
}
);
} catch (error) {
throw new Error(error.message);
}
}
public async setImage(userId: string, newImage: string): Promise<void | Error> {
try {
await this.user.findByIdAndUpdate(
userId,
{ image: newImage }
);
} catch (error) {
throw new Error(error.message);
}
}
public async setPassword(userId: string, oldPassword: string, newPassword: string): Promise<void | Error> {
try {
const user = await this.user.findById(userId);
if (await user.isValidPassword(oldPassword)) {
user.password = newPassword;
await user.save();
} else {
throw new Error('Old password does not match.');
}
} catch (error) {
throw new Error(error.message);
}
}
}
export default UserService;

@ -1,34 +0,0 @@
import swaggerJsdoc from "swagger-jsdoc";
const options = {
definition: {
openapi: "3.0.1",
info: {
title: "FLAD API",
version: "1.0.0",
description:
"This is the Express API for the Flad project.",
contact: {
name: "Flad Dev",
url: "code",
email: "fladdevpro@gmail.com",
},
},
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
}
}
},
security: [{
bearerAuth: ["read"]
}]
},
apis: ["./dist/**/*.js"],
};
export const specs = swaggerJsdoc(options);

@ -1,17 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
}
},
"include": ["./src/**/*"]
}

@ -9,7 +9,6 @@ npm-debug.*
*.mobileprovision
*.orig.*
web-build/
constants/config.ts
# macOS
.DS_Store

@ -1,13 +1,25 @@
import { StyleSheet } from 'react-native';
import { Provider } from 'react-redux';
import store from './redux/store';
import AuthNavigation from './navigation/AuthNavigation';
import * as SplashScreen from 'expo-splash-screen';
import SpotifyService from './services/spotify/spotify.service';
SplashScreen.preventAutoHideAsync();
export default function App() {
return (
<Provider store={store}>
<AuthNavigation />
<AuthNavigation/>
</Provider>
);
}
}
const styles = StyleSheet.create({
mainSafeArea: {
flex: 1,
backgroundColor: "#141414",
}
});
export const theService = new SpotifyService('BQC0rAGJvxTt4-24P-nda6qP9iXYCql2eApnUAoEbZZkKemJ11cU3Nx-I_tKVX0FwEgFbIbSIuaVvxOapRVJq2z1Htyy3XQ5jIYNsrhrnp3KTCfppamAjxgDTf6khBrNGTxe6CNKBsMhc5IRnphey5Td2zJPvGMwnFFfMQdCtVAVsCNK7kPKlCAaf_kRMAoPn30Qk4RD45XmwtZIwQg7X0J4beGuHSiBf0MRjhsnFEW89GxVm8YuIVwgrDbF3izfPR0AlqS4IMJT5m4pEA72lYEwp1JnSDVsafILzmksaqG-11H3WAsWIENrOIu_j7qNgbvYwmUWXOrYmeWBkQ');

@ -0,0 +1,12 @@
export default class Artist {
private id: string;
private name: string;
private url: string; // Image.source
constructor(id: string, name: string, url: string) {
this.id = id;
this.name = name;
this.url = url;
}
}

@ -0,0 +1,34 @@
import SpotifyService from "../services/spotify/spotify.service";
class Manager {
// injection de dépences
spotifyService = new SpotifyService();
userService = new userService();
// spotify methods
apiAuthorization(url: string) {
this.spotifyService.apiAuthorization(url);
}
getCompleteMusic = async (id: string): Promise<Music> => {
// Map info = await spotifyService.getTrackInfo(id);
// return Music(id, info['name'], info['artist'], info['cover']);
}
removeFromPlaylist(id: string) {
this.spotifyService.removeFromPlaylist(id);
}
addToPlaylist(id: string) {
this.spotifyService.addToPlaylist(id);
}
playTrack(id: string) {
this.spotifyService.playTrack(id);
}
}
export default Manager;

@ -0,0 +1,14 @@
// export default class Music {
// private id : string;
// private name : string;
// private artist : string;
// private linkCover : string; // Image.source
// constructor(id : string, name : string, artist : string, linkCover : string){
// this.id = id;
// this.name = name;
// this.artist = artist;
// this.linkCover = linkCover;
// }
// }

@ -0,0 +1,22 @@
import Music from "./Music";
export class Spot {
private _userId: string;
private _music: Music;
constructor(userId: string, music: Music) {
this._userId = userId;
this._music = music;
}
get userSpotifyId(): string {
return this._userId;
}
set userSpotifyId(value: string) {
this._userId = value;
}
get music(): Music {
return this._music;
}
set music(value: Music) {
this._music = value;
}
}

@ -0,0 +1,5 @@
class TokenSpotify {
_accessToken: string;
_refreshToken: string;
late DateTime _tokenEnd;
}

@ -0,0 +1,43 @@
export class User {
//attributes from DAFL
private _idFlad: string;
private _idSpotify: string;
private _email: string;
private _createdAt: Date;
private _name: string;
public image: string = require('../assets/images/jul.png');
//constructors
constructor(idFlad: string, idSpotify: string, email: string, createdAt: Date, name: string, image: string) {
this._name = name;
this._idFlad = idFlad;
this._idSpotify = idSpotify;
this._createdAt = createdAt;
this._email = email;
this.image = image;
}
get idFlad(): string {
return this._idFlad;
}
get idSpotify(): string {
return this._idSpotify;
}
get email(): string {
return this._email;
}
get createAt(): Date {
return this._createdAt;
}
get name(): string {
return this._name;
}
static empty() {
return new User('', '', '', new Date(), '', require('../assets/images/jul.png'));
}
toString() {
return 'User : ' + this.idFlad + ', ' + this.name + ', ' + this.idSpotify;
}
}

@ -0,0 +1,14 @@
import Music from "../Music";
export default class MusicFactory {
static mapFromSpotifyTrack(jsonMusic: any): Music {
const music = new Music(
jsonMusic.id,
jsonMusic.name,
"",
jsonMusic.album.images[0].url,
jsonMusic.preview_url
);
return music;
}
}

@ -0,0 +1,12 @@
import { User } from "../User";
export class UserFactory {
public static JsonToModel( jsonUser :any ) : User{
return new User(jsonUser.idFlad, jsonUser.idSpotify, jsonUser.email, jsonUser.createdAt, jsonUser.name, jsonUser.imageUrl);
}
public static uptade( jsonUser :any ) : User{
return new User(jsonUser.idFlad, jsonUser.idSpotify, jsonUser.email, jsonUser.createdAt, jsonUser.name, jsonUser.imageUrl);
}
}

@ -4,10 +4,10 @@
"slug": "FLAD",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"icon": "./assets/icons/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/images/splash.png",
"image": "./assets/icons/splash.png",
"resizeMode": "contain",
"backgroundColor": "#1d2129"
},
@ -28,12 +28,12 @@
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"foregroundImage": "./assets/icons/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/images/favicon.png"
"favicon": "./assets/icons/favicon.png"
},
"plugins": [
[

@ -0,0 +1,6 @@
export const GraphicalCharterDark = {
"body": "#141414",
"Text": "white",
"Card": "#232123",
"Line": "#403F3F"
}

@ -0,0 +1,6 @@
export const GraphicalCharterLight = {
"body": "#f2f2f6",
"Text": "black",
"Card": "#fff",
"Line": "#e2e2e3"
}

@ -1,9 +0,0 @@
const Icons = {
discovery: require('./images/icon_discovery.png'),
like: require('./images/icon_like.png'),
dislike: require('./images/icon_dislike.png'),
bookmark : require('./images/icon_bookmark.svg'),
share : require('./images/Vector.png'),
}
export default Icons;

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

@ -0,0 +1,5 @@
export const spotifyCredentials = {
clientId: 'a5cb39302b6e4c64957de1df50742d71',
clientSecret: 'e23db1cd77ee4b589ee99525e282b2e8',
redirectUri: 'Your Redirect URI'
}

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -0,0 +1,3 @@
<svg width="21" height="19" viewBox="0 0 21 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.85029 4.158C1.85877 3.01308 2.68386 2.08791 3.70044 2.08791H17.2996C18.3214 2.08791 19.1498 3.0227 19.1498 4.17582V4.18139L11.7151 8.74107C11.183 9.06737 10.5432 9.07922 10.0019 8.77281L1.85029 4.158ZM1.85022 6.49388V14.8242C1.85022 15.9773 2.67859 16.9121 3.70044 16.9121H17.2996C18.3214 16.9121 19.1498 15.9773 19.1498 14.8242V6.55774L12.5986 10.5756C11.5345 11.2282 10.2548 11.2519 9.17228 10.639L1.85022 6.49388ZM3.70044 0C1.65674 0 0 1.86958 0 4.17582V14.8242C0 17.1304 1.65674 19 3.70044 19H17.2996C19.3433 19 21 17.1304 21 14.8242V4.17582C21 1.86958 19.3433 0 17.2996 0H3.70044Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 757 B

@ -0,0 +1,6 @@
<svg width="20" height="17" viewBox="0 0 20 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_70_52" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.12142 8.63961C1.28389 8.84583 1.46152 9.03956 1.65263 9.21911L8.09971 16.2523C8.8973 17.1224 10.271 17.1161 11.0606 16.2388L17.9089 8.62951L17.8988 8.62763C18.674 7.71412 19.1417 6.53135 19.1417 5.23935C19.1417 2.34574 16.7959 0 13.9023 0C12.0669 0 10.4518 0.943814 9.51612 2.3726C8.56693 1.03408 7.00516 0.16054 5.23935 0.16054C2.34574 0.16054 0 2.50628 0 5.39989C0 6.61822 0.415838 7.73942 1.11332 8.6293L1.11217 8.62951L1.12142 8.63961Z"/>
</mask>
<path d="M1.12142 8.63961L2.69245 7.40192L2.6462 7.34322L2.59571 7.28813L1.12142 8.63961ZM1.65263 9.21911L3.12694 7.86766L3.07648 7.81261L3.02206 7.76148L1.65263 9.21911ZM8.09971 16.2523L9.57402 14.9008L9.57402 14.9008L8.09971 16.2523ZM11.0606 16.2388L12.5472 17.5767L12.5472 17.5767L11.0606 16.2388ZM17.9089 8.62951L19.3955 9.96744L21.7842 7.31332L18.2732 6.66296L17.9089 8.62951ZM17.8988 8.62763L16.3739 7.33352L14.1402 9.96545L17.5345 10.5942L17.8988 8.62763ZM9.51612 2.3726L7.88468 3.5295L9.58181 5.92274L11.1892 3.46835L9.51612 2.3726ZM1.11332 8.6293L1.47049 10.5972L4.73275 10.0051L2.68743 7.39553L1.11332 8.6293ZM1.11217 8.62951L0.755003 6.66166L-2.81154 7.30899L-0.362119 9.98099L1.11217 8.62951ZM-0.449608 9.87729C-0.225421 10.1619 0.0196126 10.4291 0.283205 10.6767L3.02206 7.76148C2.90343 7.65003 2.7932 7.52981 2.69245 7.40192L-0.449608 9.87729ZM9.57402 14.9008L3.12694 7.86766L0.178324 10.5706L6.6254 17.6037L9.57402 14.9008ZM9.57402 14.9008L9.57402 14.9008L6.6254 17.6037C8.22058 19.3439 10.968 19.3314 12.5472 17.5767L9.57402 14.9008ZM16.4223 7.29158L9.57402 14.9008L12.5472 17.5767L19.3955 9.96744L16.4223 7.29158ZM17.5345 10.5942L17.5446 10.5961L18.2732 6.66296L18.263 6.66108L17.5345 10.5942ZM19.4236 9.92175C20.4941 8.6604 21.1417 7.02293 21.1417 5.23935H17.1417C17.1417 6.03977 16.8539 6.76784 16.3739 7.33352L19.4236 9.92175ZM21.1417 5.23935C21.1417 1.24117 17.9005 -2 13.9023 -2V2C15.6914 2 17.1417 3.45031 17.1417 5.23935H21.1417ZM13.9023 -2C11.3642 -2 9.13258 -0.692242 7.843 1.27684L11.1892 3.46835C11.7711 2.57987 12.7695 2 13.9023 2V-2ZM11.1476 1.21569C9.83945 -0.628956 7.68082 -1.83946 5.23935 -1.83946V2.16054C6.32949 2.16054 7.29441 2.69712 7.88468 3.5295L11.1476 1.21569ZM5.23935 -1.83946C1.24117 -1.83946 -2 1.40171 -2 5.39989H2C2 3.61085 3.45031 2.16054 5.23935 2.16054V-1.83946ZM-2 5.39989C-2 7.08121 -1.42438 8.63367 -0.460783 9.86307L2.68743 7.39553C2.25605 6.84516 2 6.15522 2 5.39989H-2ZM1.46933 10.5974L1.47049 10.5972L0.756159 6.66145L0.755003 6.66166L1.46933 10.5974ZM2.59571 7.28813L2.58645 7.27804L-0.362119 9.98099L-0.352865 9.99108L2.59571 7.28813Z" fill="white" mask="url(#path-1-inside-1_70_52)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.88862 0.324035C1.45657 -0.108012 0.756083 -0.108012 0.324036 0.324035C-0.108011 0.756082 -0.108013 1.45657 0.324034 1.88861L5.75471 7.31929L0.324038 12.75C-0.108009 13.182 -0.108009 13.8825 0.324038 14.3145C0.756085 14.7466 1.45657 14.7466 1.88862 14.3145L7.31929 8.88387L12.75 14.3145C13.182 14.7466 13.8825 14.7466 14.3145 14.3145C14.7466 13.8825 14.7466 13.182 14.3145 12.75L8.88387 7.31929L14.3145 1.88863C14.7466 1.45658 14.7466 0.756092 14.3145 0.324045C13.8825 -0.108002 13.182 -0.108004 12.75 0.324043L7.31929 5.75471L1.88862 0.324035Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 715 B

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

@ -0,0 +1,3 @@
<svg width="26" height="18" viewBox="0 0 26 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.837607 0C1.3002 0 1.67521 0.400086 1.67521 0.893617V17.1064C1.67521 17.5999 1.3002 18 0.837607 18C0.375009 18 0 17.5999 0 17.1064V0.893617C0 0.400086 0.375009 0 0.837607 0ZM4.90598 0C5.36858 0 5.74359 0.400086 5.74359 0.893617V17.1064C5.74359 17.5999 5.36858 18 4.90598 18C4.44339 18 4.06838 17.5999 4.06838 17.1064V0.893617C4.06838 0.400086 4.44339 0 4.90598 0ZM9.93162 2.53621V16.2128H12.3248V12.1941C12.62 12.367 12.9968 12.5 13.4468 12.5H14V16.8511C14 17.4856 13.5178 18 12.9231 18H9.33333C8.72658 18 8.25641 17.4699 8.25641 16.8482V1.3651C8.25641 0.464709 9.19429 -0.101539 9.9188 0.39861L13.5077 2.87616C13.813 3.08688 14 3.44946 14 3.84103V6.5H13.4468C12.9968 6.5 12.62 6.63296 12.3248 6.80589V4.18828L9.93162 2.53621ZM13 9.5C13 10.0523 13.2 10.5 13.4468 10.5H14H16.5V13.5532C16.5 13.8 16.9477 14 17.5 14C18.0523 14 18.5 13.8 18.5 13.5532V10.5H21.5532C21.8 10.5 22 10.0523 22 9.5C22 8.94772 21.8 8.5 21.5532 8.5H18.5V5.44681C18.5 5.20004 18.0523 5 17.5 5C16.9477 5 16.5 5.20004 16.5 5.44681V8.5H14H13.4468C13.2 8.5 13 8.94771 13 9.5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -0,0 +1,12 @@
const Icons = {
discovery: require('./icon_discovery.png'),
like: require('./icon_like.png'),
dislike: require('./icon_dislike.png'),
bookmark : require('./icon_bookmark.svg'),
share : require('./Vector.png'),
// riveLike : require('./light_like.riv'),
}
export default Icons;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save