Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0db8d49618 | |||
| 4923fc72b2 |
@@ -51,7 +51,6 @@ import { PasswordResetDto } from "./dto/password_reset.dto ";
|
||||
import { mapInclude } from "src/utils/include";
|
||||
import { SongController } from "src/song/song.controller";
|
||||
import { ChromaAuthGuard } from "./chroma-auth.guard";
|
||||
import { GuestDto } from "./dto/guest.dto";
|
||||
|
||||
@ApiTags("auth")
|
||||
@Controller("auth")
|
||||
@@ -163,8 +162,8 @@ export class AuthController {
|
||||
@HttpCode(200)
|
||||
@ApiOperation({ description: "Login as a guest account" })
|
||||
@ApiOkResponse({ description: "Successfully logged in", type: JwtToken })
|
||||
async guest(@Body() guestdto: GuestDto): Promise<JwtToken> {
|
||||
const user = await this.usersService.createGuest(guestdto.username);
|
||||
async guest(): Promise<JwtToken> {
|
||||
const user = await this.usersService.createGuest();
|
||||
await this.settingsService.createUserSetting(user.id);
|
||||
return this.authService.login(user);
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { IsNotEmpty } from "class-validator";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
|
||||
export class GuestDto {
|
||||
@ApiProperty()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { User, Prisma } from "@prisma/client";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import * as bcrypt from "bcryptjs";
|
||||
import { createHash } from "crypto";
|
||||
import { createHash, randomUUID } from "crypto";
|
||||
import { createReadStream, existsSync } from "fs";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
@@ -46,10 +46,10 @@ export class UsersService {
|
||||
});
|
||||
}
|
||||
|
||||
async createGuest(displayName: string): Promise<User> {
|
||||
async createGuest(): Promise<User> {
|
||||
return this.prisma.user.create({
|
||||
data: {
|
||||
username: displayName,
|
||||
username: `Guest ${randomUUID()}`,
|
||||
isGuest: true,
|
||||
// Not realyl clean but better than a separate table or breaking the api by adding nulls.
|
||||
email: null,
|
||||
|
||||
@@ -9,7 +9,7 @@ Resource ./auth.resource
|
||||
*** Test Cases ***
|
||||
LoginAsGuest
|
||||
[Documentation] Login as a guest
|
||||
&{res}= POST /auth/guest {"username": "i-am-a-guest"}
|
||||
&{res}= POST /auth/guest
|
||||
Output
|
||||
Integer response status 200
|
||||
String response body access_token
|
||||
@@ -20,13 +20,12 @@ LoginAsGuest
|
||||
Integer response status 200
|
||||
Boolean response body isGuest true
|
||||
Integer response body partyPlayed 0
|
||||
String response body username "i-am-a-guest"
|
||||
|
||||
[Teardown] DELETE /auth/me
|
||||
|
||||
TwoGuests
|
||||
[Documentation] Login as a guest
|
||||
&{res}= POST /auth/guest {"username": "i-am-another-guest"}
|
||||
&{res}= POST /auth/guest
|
||||
Output
|
||||
Integer response status 200
|
||||
String response body access_token
|
||||
@@ -37,9 +36,8 @@ TwoGuests
|
||||
Integer response status 200
|
||||
Boolean response body isGuest true
|
||||
Integer response body partyPlayed 0
|
||||
String response body username "i-am-another-guest"
|
||||
|
||||
&{res2}= POST /auth/guest {"username": "i-am-a-third-guest"}
|
||||
&{res2}= POST /auth/guest
|
||||
Output
|
||||
Integer response status 200
|
||||
String response body access_token
|
||||
@@ -50,7 +48,6 @@ TwoGuests
|
||||
Integer response status 200
|
||||
Boolean response body isGuest true
|
||||
Integer response body partyPlayed 0
|
||||
String response body username "i-am-a-third-guest"
|
||||
|
||||
[Teardown] Run Keywords DELETE /auth/me
|
||||
... AND Set Headers {"Authorization": "Bearer ${res.body.access_token}"}
|
||||
@@ -58,7 +55,7 @@ TwoGuests
|
||||
|
||||
GuestToNormal
|
||||
[Documentation] Login as a guest and convert to a normal account
|
||||
&{res}= POST /auth/guest {"username": "i-will-be-a-real-user"}
|
||||
&{res}= POST /auth/guest
|
||||
Output
|
||||
Integer response status 200
|
||||
String response body access_token
|
||||
@@ -68,13 +65,11 @@ GuestToNormal
|
||||
Output
|
||||
Integer response status 200
|
||||
Boolean response body isGuest true
|
||||
String response body username "i-will-be-a-real-user"
|
||||
|
||||
${res}= PUT /auth/me { "password": "toto", "email": "awdaw@b.c"}
|
||||
${res}= PUT /auth/me { "username": "toto", "password": "toto", "email": "awdaw@b.c"}
|
||||
Output
|
||||
Integer response status 200
|
||||
String response body username "toto"
|
||||
Boolean response body isGuest false
|
||||
String response body username "i-will-be-a-real-user"
|
||||
|
||||
[Teardown] DELETE /auth/me
|
||||
|
||||
+2
-2
@@ -187,12 +187,12 @@ export default class API {
|
||||
});
|
||||
}
|
||||
|
||||
public static async createAndGetGuestAccount(username: string): Promise<AccessToken> {
|
||||
public static async createAndGetGuestAccount(): Promise<AccessToken> {
|
||||
return API.fetch(
|
||||
{
|
||||
route: '/auth/guest',
|
||||
method: 'POST',
|
||||
body: { username },
|
||||
body: undefined,
|
||||
},
|
||||
{ handler: AccessTokenResponseHandler }
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Slider, View, IconButton, Icon } from 'native-base';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { Audio } from 'expo-av';
|
||||
// import { Audio } from 'expo-av';
|
||||
import { VolumeHigh, VolumeSlash } from 'iconsax-react-native';
|
||||
import { Translate } from '../i18n/i18n';
|
||||
|
||||
export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; bpm: number }) => {
|
||||
const audio = useRef<Audio.Sound | null>(null);
|
||||
const audio = useRef<null>(null);
|
||||
const [enabled, setEnabled] = useState<boolean>(false);
|
||||
const volume = useRef<number>(50);
|
||||
|
||||
@@ -15,12 +15,12 @@ export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; b
|
||||
return;
|
||||
} else if (!audio.current) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
Audio.Sound.createAsync(require('../assets/metronome.mp3')).then((a) => {
|
||||
audio.current = a.sound;
|
||||
});
|
||||
// Audio.Sound.createAsync(require('../assets/metronome.mp3')).then((a) => {
|
||||
// audio.current = a.sound;
|
||||
// });
|
||||
}
|
||||
return () => {
|
||||
audio.current?.unloadAsync();
|
||||
// audio.current?.unloadAsync();
|
||||
};
|
||||
}, [enabled]);
|
||||
useEffect(() => {
|
||||
@@ -28,12 +28,12 @@ export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; b
|
||||
const int = setInterval(() => {
|
||||
if (!enabled) return;
|
||||
if (!audio.current) return;
|
||||
audio.current?.playAsync();
|
||||
// audio.current?.playAsync();
|
||||
}, 60000 / bpm);
|
||||
return () => clearInterval(int);
|
||||
}, [bpm, paused]);
|
||||
useEffect(() => {
|
||||
audio.current?.setVolumeAsync(volume.current / 100);
|
||||
// audio.current?.setVolumeAsync(volume.current / 100);
|
||||
}, [volume.current]);
|
||||
return (
|
||||
<View flex={1}>
|
||||
|
||||
@@ -5,9 +5,12 @@ import { useQuery } from '../../Queries';
|
||||
import Animated, { useSharedValue, withTiming, Easing } from 'react-native-reanimated';
|
||||
import { CursorInfoItem } from '../../models/SongCursorInfos';
|
||||
import { PianoNotes } from '../../state/SoundPlayerSlice';
|
||||
import { Audio } from 'expo-av';
|
||||
// import { Audio } from 'expo-av';
|
||||
import { SvgContainer } from './SvgContainer';
|
||||
import LoadingComponent from '../Loading';
|
||||
import Sound from 'react-native-sound';
|
||||
|
||||
Sound.setCategory('Playback');
|
||||
|
||||
// note we are also using timestamp in a context
|
||||
export type ParitionMagicProps = {
|
||||
@@ -51,7 +54,7 @@ const PartitionMagic = ({
|
||||
const [endPartitionReached, setEndPartitionReached] = React.useState(false);
|
||||
const [isPartitionSvgLoaded, setIsPartitionSvgLoaded] = React.useState(false);
|
||||
const partitionOffset = useSharedValue(0);
|
||||
const pianoSounds = React.useRef<Record<string, Audio.Sound> | null>(null);
|
||||
const pianoSounds = React.useRef<Record<string, Sound> | null>(null);
|
||||
const cursorPaddingVertical = 10;
|
||||
const cursorPaddingHorizontal = 3;
|
||||
|
||||
@@ -72,21 +75,40 @@ const PartitionMagic = ({
|
||||
React.useEffect(() => {
|
||||
if (!pianoSounds.current) {
|
||||
Promise.all(
|
||||
Object.entries(PianoNotes).map(([midiNumber, noteResource]) =>
|
||||
Audio.Sound.createAsync(noteResource, {
|
||||
volume: 1,
|
||||
progressUpdateIntervalMillis: 100,
|
||||
}).then((sound) => [midiNumber, sound.sound] as const)
|
||||
)
|
||||
Object.entries(PianoNotes).map(([midiNumber, noteResource]) => {
|
||||
// Audio.Sound.createAsync(noteResource, {
|
||||
// volume: 1,
|
||||
// progressUpdateIntervalMillis: 100,
|
||||
// }).then((sound) => [midiNumber, sound.sound] as const)
|
||||
return new Promise((resolve, reject) => {
|
||||
const sound = new Sound(noteResource, Sound.MAIN_BUNDLE, (error: any) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve([midiNumber, sound] as const);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
).then((res) => {
|
||||
pianoSounds.current = res.reduce(
|
||||
(prev, curr) => ({ ...prev, [curr[0]]: curr[1] }),
|
||||
{}
|
||||
);
|
||||
// pianoSounds.current = res.reduce(
|
||||
// (prev, curr) => ({ ...prev, [curr[0]]: curr[1] }),
|
||||
// {}
|
||||
// );
|
||||
pianoSounds.current = {};
|
||||
(res as [string, Sound][]).forEach((curr) => {
|
||||
pianoSounds.current![curr[0]] = curr[1];
|
||||
});
|
||||
console.log('sound loaded');
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}, [
|
||||
() => {
|
||||
pianoSounds?.current?.forEach((sound) => {
|
||||
sound.release();
|
||||
});
|
||||
},
|
||||
]);
|
||||
const partitionDims = React.useMemo<[number, number]>(() => {
|
||||
return [data?.pageWidth ?? 0, data?.pageHeight ?? 1];
|
||||
}, [data]);
|
||||
@@ -122,12 +144,16 @@ const PartitionMagic = ({
|
||||
cursor.notes.forEach(({ note, duration }) => {
|
||||
try {
|
||||
const sound = pianoSounds.current![note]!;
|
||||
sound.playAsync().catch(console.error);
|
||||
sound.play((success) => {
|
||||
if (!success) {
|
||||
console.log('Sound did not play');
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
sound.stopAsync();
|
||||
sound.stop();
|
||||
}, duration - 10);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log('Error key: ', note, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import SignUpForm from '../../components/forms/signupform';
|
||||
import API, { APIError } from '../../API';
|
||||
import PopupCC from './PopupCC';
|
||||
import { StyleProp, ViewStyle } from 'react-native';
|
||||
import { useQuery } from '../../Queries';
|
||||
|
||||
const handleSubmit = async (username: string, password: string, email: string) => {
|
||||
try {
|
||||
@@ -37,7 +36,6 @@ const LogoutButtonCC = ({
|
||||
}: LogoutButtonCCProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const user = useQuery(API.getUserInfo);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -56,7 +54,7 @@ const LogoutButtonCC = ({
|
||||
isVisible={isVisible}
|
||||
setIsVisible={setIsVisible}
|
||||
>
|
||||
<SignUpForm onSubmit={handleSubmit} defaultValues={{ username: user.data?.name }} />
|
||||
<SignUpForm onSubmit={handleSubmit} />
|
||||
<ButtonBase
|
||||
style={!collapse ? { width: '100%' } : {}}
|
||||
type="outlined"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Stack, View, Text, Wrap, Image, Row, Column, ScrollView } from 'native-base';
|
||||
import { FunctionComponent, useState } from 'react';
|
||||
import { Stack, View, Text, Wrap, Image, Row, Column, ScrollView, useToast } from 'native-base';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { Linking, Platform, useWindowDimensions } from 'react-native';
|
||||
import ButtonBase from './ButtonBase';
|
||||
import { Translate, translate } from '../../i18n/i18n';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import API, { APIError } from '../../API';
|
||||
import SeparatorBase from './SeparatorBase';
|
||||
import LinkBase from './LinkBase';
|
||||
@@ -12,14 +12,9 @@ import { setAccessToken } from '../../state/UserSlice';
|
||||
import useColorScheme from '../../hooks/colorScheme';
|
||||
import { useAssets } from 'expo-asset';
|
||||
import APKDownloadButton from '../APKDownloadButton';
|
||||
import PopupCC from './PopupCC';
|
||||
import GuestForm from '../forms/guestForm';
|
||||
|
||||
const handleGuestLogin = async (
|
||||
username: string,
|
||||
apiSetter: (accessToken: string) => void
|
||||
): Promise<string> => {
|
||||
const apiAccess = await API.createAndGetGuestAccount(username);
|
||||
const handleGuestLogin = async (apiSetter: (accessToken: string) => void): Promise<string> => {
|
||||
const apiAccess = await API.createAndGetGuestAccount();
|
||||
apiSetter(apiAccess);
|
||||
return translate('loggedIn');
|
||||
};
|
||||
@@ -41,8 +36,8 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
|
||||
}) => {
|
||||
const layout = useWindowDimensions();
|
||||
const dispatch = useDispatch();
|
||||
const toast = useToast();
|
||||
const colorScheme = useColorScheme();
|
||||
const [guestModalIsOpen, openGuestModal] = useState(false);
|
||||
const [logo] = useAssets(
|
||||
colorScheme == 'light'
|
||||
? require('../../assets/icon_light.png')
|
||||
@@ -90,32 +85,21 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
|
||||
</Row>
|
||||
<ButtonBase
|
||||
title={translate('guestMode')}
|
||||
onPress={() => openGuestModal(true)}
|
||||
onPress={async () => {
|
||||
try {
|
||||
handleGuestLogin((accessToken: string) => {
|
||||
dispatch(setAccessToken(accessToken));
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof APIError) {
|
||||
toast.show({ description: translate(error.userMessage) });
|
||||
return;
|
||||
}
|
||||
toast.show({ description: error as string });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Wrap>
|
||||
<PopupCC
|
||||
title={translate('guestMode')}
|
||||
isVisible={guestModalIsOpen}
|
||||
setIsVisible={openGuestModal}
|
||||
>
|
||||
<GuestForm
|
||||
onSubmit={(username) =>
|
||||
handleGuestLogin(username, (accessToken: string) => {
|
||||
dispatch(setAccessToken(accessToken));
|
||||
})
|
||||
.then(() => {
|
||||
openGuestModal(false);
|
||||
return translate('loggedIn');
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error instanceof APIError) {
|
||||
return translate('usernameTaken');
|
||||
}
|
||||
return error as string;
|
||||
})
|
||||
}
|
||||
/>
|
||||
</PopupCC>
|
||||
<ScrollView
|
||||
contentContainerStyle={{
|
||||
padding: 16,
|
||||
@@ -168,9 +152,7 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
|
||||
title={translate('continuewithgoogle')}
|
||||
onPress={() => Linking.openURL(`${API.baseUrl}/auth/login/google`)}
|
||||
/>
|
||||
<SeparatorBase>
|
||||
<Translate translationKey="or" />
|
||||
</SeparatorBase>
|
||||
<SeparatorBase>or</SeparatorBase>
|
||||
<Stack
|
||||
space={3}
|
||||
justifyContent="center"
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import React from 'react';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import { string } from 'yup';
|
||||
import { useToast, Column } from 'native-base';
|
||||
import TextFormField from '../UI/TextFormField';
|
||||
import ButtonBase from '../UI/ButtonBase';
|
||||
import { Sms } from 'iconsax-react-native';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
|
||||
interface GuestFormProps {
|
||||
onSubmit: (username: string) => Promise<string>;
|
||||
}
|
||||
|
||||
const validationSchemas = {
|
||||
username: string().required('Username is required'),
|
||||
};
|
||||
|
||||
const GuestForm = ({ onSubmit }: GuestFormProps) => {
|
||||
const [formData, setFormData] = React.useState({
|
||||
username: {
|
||||
value: '',
|
||||
error: null as string | null,
|
||||
},
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
const layout = useWindowDimensions();
|
||||
|
||||
return (
|
||||
<Column style={{ width: layout.width * 0.5 }}>
|
||||
<TextFormField
|
||||
style={{ marginVertical: 10 }}
|
||||
isRequired
|
||||
icon={Sms}
|
||||
placeholder={translate('username')}
|
||||
value={formData.username.value}
|
||||
error={formData.username.error}
|
||||
onChangeText={(t) => {
|
||||
let error: null | string = null;
|
||||
validationSchemas.username
|
||||
.validate(t)
|
||||
.catch((e) => (error = e.message))
|
||||
.finally(() => {
|
||||
setFormData({ ...formData, username: { value: t, error } });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ButtonBase
|
||||
isDisabled={formData.username.error !== null || formData.username.value === ''}
|
||||
type={'filled'}
|
||||
title={translate('submitBtn')}
|
||||
style={{ marginVertical: 10 }}
|
||||
onPress={() => {
|
||||
onSubmit(formData.username.value)
|
||||
.then((e) => {
|
||||
toast.show({ description: e as string });
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.show({ description: e as string });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default GuestForm;
|
||||
@@ -10,14 +10,13 @@ import ButtonBase from '../UI/ButtonBase';
|
||||
import Spacer from '../UI/Spacer';
|
||||
|
||||
interface SignupFormProps {
|
||||
defaultValues: Partial<{ username: string }>;
|
||||
onSubmit: (username: string, password: string, email: string) => Promise<string>;
|
||||
}
|
||||
|
||||
const SignUpForm = ({ onSubmit, defaultValues }: SignupFormProps) => {
|
||||
const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
||||
const [formData, setFormData] = React.useState({
|
||||
username: {
|
||||
value: defaultValues.username || '',
|
||||
value: '',
|
||||
error: null as string | null,
|
||||
},
|
||||
password: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export const en = {
|
||||
error: 'Error',
|
||||
or: 'or',
|
||||
guestMode: 'Guest Mode',
|
||||
downloadAPK: 'Download Android App',
|
||||
goBackHome: 'Go Back Home',
|
||||
@@ -324,7 +323,6 @@ export const en = {
|
||||
|
||||
export const fr: typeof en = {
|
||||
error: 'Erreur',
|
||||
or: 'ou',
|
||||
downloadAPK: "Télécharger l'App Android",
|
||||
guestMode: 'Mode Invité',
|
||||
goBackHome: "Retourner à l'accueil",
|
||||
@@ -648,7 +646,6 @@ export const fr: typeof en = {
|
||||
|
||||
export const sp: typeof en = {
|
||||
error: 'Error',
|
||||
or: 'u',
|
||||
downloadAPK: 'Descarga la Aplicación de Android',
|
||||
guestMode: 'Modo Invitado',
|
||||
anErrorOccured: 'ocurrió un error',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user