Add a new ServiceSpotify, improved the structure and added new routes to modify user information and favorite music ✅
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
0ce68c7c26
commit
1a0e02bdef
After Width: | Height: | Size: 8.6 KiB |
File diff suppressed because one or more lines are too long
@ -0,0 +1,5 @@
|
||||
export interface IMusic {
|
||||
idMusic: string;
|
||||
idUser: string;
|
||||
date: Date;
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
import { Document } from 'mongoose';
|
||||
import { IMusic } from './Music';
|
||||
|
||||
export default interface User extends Document {
|
||||
email: string;
|
||||
idSpotify: string;
|
||||
tokenSpotify: string;
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
idFlad: string;
|
||||
idSpotify: string;
|
||||
isValidPassword(password: string): Promise<Error | boolean>;
|
||||
image: string;
|
||||
musics_likes: IMusic[];
|
||||
}
|
After Width: | Height: | Size: 1.0 KiB |
@ -1,8 +0,0 @@
|
||||
export default function AdjustSize(Text: string) {
|
||||
const titleLength = Text.length;
|
||||
const minFontSize = 23;
|
||||
const maxFontSize = 48;
|
||||
const fontRatio = 1.1;
|
||||
const fontSize = Math.max(minFontSize, maxFontSize - (titleLength * fontRatio));
|
||||
return fontSize;
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import { View } from "react-native";
|
||||
import Animated, { interpolate, SensorType, useAnimatedSensor, useAnimatedStyle } from "react-native-reanimated";
|
||||
|
||||
const halfPi = Math.PI / 2;
|
||||
|
||||
export default function AnimatedParalax() {
|
||||
const sensor = useAnimatedSensor(SensorType.ROTATION);
|
||||
const styleAniamatedImage = useAnimatedStyle(() => {
|
||||
const { pitch, roll } = sensor.sensor.value;
|
||||
const verticalAxis = interpolate(
|
||||
pitch,
|
||||
[-halfPi, halfPi],
|
||||
[-25, 25]
|
||||
)
|
||||
const horizontalAxis = interpolate(
|
||||
roll,
|
||||
[-halfPi * 2, halfPi * 2],
|
||||
[-35, 35]
|
||||
)
|
||||
return {
|
||||
top: verticalAxis,
|
||||
left: horizontalAxis,
|
||||
};
|
||||
|
||||
})
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: 'flex-start', alignItems: 'center' }}>
|
||||
<Animated.Image
|
||||
source={{
|
||||
uri: spot.sourceUrl,
|
||||
}}
|
||||
style={[
|
||||
{
|
||||
width: 370,
|
||||
height: 370,
|
||||
borderRadius: 24,
|
||||
resizeMode: 'stretch',
|
||||
}, styleAniamatedImage
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
|
||||
);
|
||||
};
|
@ -1,71 +0,0 @@
|
||||
import { View, StyleSheet, Dimensions, Image, TouchableOpacity } from "react-native";
|
||||
import Animated, {
|
||||
Layout,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
} from "react-native-reanimated";
|
||||
import { Feather as Icon } from "@expo/vector-icons";
|
||||
import Music from "../models/Music";
|
||||
import { useState } from "react";
|
||||
|
||||
const { width } = Dimensions.get("window");
|
||||
const SIZE = width / 3;
|
||||
|
||||
interface ArtistProps {
|
||||
artist: Music;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
export default function Artist({ artist, onPress }: ArtistProps) {
|
||||
const source = typeof artist.image === 'string' ? { uri: artist.image } : artist.image;
|
||||
const [selected, setSeleted] = useState(false);
|
||||
const onS = () => {
|
||||
setSeleted(!selected);
|
||||
onPress();
|
||||
};
|
||||
return (
|
||||
<TouchableOpacity onPress={onS}>
|
||||
<Animated.View
|
||||
style={styles.container}
|
||||
entering={ZoomIn}
|
||||
exiting={ZoomOut}
|
||||
layout={Layout.delay(200)}
|
||||
>
|
||||
<View style={styles.card}>
|
||||
<Image source={source} style={styles.image} />
|
||||
{selected && (
|
||||
<View style={styles.cheked}>
|
||||
<Icon name="check-circle" color="black" size={24} />
|
||||
</View>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
</View>
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: SIZE,
|
||||
height: SIZE,
|
||||
padding: 8,
|
||||
},
|
||||
card: {
|
||||
flex: 1,
|
||||
padding: 8,
|
||||
alignItems: "flex-end",
|
||||
},
|
||||
image: {
|
||||
borderRadius: 8,
|
||||
...StyleSheet.absoluteFillObject,
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
},
|
||||
cheked: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 100,
|
||||
alignItems: "center",
|
||||
}
|
||||
});
|
@ -1,38 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { View, Text, Image, Pressable, Linking, Alert } from 'react-native'
|
||||
import Artist from '../models/Artist';
|
||||
|
||||
interface ArtistChipProps {
|
||||
backgroundColor: string;
|
||||
artist: Artist;
|
||||
}
|
||||
|
||||
export default function ArtistChip({ artist }: ArtistChipProps) {
|
||||
const handlePress = useCallback(async () => {
|
||||
const supported = await Linking.canOpenURL(artist.url);
|
||||
|
||||
if (supported) {
|
||||
await Linking.openURL(artist.url);
|
||||
} else {
|
||||
Alert.alert(`Don't know how to open this URL: ${artist.url}`);
|
||||
}
|
||||
}, [artist.url]);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Pressable onPress={handlePress}>
|
||||
<View>
|
||||
<Image
|
||||
source={{
|
||||
uri: artist.image,
|
||||
}}
|
||||
></Image>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text>ii</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
@ -1,34 +0,0 @@
|
||||
import { View, StyleSheet } from 'react-native'
|
||||
import Animated, { lessThan, multiply } from 'react-native-reanimated';
|
||||
import HalfCirlce from './HalfCircle';
|
||||
|
||||
interface CircularProps {
|
||||
background: string,
|
||||
foreground: string,
|
||||
progress: Animated.Value<number>,
|
||||
radius: number;
|
||||
}
|
||||
|
||||
const PI = Math.PI;
|
||||
export default function FladInput({ background, foreground, progress }: CircularProps) {
|
||||
const theta = multiply(progress, 2 * PI);
|
||||
const opacity = lessThan(theta, PI);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={{ zIndex: 1 }}>
|
||||
<HalfCirlce backgroundColor={background} />
|
||||
<Animated.View style={{ ...StyleSheet.absoluteFillObject, transform: [{ rotate: '180%' }], opacity }}>
|
||||
<HalfCirlce backgroundColor={background} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
<View style={{ transform: [{ rotate: '180%' }] }}>
|
||||
<HalfCirlce backgroundColor={background} />
|
||||
<Animated.View>
|
||||
<HalfCirlce backgroundColor={background} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
import { View } from 'react-native'
|
||||
|
||||
interface HalfCirlceProps {
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export default function HalfCirlce({ backgroundColor }: HalfCirlceProps) {
|
||||
return (
|
||||
<View style={{
|
||||
width: RADIUS * 2,
|
||||
height: RADIUS * 2,
|
||||
overflow: "hidden",
|
||||
|
||||
}}>
|
||||
<View style={{ backgroundColor: backgroundColor, width: RADIUS * 2, height: RADIUS * 2, borderRadius: RADIUS, }}>
|
||||
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
import { View, Text, StyleSheet, Image } from 'react-native';
|
||||
|
||||
export interface RenderCellProps {
|
||||
data : any;
|
||||
}
|
||||
export const LittleCard = (props: RenderCellProps) => {
|
||||
return (
|
||||
<View style={styles.similarContainer}>
|
||||
<Image source={{ uri: props.data.cover }} style={styles.similarPoster}></Image>
|
||||
<Text numberOfLines={2} style={styles.similarTitleFilm}>{props.data.name}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
similarContainer: {
|
||||
marginHorizontal: 7
|
||||
},
|
||||
similarTitleFilm: {
|
||||
color: "#DADADA",
|
||||
paddingTop: 5,
|
||||
fontWeight: "300"
|
||||
},
|
||||
similarPoster: {
|
||||
height: 160,
|
||||
width: 160,
|
||||
borderRadius: 16
|
||||
}
|
||||
})
|
@ -1,54 +0,0 @@
|
||||
import Music from "../models/Music";
|
||||
import { Spot } from "../models/Spot";
|
||||
|
||||
export const spotsData: Spot[] = [
|
||||
new Spot("1", new Music("6KNw3UKRp3QRsO7Cf4ASVE",
|
||||
"MOLLY - A COLORS SHOW",
|
||||
"Tame Impala",
|
||||
"https://i.scdn.co/image/ab67616d0000b2734299eb40408fc73ce8bf490a",
|
||||
"https://p.scdn.co/mp3-preview/4faf99856f15e03a09d50b91006efd3205606866?cid=774b29d4f13844c495f206cafdad9c86")
|
||||
),
|
||||
new Spot("2", new Music("5yHoANSze7sGzhn9MUarH3",
|
||||
"Passat",
|
||||
"Silk Sonic, Bruno Mars, Anderson .Paak",
|
||||
"https://i.scdn.co/image/ab67616d0000b273e9df9b5a7df491536c51c922",
|
||||
"https://p.scdn.co/mp3-preview/0bb7472026a00790950fc231fe61963ef7cc867b?cid=774b29d4f13844c495f206cafdad9c86")
|
||||
),
|
||||
new Spot("3", new Music("7suNqxRED5CrwyZSzYC0nT",
|
||||
"Extendo",
|
||||
"Kali Uchis",
|
||||
"https://i.scdn.co/image/ab67616d0000b273b856464c40a062d1723a21f2",
|
||||
"https://p.scdn.co/mp3-preview/5398121f6295965e3c7cad8a6dca5667ba7f4713?cid=774b29d4f13844c495f206cafdad9c86")
|
||||
),
|
||||
new Spot("4", new Music("07JqNLmPUJSlcouGQoJlzq",
|
||||
"Addiction",
|
||||
"Harry Styles",
|
||||
"https://i.scdn.co/image/ab67616d0000b2739297f238f237431d56c67460",
|
||||
"https://p.scdn.co/mp3-preview/33d12e9e5a3dd3394b1649d515912260b01579dd?cid=774b29d4f13844c495f206cafdad9c86")
|
||||
),
|
||||
new Spot("5", new Music("5Ylp75kdffyJSwISRPqEiL",
|
||||
"La Vidéo",
|
||||
"Harry Styles",
|
||||
"https://i.scdn.co/image/ab67616d0000b2738900d48677696015bf325b8b",
|
||||
"https://p.scdn.co/mp3-preview/4fff3f8d76a422f42cea39f001836a3d54937fc4?cid=774b29d4f13844c495f206cafdad9c86")
|
||||
),
|
||||
new Spot("6", new Music("30d0q6kt1BIfwAQUCAfxVQ",
|
||||
"Calme toi",
|
||||
"Kerchack",
|
||||
"https://i.scdn.co/image/ab67616d0000b273b4f73fb5c5ea299c7ebfbf60",
|
||||
"https://p.scdn.co/mp3-preview/5de1103b9528c1e47e03d32b0aa5dbfe797191a2?cid=774b29d4f13844c495f206cafdad9c86")
|
||||
),
|
||||
new Spot("7", new Music("7IXQrRgmHxWYWitSlyFY7z",
|
||||
"Peur (feat. Ziak)",
|
||||
"Ziak",
|
||||
"https://i.scdn.co/image/ab67616d0000b273b533a3a5625bc6ce1be56a2e",
|
||||
"https://p.scdn.co/mp3-preview/2c613f31b11375980aba80a5b535bf87ddb6211b?cid=774b29d4f13844c495f206cafdad9c86")
|
||||
),
|
||||
new Spot("8", new Music("7wBoSW48q4ZFe8qSdozqqi",
|
||||
"Blue",
|
||||
"Kerchack",
|
||||
"https://i.scdn.co/image/ab67616d0000b273cc4e66af40292c9d92146909",
|
||||
"https://p.scdn.co/mp3-preview/401b51374dd3f2a15466a0b415a9ac7d2114a54b?cid=774b29d4f13844c495f206cafdad9c86")
|
||||
)
|
||||
|
||||
];
|
@ -1,7 +0,0 @@
|
||||
Object.defineProperty(exports, '__esModule', {value: true});
|
||||
require('./mqttLib');
|
||||
const storage = require('./storage');
|
||||
function initialize() {
|
||||
global.localStorage = storage;
|
||||
}
|
||||
exports.default = initialize;
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +0,0 @@
|
||||
const storage = {
|
||||
setItem: (key, item) => {
|
||||
storage[key] = item;
|
||||
},
|
||||
getItem: key => storage[key],
|
||||
removeItem: key => {
|
||||
delete storage[key];
|
||||
},
|
||||
};
|
||||
export default storage;
|
@ -1,15 +1,45 @@
|
||||
export default class Artist {
|
||||
private id: string;
|
||||
private name: string;
|
||||
private _id: string;
|
||||
private _name: string;
|
||||
private _image: string;
|
||||
private _url: string;
|
||||
|
||||
constructor(id: string, name: string, url: string) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
constructor(id: string, name: string, image: string, url: string) {
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this._image = image;
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
set id(value: string) {
|
||||
this._id = value;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
set name(value: string) {
|
||||
this._name = value;
|
||||
}
|
||||
|
||||
get image(): string {
|
||||
return this._image;
|
||||
}
|
||||
|
||||
set image(value: string) {
|
||||
this._image = value;
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
return this.url;
|
||||
return this._url;
|
||||
}
|
||||
|
||||
set url(value: string) {
|
||||
this._url = value;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import EmptyMusicService from "../services/EmptyMusicService";
|
||||
import IMusicService from "../services/musics/interfaces/IMusicService";
|
||||
import SpotifyService from "../services/musics/spotify/SpotifyService";
|
||||
|
||||
export class MusicServiceProvider {
|
||||
static musicService: IMusicService;
|
||||
|
||||
static initSpotify(refreshToken: string, idSpotify: string) {
|
||||
this.musicService = new SpotifyService(refreshToken, idSpotify);
|
||||
}
|
||||
|
||||
static resetService() {
|
||||
this.musicService = new EmptyMusicService();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
export class Person {
|
||||
private _id: string;
|
||||
private _name: string;
|
||||
public image: string;
|
||||
|
||||
constructor(id: string, idSpotify: string, name: string, email: string, creationDate: Date, image: string) {
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import Artist from "../Artist";
|
||||
|
||||
export default class ArtistMapper {
|
||||
static toModel(artist: any): Artist {
|
||||
return new Artist(artist.id, artist.name, (artist?.images?.[0]?.url ?? ""), artist.external_urls.spotify);
|
||||
}
|
||||
}
|
@ -1,12 +1,18 @@
|
||||
import Music from "../Music";
|
||||
import ArtistMapper from "./ArtistMapper";
|
||||
|
||||
export default class MusicMapper {
|
||||
static toModel(music: any): Music {
|
||||
const artists = music.artists.map((artist: any) => ArtistMapper.toModel(artist));
|
||||
return new Music(
|
||||
music.id,
|
||||
music.name,
|
||||
music.artists[0].name,
|
||||
music.external_urls.spotify,
|
||||
artists,
|
||||
music.album.images[0].url,
|
||||
music.album.release_date.split('-')[0],
|
||||
music.duration_ms / 1000,
|
||||
music.explicit,
|
||||
music.preview_url
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
import { Spot } from "../Spot";
|
||||
|
||||
export class SpotMapper {
|
||||
public static toModel(spot: any): Spot {
|
||||
return new Spot(spot.idUser, spot.music, new Date(spot.date));
|
||||
}
|
||||
}
|
@ -1,23 +1,21 @@
|
||||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import ConversationScreen from '../screens/ConversationScreen'
|
||||
import ChatScreen from '../screens/ChatScreen';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
|
||||
export default function MessagingNavigation() {
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
return (
|
||||
<Stack.Navigator initialRouteName="Conversation" screenOptions={{ gestureEnabled: true, headerShown: false, cardOverlayEnabled: true, cardStyle: { backgroundColor: "transparent" } }}>
|
||||
<Stack.Navigator initialRouteName="Conversation" >
|
||||
<Stack.Screen
|
||||
name="Conversation"
|
||||
component={ConversationScreen}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Chat"
|
||||
component={ChatScreen}
|
||||
options={{
|
||||
headerShown: true
|
||||
}}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
)
|
||||
|
@ -1,27 +1,21 @@
|
||||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import SpotScreen from '../screens/SpotScreen'
|
||||
import DetailScreen from '../screens/DetailScreen';
|
||||
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
|
||||
export default function SpotNavigation() {
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{
|
||||
gestureEnabled: false,
|
||||
headerShown: false,
|
||||
cardOverlayEnabled: true,
|
||||
|
||||
}}
|
||||
>
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen
|
||||
name="Spot"
|
||||
component={SpotScreen}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Detail"
|
||||
component={DetailScreen}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
)
|
||||
|
@ -0,0 +1,81 @@
|
||||
import axios from "axios";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { Spot } from "../../models/Spot";
|
||||
import configs from "../../constants/config";
|
||||
import { MusicServiceProvider } from "../../models/MusicServiceProvider";
|
||||
import { setFavoriteMusic, setUserCurrentMusic } from "../actions/appActions";
|
||||
import { setAccessError, setErrorEmptyMusic } from "../actions/userActions";
|
||||
import { SpotMapper } from "../../models/mapper/SpotMapper";
|
||||
|
||||
export const getUserCurrentMusic = () => {
|
||||
//@ts-ignore
|
||||
return async dispatch => {
|
||||
try {
|
||||
let idTrack;
|
||||
const resp = await MusicServiceProvider.musicService.getCurrentlyPlayingMusic();
|
||||
if (resp === null) {
|
||||
idTrack = await MusicServiceProvider.musicService.getRecentlyPlayedMusic();
|
||||
if (idTrack === null) {
|
||||
dispatch(setErrorEmptyMusic(true));
|
||||
dispatch(setUserCurrentMusic(null));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
idTrack = resp;
|
||||
}
|
||||
const music = await MusicServiceProvider.musicService.getMusicById(idTrack);
|
||||
dispatch(setUserCurrentMusic(music))
|
||||
} catch (error: any) {
|
||||
console.error("Error retrieving music currently listened : " + error);
|
||||
switch (error.response.status) {
|
||||
case 403:
|
||||
dispatch(setAccessError(true));
|
||||
break;
|
||||
default:
|
||||
dispatch(setAccessError(true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const addFavoriteMusic = (spot: Spot) => {
|
||||
//@ts-ignore
|
||||
return async dispatch => {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export const getFavoriteMusic = () => {
|
||||
//@ts-ignore
|
||||
return async dispatch => {
|
||||
try {
|
||||
let token: string | null = await SecureStore.getItemAsync(configs.key);
|
||||
const headers = {
|
||||
'Authorization': 'Bearer ' + token
|
||||
};
|
||||
const resp = await axios.get(
|
||||
configs.API_URL + '/user/musics',
|
||||
{ headers }
|
||||
)
|
||||
|
||||
const musicIds = resp.data.musics.map((music: any) => music.idMusic);
|
||||
const musics = await MusicServiceProvider.musicService.getMusicsWithIds(musicIds);
|
||||
const result = resp.data.musics
|
||||
.filter((music: any) => musics.some((m: any) => m.id === music.idMusic))
|
||||
.map((music: any) => {
|
||||
const matchingMusic = musics.find((m: any) => m.id === music.idMusic);
|
||||
return {
|
||||
...music,
|
||||
music: matchingMusic,
|
||||
};
|
||||
});
|
||||
|
||||
dispatch(setFavoriteMusic(result.map((item: any) => SpotMapper.toModel(item))));
|
||||
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
dispatch(setAccessError(true));
|
||||
}
|
||||
};
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { Spot } from "../../models/Spot";
|
||||
|
||||
export const likeSpot = async (spot: Spot) => {
|
||||
return async (dispatch) => {
|
||||
axios.post("osdj").then(responce => {
|
||||
if (responce.status == 200) {
|
||||
dispatch(true);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.log("something goes wrong while searching : " + error);
|
||||
;
|
||||
})
|
||||
|
||||
|
||||
};
|
||||
}
|
@ -1,69 +1,9 @@
|
||||
import axios from "axios";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { Spot } from "../../models/Spot";
|
||||
import SpotifyService from "../../services/spotify/spotify.service";
|
||||
import { setSpotList, setUserCurrentMusic } from "../actions/spotActions";
|
||||
const key = 'userToken';
|
||||
|
||||
export type CreateSpotReqBody = {
|
||||
id: string;
|
||||
name: string;
|
||||
artist: string;
|
||||
linkCover: string;
|
||||
user: string;
|
||||
}
|
||||
export const getSpotList = (spotsData : Record<string, string> , resuestHandler: SpotifyService) => {
|
||||
export const getSpotList = () => {
|
||||
//@ts-ignore
|
||||
return async dispatch => {
|
||||
try {
|
||||
//@ts-ignore
|
||||
if (spotsData) {
|
||||
|
||||
const spots = await Promise.all(
|
||||
Object.entries(spotsData).map(async ([userId, value]) => {
|
||||
const completeMusic = await resuestHandler.getMusicById(value);
|
||||
return new Spot(userId, completeMusic);
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(setSpotList(spots)); // our action is called here
|
||||
} else {
|
||||
console.log('Login Failed', 'Username or Password is incorrect');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log('Error---------', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
export const getCurrentUserMusic = (resuestHandler: SpotifyService) => {
|
||||
//@ts-ignore
|
||||
return async dispatch => {
|
||||
try {
|
||||
//@ts-ignore
|
||||
var currentTrackResponse = await resuestHandler.getUserCurrentMusic();
|
||||
if (!currentTrackResponse) {
|
||||
const recentlyTrackResponse = await resuestHandler.getUserRecentlyPlayedMusic();
|
||||
if (!recentlyTrackResponse) {
|
||||
throw new Error;
|
||||
} else {
|
||||
currentTrackResponse = recentlyTrackResponse;
|
||||
}
|
||||
}
|
||||
const completeMusic = await resuestHandler.getMusicById(currentTrackResponse);
|
||||
if(!completeMusic){
|
||||
return;
|
||||
}
|
||||
dispatch(setUserCurrentMusic(completeMusic));
|
||||
}
|
||||
catch (error) {
|
||||
console.log('Error---------', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
export const searchMusic = async (resuestHandler: SpotifyService, search: string) => {
|
||||
|
||||
return async (dispatch) => {
|
||||
return resuestHandler.searchMusic(search).then(musics => dispatch((musics))).catch(err => console.log("something goes wrong while searching : " + err));
|
||||
};
|
||||
}
|
||||
}
|
@ -1,8 +1,72 @@
|
||||
import { setDarkMode } from "../actions/userActions";
|
||||
import axios from "axios";
|
||||
import configs from "../../constants/config";
|
||||
import { setDarkMode, setErrorNetwork, setErrorUpdateMessage, userLogin } from "../actions/userActions";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { UserMapper } from "../../models/mapper/UserMapper";
|
||||
|
||||
export const darkMode = (value: boolean) => {
|
||||
//@ts-ignore
|
||||
return async dispatch => {
|
||||
dispatch(setDarkMode(value));
|
||||
}
|
||||
}
|
||||
|
||||
export const setName = (name: string) => {
|
||||
//@ts-ignore
|
||||
return async dispatch => {
|
||||
try {
|
||||
let token: string | null = await SecureStore.getItemAsync(configs.key);
|
||||
const headers = {
|
||||
'Authorization': 'Bearer ' + token
|
||||
};
|
||||
await axios.put(configs.API_URL + '/user/name', { name }, { headers });
|
||||
|
||||
const user = await axios.get(
|
||||
configs.API_URL + '/user',
|
||||
{ headers }
|
||||
)
|
||||
dispatch(userLogin(UserMapper.toModel(user.data.data)));
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Error : " + error.message);
|
||||
switch (error.response.status) {
|
||||
case 409:
|
||||
dispatch(setErrorUpdateMessage("Nom déjà utilisé."))
|
||||
break;
|
||||
default:
|
||||
dispatch(setErrorNetwork(true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const setMail = (email: string) => {
|
||||
//@ts-ignore
|
||||
return async dispatch => {
|
||||
try {
|
||||
let token: string | null = await SecureStore.getItemAsync(configs.key);
|
||||
const headers = {
|
||||
'Authorization': 'Bearer ' + token
|
||||
};
|
||||
|
||||
await axios.put(configs.API_URL + '/user/email', { email }, { headers });
|
||||
|
||||
const user = await axios.get(
|
||||
configs.API_URL + '/user',
|
||||
{ headers }
|
||||
)
|
||||
dispatch(userLogin(UserMapper.toModel(user.data.data)));
|
||||
} catch (error: any) {
|
||||
console.error("Error : " + error.message);
|
||||
switch (error.response.status) {
|
||||
case 409:
|
||||
dispatch(setErrorUpdateMessage("Email déjà utilisé."))
|
||||
break;
|
||||
default:
|
||||
dispatch(setErrorNetwork(true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
export const playlistTypes = {
|
||||
FETCH_USER_PLAYLISTS: 'FETCH_SPOT',
|
||||
SAVE_IN_FLAD_PLAYLIST: 'SAVE_IN_FLAD_PLAYLIST',
|
||||
FETCH_FLAD_PLAYLIST: 'FETCH_SPOT',
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
export const spotTypes = {
|
||||
FETCH_SPOT: 'FETCH_SPOT',
|
||||
ADD_SPOT_MOCK: 'ADD_SPOT_MOCK',
|
||||
REMOVE_SPOT: 'REMOVE_SPOT',
|
||||
REMOVE_SPOT: 'REMOVE_SPOT'
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
export const spotifyTypes = {
|
||||
GET_USER_CURRENT_MUSIC: 'GET_USER_CURRENT_MUSIC',
|
||||
GET_USER_CURRENT_MUSIC: 'GET_USER_CURRENT_MUSIC'
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
export const userTypes = {
|
||||
RESTORE_TOKEN: "RESTORE_TOKEN",
|
||||
LOGIN: 'LOGIN',
|
||||
SIGNUP: 'SIGNUP',
|
||||
USER_LOGOUT: 'USER_LOGOUT',
|
||||
SAVE_SPOTIFY: 'SAVE_SPOTIFY',
|
||||
UPDATE_USER: 'UPDATE_USER',
|
||||
ERROR_LOGIN: "ERROR_LOGIN",
|
||||
ERROR_SIGNUP: "ERROR_SIGNUP",
|
||||
DARK_MODE: "DARK_MODE",
|
||||
ERROR_NETWORK: "ERROR_NETWORK"
|
||||
SET_ERROR_LOGIN: "SET_ERROR_LOGIN",
|
||||
SET_ERROR_SIGNUP: "SET_ERROR_SIGNUP",
|
||||
SET_DARK_MODE: "SET_DARK_MODE",
|
||||
SET_ERROR_NETWORK: "SET_ERROR_NETWORK",
|
||||
SET_ERROR_ACCESS: "SET_ERROR_ACCESS",
|
||||
SET_ERROR_EMPTY_MUSIC: "SET_ERROR_EMPTY_MUSIC",
|
||||
SET_ERROR_UPDATE: "SET_ERROR_UPDATE",
|
||||
SET_ERROR_UPDATE_MESSAGE: "SET_ERROR_UPDATE_MESSAGE"
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import Music from "../models/Music";
|
||||
import IMusicService from "./musics/interfaces/IMusicService";
|
||||
|
||||
export default class EmptyMusicService implements IMusicService {
|
||||
getMusicById(id: string): Promise<Music> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
getRecentlyPlayedMusic(): Promise<string | null> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
getCurrentlyPlayingMusic(): Promise<string | null> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
getMusicsWithIds(ids: string[]): Promise<Music[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
getMusicsWithName(name: string): Promise<Music[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
addToPlaylist(idTrack: string): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
getSimilarTracks(idTrack: string): Promise<Music[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import Music from "../../../models/Music";
|
||||
|
||||
export default interface IMusicService {
|
||||
getMusicById(id: string): Promise<Music>;
|
||||
getRecentlyPlayedMusic(): Promise<string | null>;
|
||||
getCurrentlyPlayingMusic(): Promise<string | null>;
|
||||
getMusicsWithIds(ids: string[]): Promise<Music[]>;
|
||||
getMusicsWithName(name: string): Promise<Music[]>;
|
||||
addToPlaylist(idTrack: string): void;
|
||||
getSimilarTracks(idTrack: string): Promise<Music[]>;
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
import axios from "axios";
|
||||
import Music from "../../../models/Music";
|
||||
import IMusicService from "../interfaces/IMusicService";
|
||||
import TokenSpotify from "./TokenSpotify";
|
||||
import MusicMapper from "../../../models/mapper/MusicMapper";
|
||||
|
||||
export default class SpotifyService implements IMusicService {
|
||||
private readonly API_URL = "https://api.spotify.com/v1";
|
||||
private readonly PLAYLIST_NAME = "Flad's discovery";
|
||||
private _token: TokenSpotify;
|
||||
private _idSpotify: string;
|
||||
|
||||
constructor(refreshToken: string, idSpotify: string) {
|
||||
this._token = new TokenSpotify(refreshToken);
|
||||
this._idSpotify = idSpotify;
|
||||
}
|
||||
|
||||
async getMusicById(id: string): Promise<Music> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
try {
|
||||
const response = await axios.get(`${this.API_URL}/tracks/${id}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`
|
||||
},
|
||||
});
|
||||
return MusicMapper.toModel(response.data)
|
||||
} catch (error: any) {
|
||||
console.log("Error retrieving music information : " + error)
|
||||
throw new Error("Error retrieving music information : " + error)
|
||||
}
|
||||
}
|
||||
|
||||
async getRecentlyPlayedMusic(limit: number = 1): Promise<string | null> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
|
||||
const response = await axios.get(`${this.API_URL}/me/player/recently-played?limit=${limit}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`
|
||||
},
|
||||
});
|
||||
if (response.data.items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return response.data.items[0].track.id
|
||||
}
|
||||
|
||||
async getCurrentlyPlayingMusic(): Promise<string | null> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
|
||||
const response = await axios.get(`${this.API_URL}/me/player/currently-playing`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`
|
||||
},
|
||||
});
|
||||
|
||||
if (response.data.item === undefined) {
|
||||
return null;
|
||||
}
|
||||
return response.data.item.id
|
||||
}
|
||||
|
||||
async getMusicsWithIds(ids: string[]): Promise<Music[]> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
var url = `${this.API_URL}/tracks?market=FR&ids=`;
|
||||
if (ids.length == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
url += ids.join('%2C');
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`
|
||||
},
|
||||
});
|
||||
const tracksData = response.data.tracks.filter((musicData: any) => musicData !== null);
|
||||
return tracksData.map((musicData: any) => MusicMapper.toModel(musicData));
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getMusicsWithName(name: string, limit: number = 20, offset: number = 0): Promise<Music[]> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${this.API_URL}/search?q=track%3A${name}&type=track&market=fr&limit=${limit}&offset=${offset}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`
|
||||
},
|
||||
});
|
||||
return response.data.tracks.items.map((musicData: any) => MusicMapper.toModel(musicData));
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async _getPlaylistId(): Promise<string> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${access_token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
const response = await axios.get(`${this.API_URL}/me/playlists?limit=50`, { headers });
|
||||
const fladPlaylist = response.data.items.filter((playlist: any) => playlist.name === this.PLAYLIST_NAME);
|
||||
|
||||
if (fladPlaylist.length >= 1) {
|
||||
return fladPlaylist[0].id;
|
||||
}
|
||||
|
||||
return await this._createPlaylist();
|
||||
}
|
||||
|
||||
async _createPlaylist(): Promise<string> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
const data = {
|
||||
name: this.PLAYLIST_NAME,
|
||||
description: 'Retrouvez toutes vos découvertes faites sur FladMusic 🎵',
|
||||
public: true
|
||||
};
|
||||
var headers = {
|
||||
'Authorization': `Bearer ${access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
};
|
||||
const response = await axios.post(`${this.API_URL}/users/${this._idSpotify}/playlists`, data, { headers });
|
||||
|
||||
return response.data.id;
|
||||
}
|
||||
|
||||
async _isInPlaylist(idTrack: string, idPlaylist: string): Promise<Boolean> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
const response = await axios.get(`${this.API_URL}/playlists/${idPlaylist}/tracks?limit=100`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`
|
||||
},
|
||||
});
|
||||
var nbTracks = response.data.items.filter((item: any) => item.track.id === idTrack).length;
|
||||
|
||||
return (nbTracks >= 1) ? true : false;
|
||||
}
|
||||
|
||||
async addToPlaylist(idTrack: string): Promise<void> {
|
||||
var idPlaylist = await this._getPlaylistId();
|
||||
|
||||
if (await this._isInPlaylist(idTrack, idPlaylist)) {
|
||||
return;
|
||||
}
|
||||
const access_token = await this._token.getAccessToken();
|
||||
|
||||
const data = {
|
||||
uris: [`spotify:track:${idTrack}`],
|
||||
position: 0
|
||||
};
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
await axios.post(`${this.API_URL}/playlists/${idPlaylist}/tracks`, data, { headers })
|
||||
.then(response => {
|
||||
console.log('Song successfully added to playlist.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error adding song to playlist: ', error);
|
||||
});
|
||||
}
|
||||
|
||||
async getSimilarTracks(idTrack: string, limit: number = 20, offset: number = 0): Promise<Music[]> {
|
||||
const access_token = await this._token.getAccessToken();
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${this.API_URL}/recommendations?limit=${limit}&offset=${offset}&seed_tracks=${idTrack}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`
|
||||
},
|
||||
});
|
||||
return response.data.tracks.map((musicData: any) => MusicMapper.toModel(musicData));
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import axios from "axios";
|
||||
import configs from "../../../constants/config";
|
||||
|
||||
export default class TokenSpotify {
|
||||
private _accessToken: string = '';
|
||||
private _refreshToken: string;
|
||||
private _tokenEnd: Date;
|
||||
|
||||
constructor(refreshToken: string) {
|
||||
this._refreshToken = refreshToken;
|
||||
this._tokenEnd = new Date();
|
||||
this._tokenEnd.setSeconds(this._tokenEnd.getSeconds() - 10);
|
||||
}
|
||||
|
||||
async getAccessToken(): Promise<string> {
|
||||
if (this._isTokenExpired()) {
|
||||
await this._getRefreshToken();
|
||||
}
|
||||
return this._accessToken;
|
||||
}
|
||||
|
||||
private async _getRefreshToken(): Promise<void> {
|
||||
|
||||
const response = await axios.get(
|
||||
configs.API_URL + '/spotify/refresh?refresh_token=' + this._refreshToken
|
||||
)
|
||||
if (response.status === 200) {
|
||||
const responseData = await response.data;
|
||||
this._accessToken = responseData.access_token;
|
||||
this._tokenEnd = new Date();
|
||||
this._tokenEnd.setSeconds(this._tokenEnd.getSeconds() + responseData.expires_in)
|
||||
} else {
|
||||
console.log(`Error refreshing token: ${response.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
private _isTokenExpired(): boolean {
|
||||
return new Date() > this._tokenEnd;
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
interface IspotifyAuthHandler {
|
||||
abstract async getUserToken(): Promise<string>;
|
||||
abstract async getUserTokenFromRefreshToken(): Promise<string>;
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
import axios from "axios";
|
||||
import MusicMapper from "../../model/mapper/MusicMapper";
|
||||
import Music from "../../model/Music";
|
||||
import { FetchOptions, RequestHandler } from "./spotifyRequestHandler/utils";
|
||||
|
||||
export class MusicMinimal {
|
||||
public id: string;
|
||||
public title: string;
|
||||
public image: string;
|
||||
|
||||
constructor(id: string, title: string, bio: string, image: string, trackPreviewUrl: string) {
|
||||
this.title = title;
|
||||
this.image = image;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
export default class SpotifyService implements IspotifyService {
|
||||
private readonly API_URL = API_URL;
|
||||
private spotifyRequestHandler = new RequestHandler();
|
||||
public token: string;
|
||||
|
||||
|
||||
constructor(token: string) {
|
||||
this.token = token;
|
||||
}
|
||||
public async getMusicById(idMusic: string): Promise<Music| null> {
|
||||
let requestData: string = '/tracks/' + idMusic;
|
||||
const respMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, undefined, this.token);
|
||||
if (respMusic.status != 200) {
|
||||
return null;
|
||||
}
|
||||
console.log(respMusic.data.artists[0].id);
|
||||
|
||||
return MusicFactory.mapFromSpotifyTrack(respMusic.data);
|
||||
}
|
||||
|
||||
public async getUserCurrentMusic(): Promise<string | null> {
|
||||
let requestData: string = '/me/player/currently-playing';
|
||||
const respMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, undefined, this.token);
|
||||
if (respMusic.status != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return respMusic.data.item.id;
|
||||
}
|
||||
|
||||
public async getUserRecentlyPlayedMusic(): Promise<string | null> {
|
||||
let requestData: string = '/me/player/recently-played';
|
||||
const respMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, undefined, this.token);
|
||||
if (respMusic.status != 200) {
|
||||
return null;
|
||||
}
|
||||
if (respMusic.data.items.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
return respMusic.data.items[0].track.id;
|
||||
}
|
||||
|
||||
public async playMusic(idMusic: string): Promise<void> {
|
||||
const fetchOptions: FetchOptions = {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
uris: [`spotify:track:${idMusic}`],
|
||||
position_ms: 0
|
||||
}
|
||||
};
|
||||
throw new Error("not Implemented")
|
||||
}
|
||||
|
||||
public async searchMusic(text: string): Promise<Music[]> {
|
||||
const requestData: string = '/search';
|
||||
const fetchOptions: FetchOptions = {
|
||||
params: {
|
||||
q: text,
|
||||
type: 'track'
|
||||
}
|
||||
};
|
||||
const respMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, fetchOptions, this.token);
|
||||
|
||||
const tracksData = respMusic?.data?.tracks?.items;
|
||||
if (!tracksData || !Array.isArray(tracksData)) {
|
||||
return [];
|
||||
}
|
||||
const tracks = tracksData.map((trackData: any) => {
|
||||
return MusicFactory.mapFromSpotifyTrack(trackData)
|
||||
});
|
||||
return tracks;
|
||||
}
|
||||
// tempo version
|
||||
public async getMusicMoreDetails(idMusic: string): Promise<string> {
|
||||
let requestData: string = '/audio-features/' + idMusic;
|
||||
const respMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, undefined, this.token);
|
||||
return respMusic.data.audio_features.tempo;
|
||||
}
|
||||
|
||||
public async getRelatedArtist(idArtist: string): Promise<string> {
|
||||
let requestData: string = '/artists/' + idArtist + '/related-artists';
|
||||
const respMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, undefined, this.token);
|
||||
return respMusic.data.audio_features.tempo;
|
||||
}
|
||||
|
||||
public async getArtistTopTracks(idArtist: string): Promise<string> {
|
||||
let requestData: string = '/artists/' + idArtist + '/top-tracks';
|
||||
const respMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, undefined, this.token);
|
||||
return respMusic.data.audio_features.tempo;
|
||||
}
|
||||
|
||||
public async addItemToPlayList(playlistId: string, idMusic: string): Promise<void> {
|
||||
let requestData: string = '/playlists/' + playlistId + '/tracks';
|
||||
const fetchOptions: FetchOptions = {
|
||||
method: 'POST',
|
||||
body: {
|
||||
uris: [`spotify:track:${idMusic}`]
|
||||
}
|
||||
};
|
||||
const respMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, fetchOptions, this.token);
|
||||
console.log(respMusic.data);
|
||||
}
|
||||
|
||||
public async getSimilarTrack(musicId: string, limit: number = 1, market?: string): Promise<Music[]> {
|
||||
const requestData: string = '/recommendations/' +
|
||||
'?limit=' + limit +
|
||||
'&market=FR' +
|
||||
'&seed_tracks=' + musicId;
|
||||
let respSimilarMusic;
|
||||
try {
|
||||
respSimilarMusic = await this.spotifyRequestHandler.spotifyFetch(requestData, {}, this.token);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
if (!respSimilarMusic || !respSimilarMusic.data.tracks) {
|
||||
return [];
|
||||
}
|
||||
const similars: Music[] = await Promise.all(
|
||||
respSimilarMusic.data.tracks.map(async (trackData: any) => {
|
||||
if (trackData.id != undefined) {
|
||||
const data = await this.getMusicById(trackData.id);
|
||||
return data;
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
)
|
||||
return similars.filter((music: Music | undefined) => !!music) as Music[];
|
||||
}
|
||||
|
||||
|
||||
async getSpotifyCredentials() {
|
||||
const res = await axios.get(this.API_URL)
|
||||
const spotifyCredentials = res.data;
|
||||
return spotifyCredentials
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
interface IspotifyService {
|
||||
getMusicById(idMusic: string): Promise<any>;
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import { MY_SECURE_AUTH_STATE_KEY, MY_SECURE_AUTH_STATE_KEY_REFRESH } from "../../../screens/RegisterScreen";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
|
||||
export type Methods = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
|
||||
|
||||
export interface FetchOptions {
|
||||
/** The headers that will be applied to the request when sent. */
|
||||
headers?: Record<string, string>;
|
||||
/** The type of HTTP method being used. */
|
||||
method?: Methods;
|
||||
/** Parameters used for search queries.*/
|
||||
params?: Record<string, any>;
|
||||
/**If present, this refers to the JSON data that will be included in the request body. */
|
||||
body?: Record<string, string | boolean | number | (string | boolean | number)[]>;
|
||||
}
|
||||
|
||||
export class RequestHandler {
|
||||
private _version: `v${number}` = 'v1';
|
||||
|
||||
get version(): string {
|
||||
return this._version;
|
||||
}
|
||||
public async spotifyFetch(url: string, options: FetchOptions = {}, token: string): Promise<AxiosResponse<any, any>> {
|
||||
try {
|
||||
const resp = await axios({
|
||||
url: `https://api.spotify.com/${this.version}${url}`,
|
||||
method: options.method || 'GET',
|
||||
params: options.params,
|
||||
headers: {
|
||||
Authorization: "Bearer " + token,
|
||||
Accept: 'application/json',
|
||||
...options.headers
|
||||
},
|
||||
data: options.body
|
||||
});
|
||||
|
||||
return resp;
|
||||
}
|
||||
catch(error : any){
|
||||
const errorMessage = error.response.data?.error?.message;
|
||||
if (errorMessage === "Invalid access token" || errorMessage === "The access token expired" ) {
|
||||
console.log('### Warning ! ### try refresh token Request Handler ' +error);
|
||||
|
||||
const newToken = await this.refreshToken();
|
||||
console.log('### GOOD Warning ! ### new token Request Handler ' +newToken);
|
||||
// Mettez à jour le token dans le store ou le reducer ici
|
||||
return this.spotifyFetch(url, options, newToken);
|
||||
}
|
||||
else {
|
||||
console.log('### Error ! ### while fetching Data in the SPotify Request Handler ' +error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
private async refreshToken(): Promise<string> {
|
||||
|
||||
// Faites une demande à votre API pour obtenir un nouveau token Spotify
|
||||
let refreshToken = await SecureStore.getItemAsync(MY_SECURE_AUTH_STATE_KEY_REFRESH);
|
||||
console.log('refresh token : ' + refreshToken);
|
||||
const response = await axios.get(`https://flad-api-production.up.railway.app/api/spotify/refresh?refresh_token=${refreshToken}`);
|
||||
// Renvoie le nouveau token
|
||||
const {
|
||||
access_token : access_token,
|
||||
refresh_token: refresh_token,
|
||||
} = response.data as SpotifyAuthResponse
|
||||
console.log('new access token : ' + access_token);
|
||||
console.log('new refresh token : ' + refresh_token);
|
||||
await SecureStore.setItemAsync(MY_SECURE_AUTH_STATE_KEY, access_token);
|
||||
return access_token;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface SpotifyAuthResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import initialize from '../lib';
|
||||
|
||||
initialize();
|
||||
class MqttClient {
|
||||
|
||||
constructor() {
|
||||
const clientId = 'ReactNativeMqtt';
|
||||
this.client = new Paho.MQTT.Client('127.0.0.1', 9001, clientId);
|
||||
this.client.onMessageArrived = this.onMessageArrived;
|
||||
this.callbacks = {};
|
||||
this.onSuccessHandler = undefined;
|
||||
this.onConnectionLostHandler = undefined;
|
||||
this.isConnected = false;
|
||||
}
|
||||
|
||||
onConnect = (onSuccessHandler, onConnectionLostHandler) => {
|
||||
this.onSuccessHandler = onSuccessHandler;
|
||||
this.onConnectionLostHandler = onConnectionLostHandler;
|
||||
this.client.onConnectionLost = () => {
|
||||
this.isConnected = false;
|
||||
onConnectionLostHandler();
|
||||
};
|
||||
|
||||
this.client.connect({
|
||||
timeout: 10,
|
||||
onSuccess: () => {
|
||||
this.isConnected = true;
|
||||
onSuccessHandler();
|
||||
},
|
||||
useSSL: false,
|
||||
onFailure: this.onError,
|
||||
reconnect: true,
|
||||
keepAliveInterval: 20,
|
||||
cleanSession: true,
|
||||
});
|
||||
};
|
||||
|
||||
onError = ({errorMessage}) => {
|
||||
console.log(errorMessage);
|
||||
this.isConnected = false;
|
||||
Alert.alert('Failed', 'Failed to connect to MQTT', [
|
||||
{
|
||||
text: 'Cancel',
|
||||
onPress: () => console.log('Cancel Pressed'),
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Try Again',
|
||||
onPress: () =>
|
||||
this.onConnect(
|
||||
this.onSuccessHandler,
|
||||
this.onConnectionLostHandler,
|
||||
),
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
onMessageArrived = message => {
|
||||
const {payloadString, topic} = message;
|
||||
console.log('onMessageArrived:', payloadString);
|
||||
this.callbacks[topic](payloadString);
|
||||
};
|
||||
|
||||
onPublish = (topic, message) => {
|
||||
this.client.publish(topic, message);
|
||||
};
|
||||
|
||||
onSubscribe = (topic, callback) => {
|
||||
this.callbacks[topic] = callback;
|
||||
this.client.subscribe(topic);
|
||||
};
|
||||
|
||||
unsubscribe = topic => {
|
||||
delete this.callbacks[topic];
|
||||
this.client.unsubscribe(topic);
|
||||
};
|
||||
}
|
||||
|
||||
let client = new MqttClient();
|
||||
export {client as MqttClient};
|
Loading…
Reference in new issue